From db5b908d621fcb951717075c2cc1b2ab45f7f4b8 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 5 Aug 2017 00:22:31 -0700 Subject: [PATCH 001/378] Move xterm.js -> Terminal.ts --- src/{xterm.js => Terminal.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{xterm.js => Terminal.ts} (100%) diff --git a/src/xterm.js b/src/Terminal.ts similarity index 100% rename from src/xterm.js rename to src/Terminal.ts From 2104dccec5f02ddfa33142900d45fee6162d9813 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 5 Aug 2017 01:58:42 -0700 Subject: [PATCH 002/378] Fix most errors in Terminal.ts --- src/Charsets.ts | 6 +- src/EventEmitter.ts | 4 + src/Interfaces.ts | 2 +- src/Terminal.ts | 4272 +++++++++++++++++++------------------ src/Types.ts | 5 + src/utils/CircularList.ts | 3 +- src/xterm.ts | 3 + 7 files changed, 2157 insertions(+), 2138 deletions(-) create mode 100644 src/xterm.ts diff --git a/src/Charsets.ts b/src/Charsets.ts index f567ef44..c2c92e8d 100644 --- a/src/Charsets.ts +++ b/src/Charsets.ts @@ -2,17 +2,19 @@ * @license MIT */ +import { Charset } from './Types'; + /** * The character sets supported by the terminal. These enable several languages * to be represented within the terminal with only 8-bit encoding. See ISO 2022 * for a discussion on character sets. Only VT100 character sets are supported. */ -export const CHARSETS: {[key: string]: {[key: string]: string}} = {}; +export const CHARSETS: { [key: string]: Charset } = {}; /** * The default character set, US. */ -export const DEFAULT_CHARSET = CHARSETS['B']; +export const DEFAULT_CHARSET: Charset = CHARSETS['B']; /** * DEC Special Character and Line Drawing Set. diff --git a/src/EventEmitter.ts b/src/EventEmitter.ts index 00c10941..3d34da75 100644 --- a/src/EventEmitter.ts +++ b/src/EventEmitter.ts @@ -68,4 +68,8 @@ export class EventEmitter implements IEventEmitter { public listeners(type): ListenerType[] { return this._events[type] || []; } + + protected destroy(): void { + this._events = {}; + } } diff --git a/src/Interfaces.ts b/src/Interfaces.ts index f19a7f28..c675f7a2 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -92,8 +92,8 @@ export interface ILinkifier { export interface ICircularList extends IEventEmitter { length: number; maxLength: number; + forEach: (callbackfn: (value: T, index: number) => void) => void; - forEach(callbackfn: (value: T, index: number, array: T[]) => void): void; get(index: number): T; set(index: number, value: T): void; push(value: T): void; diff --git a/src/Terminal.ts b/src/Terminal.ts index 5aa069e7..18fcd000 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -11,6 +11,7 @@ */ import { BufferSet } from './BufferSet'; +import { Buffer } from './Buffer'; import { CompositionHelper } from './CompositionHelper'; import { EventEmitter } from './EventEmitter'; import { Viewport } from './Viewport'; @@ -28,6 +29,11 @@ import * as Mouse from './utils/Mouse'; import { CHARSETS } from './Charsets'; import { getRawByteCoords } from './utils/Mouse'; import { translateBufferLineToString } from './utils/BufferLine'; +import { TerminalOptions, CustomKeyEventHandler, Charset } from './Types'; +import { ITerminal, IBrowser } from "./Interfaces"; + +// Declare for RequireJS in loadAddon +declare var define: any; /** * Terminal Emulation References: @@ -63,202 +69,16 @@ var WRITE_BATCH_SIZE = 300; */ var CURSOR_BLINK_INTERVAL = 600; -/** - * Terminal - */ - -/** - * Creates a new `Terminal` object. - * - * @param {object} options An object containing a set of options, the available options are: - * - `cursorBlink` (boolean): Whether the terminal cursor blinks - * - `cols` (number): The number of columns of the terminal (horizontal size) - * - `rows` (number): The number of rows of the terminal (vertical size) - * - * @public - * @class Xterm Xterm - * @alias module:xterm/src/xterm - */ -function Terminal(options) { - var self = this; - - if (!(this instanceof Terminal)) { - return new Terminal(arguments[0], arguments[1], arguments[2]); - } - - self.browser = Browser; - self.cancel = Terminal.cancel; - - EventEmitter.call(this); - - if (typeof options === 'number') { - options = { - cols: arguments[0], - rows: arguments[1], - handler: arguments[2] - }; - } - - options = options || {}; - Object.keys(Terminal.defaults).forEach(function(key) { - if (options[key] == null) { - options[key] = Terminal.options[key]; - if (Terminal[key] !== Terminal.defaults[key]) { - options[key] = Terminal[key]; - } - } - self[key] = options[key]; - }); - if (options.colors.length === 8) { - options.colors = options.colors.concat(Terminal._colors.slice(8)); - } else if (options.colors.length === 16) { - options.colors = options.colors.concat(Terminal._colors.slice(16)); - } else if (options.colors.length === 10) { - options.colors = options.colors.slice(0, -2).concat( - Terminal._colors.slice(8, -2), options.colors.slice(-2)); - } else if (options.colors.length === 18) { - options.colors = options.colors.concat( - Terminal._colors.slice(16, -2), options.colors.slice(-2)); - } - this.colors = options.colors; - this.options = options; - // this.context = options.context || window; - // this.document = options.document || document; - this.parent = options.body || options.parent || ( - document ? document.getElementsByTagName('body')[0] : null - ); - this.cols = options.cols || options.geometry[0]; - this.rows = options.rows || options.geometry[1]; - this.geometry = [this.cols, this.rows]; - - if (options.handler) { - this.on('data', options.handler); - } - - this.cursorState = 0; - this.cursorHidden = false; - this.convertEol; - this.queue = ''; - this.customKeyEventHandler = null; - this.cursorBlinkInterval = null; - - // modes - this.applicationKeypad = false; - this.applicationCursor = false; - this.originMode = false; - this.insertMode = false; - this.wraparoundMode = true; // defaults: xterm - true, vt100 - false - - // charset - this.charset = null; - this.gcharset = null; - this.glevel = 0; - this.charsets = [null]; - - // mouse properties - this.decLocator; - this.x10Mouse; - this.vt200Mouse; - this.vt300Mouse; - this.normalMouse; - this.mouseEvents; - this.sendFocus; - this.utfMouse; - this.sgrMouse; - this.urxvtMouse; - - // misc - this.element; - this.children; - this.refreshStart; - this.refreshEnd; - this.savedX; - this.savedY; - this.savedCols; - - // stream - this.readable = true; - this.writable = true; - - this.defAttr = (0 << 18) | (257 << 9) | (256 << 0); - this.curAttr = this.defAttr; - - this.params = []; - this.currentParam = 0; - this.prefix = ''; - this.postfix = ''; - - this.inputHandler = new InputHandler(this); - this.parser = new Parser(this.inputHandler, this); - // Reuse renderer if the Terminal is being recreated via a Terminal.reset call. - this.renderer = this.renderer || null; - this.selectionManager = this.selectionManager || null; - this.linkifier = this.linkifier || new Linkifier(); - - // user input states - this.writeBuffer = []; - this.writeInProgress = false; - - /** - * Whether _xterm.js_ sent XOFF in order to catch up with the pty process. - * This is a distinct state from writeStopped so that if the user requested - * XOFF via ^S that it will not automatically resume when the writeBuffer goes - * below threshold. - */ - this.xoffSentToCatchUp = false; - - /** Whether writing has been stopped as a result of XOFF */ - this.writeStopped = false; - - // leftover surrogate high from previous write invocation - this.surrogate_high = ''; - - // Create the terminal's buffers and set the current buffer - this.buffers = new BufferSet(this); - this.buffer = this.buffers.active; // Convenience shortcut; - this.buffers.on('activate', function (buffer) { - this._terminal.buffer = buffer; - }); - - var i = this.rows; - - while (i--) { - this.buffer.lines.push(this.blankLine()); - } - // Ensure the selection manager has the correct buffer - if (this.selectionManager) { - this.selectionManager.setBuffer(this.buffer.lines); - } - - this.setupStops(); - - // Store if user went browsing history in scrollback - this.userScrolling = false; -} - -inherits(Terminal, EventEmitter); - -/** - * back_color_erase feature for xterm. - */ -Terminal.prototype.eraseAttr = function() { - // if (this.is('screen')) return this.defAttr; - return (this.defAttr & ~0x1ff) | (this.curAttr & 0x1ff); -}; - -/** - * Colors - */ // Colors 0-15 -Terminal.tangoColors = [ +const tangoColors = [ // dark: '#2e3436', '#cc0000', @@ -281,8 +101,8 @@ Terminal.tangoColors = [ // Colors 0-15 + 16-255 // Much thanks to TooTallNate for writing this. -Terminal.colors = (function() { - var colors = Terminal.tangoColors.slice() +const defaultColors = (function() { + let colors = tangoColors.slice() , r = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff] , i; @@ -294,9 +114,10 @@ Terminal.colors = (function() { // 232-255 (grey) i = 0; + let c: number; for (; i < 24; i++) { - r = 8 + i * 10; - out(r, r, r); + c = 8 + i * 10; + out(c, c, c); } function out(r, g, b) { @@ -311,11 +132,11 @@ Terminal.colors = (function() { return colors; })(); -Terminal._colors = Terminal.colors.slice(); +const _colors = defaultColors.slice(); -Terminal.vcolors = (function() { +const vcolors = (function() { var out = [] - , colors = Terminal.colors + , colors = defaultColors , i = 0 , color; @@ -331,13 +152,8 @@ Terminal.vcolors = (function() { return out; })(); -/** - * Options - */ - -Terminal.defaults = { - colors: Terminal.colors, - theme: 'default', +const DEFAULT_OPTIONS: TerminalOptions = { + colors: defaultColors, convertEol: false, termName: 'xterm', geometry: [80, 24], @@ -356,1966 +172,2203 @@ Terminal.defaults = { // focusKeys: false, }; -Terminal.options = {}; - -Terminal.focus = null; - -each(keys(Terminal.defaults), function(key) { - Terminal[key] = Terminal.defaults[key]; - Terminal.options[key] = Terminal.defaults[key]; -}); - -/** - * Focus the terminal. Delegates focus handling to the terminal's DOM element. - */ -Terminal.prototype.focus = function() { - return this.textarea.focus(); -}; - -/** - * Retrieves an option's value from the terminal. - * @param {string} key The option key. - */ -Terminal.prototype.getOption = function(key) { - if (!(key in Terminal.defaults)) { - throw new Error('No option with key "' + key + '"'); - } - - if (typeof this.options[key] !== 'undefined') { - return this.options[key]; - } - - return this[key]; -}; - -/** - * Sets an option on the terminal. - * @param {string} key The option key. - * @param {string} value The option value. - */ -Terminal.prototype.setOption = function(key, value) { - if (!(key in Terminal.defaults)) { - throw new Error('No option with key "' + key + '"'); - } - switch (key) { - case 'scrollback': - if (value < this.rows) { - let msg = 'Setting the scrollback value less than the number of rows '; - - msg += `(${this.rows}) is not allowed.`; - - console.warn(msg); - return false; - } - - if (this.options[key] !== value) { - if (this.buffer.lines.length > value) { - const amountToTrim = this.buffer.lines.length - value; - const needsRefresh = (this.buffer.ydisp - amountToTrim < 0); - this.buffer.lines.trimStart(amountToTrim); - this.buffer.ybase = Math.max(this.buffer.ybase - amountToTrim, 0); - this.buffer.ydisp = Math.max(this.buffer.ydisp - amountToTrim, 0); - if (needsRefresh) { - this.refresh(0, this.rows - 1); - } - } - this.buffer.lines.maxLength = value; - this.viewport.syncScrollArea(); - } - break; - } - this[key] = value; - this.options[key] = value; - switch (key) { - case 'cursorBlink': this.setCursorBlinking(value); break; - case 'cursorStyle': - this.element.classList.toggle(`xterm-cursor-style-block`, value === 'block'); - this.element.classList.toggle(`xterm-cursor-style-underline`, value === 'underline'); - this.element.classList.toggle(`xterm-cursor-style-bar`, value === 'bar'); - break; - case 'tabStopWidth': this.setupStops(); break; - } -}; - -Terminal.prototype.restartCursorBlinking = function () { - this.setCursorBlinking(this.options.cursorBlink); -}; - -Terminal.prototype.setCursorBlinking = function (enabled) { - this.element.classList.toggle('xterm-cursor-blink', enabled); - this.clearCursorBlinkingInterval(); - if (enabled) { - var self = this; - this.cursorBlinkInterval = setInterval(function () { - self.element.classList.toggle('xterm-cursor-blink-on'); - }, CURSOR_BLINK_INTERVAL); - } -}; - -Terminal.prototype.clearCursorBlinkingInterval = function () { - this.element.classList.remove('xterm-cursor-blink-on'); - if (this.cursorBlinkInterval) { - clearInterval(this.cursorBlinkInterval); - this.cursorBlinkInterval = null; - } -}; - -/** - * Binds the desired focus behavior on a given terminal object. - * - * @static - */ -Terminal.bindFocus = function (term) { - on(term.textarea, 'focus', function (ev) { - if (term.sendFocus) { - term.send(C0.ESC + '[I'); - } - term.element.classList.add('focus'); - term.showCursor(); - term.restartCursorBlinking.apply(term); - Terminal.focus = term; - term.emit('focus', {terminal: term}); - }); -}; - -/** - * Blur the terminal. Delegates blur handling to the terminal's DOM element. - */ -Terminal.prototype.blur = function() { - return this.textarea.blur(); -}; - -/** - * Binds the desired blur behavior on a given terminal object. - * - * @static - */ -Terminal.bindBlur = function (term) { - on(term.textarea, 'blur', function (ev) { - term.refresh(term.buffer.y, term.buffer.y); - if (term.sendFocus) { - term.send(C0.ESC + '[O'); - } - term.element.classList.remove('focus'); - term.clearCursorBlinkingInterval.apply(term); - Terminal.focus = null; - term.emit('blur', {terminal: term}); - }); -}; - -/** - * Initialize default behavior - */ -Terminal.prototype.initGlobal = function() { - var term = this; - - Terminal.bindKeys(this); - Terminal.bindFocus(this); - Terminal.bindBlur(this); - - // Bind clipboard functionality - on(this.element, 'copy', event => { - // If mouse events are active it means the selection manager is disabled and - // copy should be handled by the host program. - if (!term.hasSelection()) { - return; - } - copyHandler(event, term, this.selectionManager); - }); - const pasteHandlerWrapper = event => pasteHandler(event, term); - on(this.textarea, 'paste', pasteHandlerWrapper); - on(this.element, 'paste', pasteHandlerWrapper); - - // Handle right click context menus - if (term.browser.isFirefox) { - // Firefox doesn't appear to fire the contextmenu event on right click - on(this.element, 'mousedown', event => { - if (event.button == 2) { - rightClickHandler(event, this.textarea, this.selectionManager); - } - }); - } else { - on(this.element, 'contextmenu', event => { - rightClickHandler(event, this.textarea, this.selectionManager); - }); - } - - // Move the textarea under the cursor when middle clicking on Linux to ensure - // middle click to paste selection works. This only appears to work in Chrome - // at the time is writing. - if (term.browser.isLinux) { - // Use auxclick event over mousedown the latter doesn't seem to work. Note - // that the regular click event doesn't fire for the middle mouse button. - on(this.element, 'auxclick', event => { - if (event.button === 1) { - moveTextAreaUnderMouseCursor(event, this.textarea, this.selectionManager); - } - }); - } -}; - -/** - * Apply key handling to the terminal - */ -Terminal.bindKeys = function(term) { - on(term.element, 'keydown', function(ev) { - if (document.activeElement != this) { - return; - } - term.keyDown(ev); - }, true); - - on(term.element, 'keypress', function(ev) { - if (document.activeElement != this) { - return; - } - term.keyPress(ev); - }, true); - - on(term.element, 'keyup', function(ev) { - if (!wasMondifierKeyOnlyEvent(ev)) { - term.focus(term); - } - }, true); - - on(term.textarea, 'keydown', function(ev) { - term.keyDown(ev); - }, true); - - on(term.textarea, 'keypress', function(ev) { - term.keyPress(ev); - // Truncate the textarea's value, since it is not needed - this.value = ''; - }, true); - - on(term.textarea, 'compositionstart', term.compositionHelper.compositionstart.bind(term.compositionHelper)); - on(term.textarea, 'compositionupdate', term.compositionHelper.compositionupdate.bind(term.compositionHelper)); - on(term.textarea, 'compositionend', term.compositionHelper.compositionend.bind(term.compositionHelper)); - term.on('refresh', term.compositionHelper.updateCompositionElements.bind(term.compositionHelper)); - term.on('refresh', function (data) { - term.queueLinkification(data.start, data.end) - }); -}; -/** - * Insert the given row to the terminal or produce a new one - * if no row argument is passed. Return the inserted row. - * @param {HTMLElement} row (optional) The row to append to the terminal. - */ -Terminal.prototype.insertRow = function (row) { - if (typeof row != 'object') { - row = document.createElement('div'); - } - this.rowContainer.appendChild(row); - this.children.push(row); - return row; -}; -/** - * Opens the terminal within an element. - * - * @param {HTMLElement} parent The element to create the terminal within. - * @param {boolean} focus Focus the terminal, after it gets instantiated in the DOM - */ -Terminal.prototype.open = function(parent, focus) { - var self=this, i=0, div; - this.parent = parent || this.parent; - if (!this.parent) { - throw new Error('Terminal requires a parent element.'); - } +export class Terminal extends EventEmitter implements ITerminal { + public textarea: HTMLTextAreaElement; + public element: HTMLElement; + public rowContainer: HTMLElement; - // Grab global elements - this.context = this.parent.ownerDocument.defaultView; - this.document = this.parent.ownerDocument; - this.body = this.document.getElementsByTagName('body')[0]; + // TODO: Do we need a reference to body? There is also a body variable? + // The body of the terminal + private parent: HTMLElement; + private context: Window; + private document: Document; + private body: HTMLBodyElement; + private viewportScrollArea: HTMLElement; + private viewportElement: HTMLElement; + private selectionContainer: HTMLElement; + private helperContainer: HTMLElement; + private compositionView: HTMLElement; + private charSizeStyleElement: HTMLStyleElement; - //Create main element container - this.element = this.document.createElement('div'); - this.element.classList.add('terminal'); - this.element.classList.add('xterm'); - this.element.classList.add('xterm-theme-' + this.theme); - this.element.classList.add(`xterm-cursor-style-${this.options.cursorStyle}`); - this.setCursorBlinking(this.options.cursorBlink); + // TODO: This should be removed from the interface, opting for modules to pull + // in Browser for themselves. + public browser: IBrowser = Browser; - this.element.setAttribute('tabindex', 0); + private options: TerminalOptions; + private colors: any; - this.viewportElement = document.createElement('div'); - this.viewportElement.classList.add('xterm-viewport'); - this.element.appendChild(this.viewportElement); - this.viewportScrollArea = document.createElement('div'); - this.viewportScrollArea.classList.add('xterm-scroll-area'); - this.viewportElement.appendChild(this.viewportScrollArea); + // TODO: This can be changed to an enum or boolean, 0 and 1 seem to be the only options + private cursorState: number = 0; + private cursorHidden: boolean = false; + private convertEol: boolean; + // TODO: This is the data queue for send, improve name and documentation + private queue: string = ''; + private customKeyEventHandler: CustomKeyEventHandler = null; + // The ID from a setInterval that tracks the blink animation. This animation + // is done in JS due to a Chromium bug with CSS animations that thrashed the + // CPU. + private cursorBlinkInterval: NodeJS.Timer = null; - // Create the selection container. - this.selectionContainer = document.createElement('div'); - this.selectionContainer.classList.add('xterm-selection'); - this.element.appendChild(this.selectionContainer); + // modes + private applicationKeypad: boolean = false; + private applicationCursor: boolean = false; + private originMode: boolean = false; + private insertMode: boolean = false; + private wraparoundMode: boolean = true; // defaults: xterm - true, vt100 - false - // Create the container that will hold the lines of the terminal and then - // produce the lines the lines. - this.rowContainer = document.createElement('div'); - this.rowContainer.classList.add('xterm-rows'); - this.element.appendChild(this.rowContainer); - this.children = []; - this.linkifier.attachToDom(document, this.children); + // charset + // The current charset + private charset: Charset = null; + private gcharset: number = null; + private glevel: number = 0; + private charsets: Charset[] = [null]; - // 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'); - // TODO: This should probably be inserted once it's filled to prevent an additional layout - this.element.appendChild(this.helperContainer); - this.textarea = document.createElement('textarea'); - this.textarea.classList.add('xterm-helper-textarea'); - this.textarea.setAttribute('autocorrect', 'off'); - this.textarea.setAttribute('autocapitalize', 'off'); - this.textarea.setAttribute('spellcheck', 'false'); - this.textarea.tabIndex = 0; - this.textarea.addEventListener('focus', function() { - self.emit('focus', {terminal: self}); - }); - this.textarea.addEventListener('blur', function() { - self.emit('blur', {terminal: self}); - }); - this.helperContainer.appendChild(this.textarea); + // mouse properties + private decLocator; + private x10Mouse; + private vt200Mouse; + private vt300Mouse; + private normalMouse; + private mouseEvents; + private sendFocus; + private utfMouse; + private sgrMouse; + private urxvtMouse; - this.compositionView = document.createElement('div'); - this.compositionView.classList.add('composition-view'); - this.compositionHelper = new CompositionHelper(this.textarea, this.compositionView, this); - this.helperContainer.appendChild(this.compositionView); + // misc + private children; + private refreshStart; + private refreshEnd; + private savedX; + private savedY; + private savedCols; - this.charSizeStyleElement = document.createElement('style'); - this.helperContainer.appendChild(this.charSizeStyleElement); + // stream + private readable: boolean = true; + private writable: boolean = true; - for (; i < this.rows; i++) { - this.insertRow(); - } - this.parent.appendChild(this.element); + private defAttr: number = (0 << 18) | (257 << 9) | (256 << 0); + private curAttr: number = (0 << 18) | (257 << 9) | (256 << 0); - this.charMeasure = new CharMeasure(document, this.helperContainer); - this.charMeasure.on('charsizechanged', function () { - self.updateCharSizeStyles(); - }); - this.charMeasure.measure(); + private params: (string | number)[] = []; + private currentParam: string | number = 0; + private prefix: string = ''; + private postfix: string = ''; - this.viewport = new Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasure); - this.renderer = new Renderer(this); - this.selectionManager = new SelectionManager( - this, this.buffer.lines, this.rowContainer, this.charMeasure - ); - this.selectionManager.on('refresh', data => { - this.renderer.refreshSelection(data.start, data.end); - }); - this.selectionManager.on('newselection', text => { - // If there's a new selection, put it into the textarea, focus and select it - // in order to register it as a selection on the OS. This event is fired - // only on Linux to enable middle click to paste selection. - this.textarea.value = text; - this.textarea.focus(); - this.textarea.select(); - }); - this.on('scroll', () => this.selectionManager.refresh()); - this.viewportElement.addEventListener('scroll', () => this.selectionManager.refresh()); - - // Setup loop that draws to screen - this.refresh(0, this.rows - 1); - - // Initialize global actions that - // need to be taken on the document. - this.initGlobal(); + // user input states + public writeBuffer: string[] = []; + private writeInProgress: boolean = false; /** - * Automatic focus functionality. - * TODO: Default to `false` starting with xterm.js 3.0. + * Whether _xterm.js_ sent XOFF in order to catch up with the pty process. + * This is a distinct state from writeStopped so that if the user requested + * XOFF via ^S that it will not automatically resume when the writeBuffer goes + * below threshold. */ - if (typeof focus == 'undefined') { - let message = 'You did not pass the `focus` argument in `Terminal.prototype.open()`.\n'; + private xoffSentToCatchUp: boolean = false; - message += 'The `focus` argument now defaults to `true` but starting with xterm.js 3.0 '; - message += 'it will default to `false`.'; + /** Whether writing has been stopped as a result of XOFF */ + private writeStopped: boolean = false; - console.warn(message); - focus = true; - } + // leftover surrogate high from previous write invocation + private surrogate_high: string = ''; - if (focus) { - this.focus(); - } + // Store if user went browsing history in scrollback + private userScrolling: boolean = false; - // Listen for mouse events and translate - // them into terminal mouse protocols. - this.bindMouse(); + private inputHandler: InputHandler; + private parser: Parser; + private renderer: Renderer; + private selectionManager: SelectionManager;; + private linkifier: Linkifier; + private buffers: BufferSet; + private buffer: Buffer; + private viewport: Viewport; + private compositionHelper: CompositionHelper; + private charMeasure: CharMeasure; + + public cols: number; + public rows: number; + public geometry: [/*cols*/number, /*rows*/number]; /** - * This event is emitted when terminal has completed opening. + * Creates a new `Terminal` object. * - * @event open + * @param {object} options An object containing a set of options, the available options are: + * - `cursorBlink` (boolean): Whether the terminal cursor blinks + * - `cols` (number): The number of columns of the terminal (horizontal size) + * - `rows` (number): The number of rows of the terminal (vertical size) + * + * @public + * @class Xterm Xterm + * @alias module:xterm/src/xterm */ - this.emit('open'); -}; + constructor( + options: any = {} + ) { + super(); + let self: any = this; -/** - * Attempts to load an add-on using CommonJS or RequireJS (whichever is available). - * @param {string} addon The name of the addon to load - * @static - */ -Terminal.loadAddon = function(addon, callback) { - if (typeof exports === 'object' && typeof module === 'object') { - // CommonJS - return require('./addons/' + addon + '/' + addon); - } else if (typeof define == 'function') { - // RequireJS - return require(['./addons/' + addon + '/' + addon], callback); - } else { - console.error('Cannot load a module without a CommonJS or RequireJS environment.'); - return false; - } -}; - -/** - * Updates the helper CSS class with any changes necessary after the terminal's - * character width has been changed. - */ -Terminal.prototype.updateCharSizeStyles = function() { - this.charSizeStyleElement.textContent = - `.xterm-wide-char{width:${this.charMeasure.width * 2}px;}` + - `.xterm-normal-char{width:${this.charMeasure.width}px;}` + - `.xterm-rows > div{height:${this.charMeasure.height}px;}`; -} - -/** - * XTerm mouse events - * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking - * To better understand these - * the xterm code is very helpful: - * Relevant files: - * button.c, charproc.c, misc.c - * Relevant functions in xterm/button.c: - * BtnCode, EmitButtonCode, EditorButton, SendMousePosition - */ -Terminal.prototype.bindMouse = function() { - var el = this.element, self = this, pressed = 32; - - // mouseup, mousedown, wheel - // left click: ^[[M 3<^[[M#3< - // wheel up: ^[[M`3> - function sendButton(ev) { - var button - , pos; - - // get the xterm-style button - button = getButton(ev); - - // get mouse coordinates - pos = getRawByteCoords(ev, self.rowContainer, self.charMeasure, self.cols, self.rows); - if (!pos) return; - - sendEvent(button, pos); - - switch (ev.overrideType || ev.type) { - case 'mousedown': - pressed = button; - break; - case 'mouseup': - // keep it at the left - // button, just in case. - pressed = 32; - break; - case 'wheel': - // nothing. don't - // interfere with - // `pressed`. - break; - } - } - - // motion example of a left click: - // ^[[M 3<^[[M@4<^[[M@5<^[[M@6<^[[M@7<^[[M#7< - function sendMove(ev) { - var button = pressed - , pos; - - pos = getRawByteCoords(ev, self.rowContainer, self.charMeasure, self.cols, self.rows); - if (!pos) return; - - // buttons marked as motions - // are incremented by 32 - button += 32; - - sendEvent(button, pos); - } - - // encode button and - // position to characters - function encode(data, ch) { - if (!self.utfMouse) { - if (ch === 255) return data.push(0); - if (ch > 127) ch = 127; - data.push(ch); - } else { - if (ch === 2047) return data.push(0); - if (ch < 127) { - data.push(ch); - } else { - if (ch > 2047) ch = 2047; - data.push(0xC0 | (ch >> 6)); - data.push(0x80 | (ch & 0x3F)); - } - } - } - - // send a mouse event: - // regular/utf8: ^[[M Cb Cx Cy - // urxvt: ^[[ Cb ; Cx ; Cy M - // sgr: ^[[ Cb ; Cx ; Cy M/m - // vt300: ^[[ 24(1/3/5)~ [ Cx , Cy ] \r - // locator: CSI P e ; P b ; P r ; P c ; P p & w - function sendEvent(button, pos) { - // self.emit('mouse', { - // x: pos.x - 32, - // y: pos.x - 32, - // button: button - // }); - - if (self.vt300Mouse) { - // NOTE: Unstable. - // http://www.vt100.net/docs/vt3xx-gp/chapter15.html - button &= 3; - pos.x -= 32; - pos.y -= 32; - var data = C0.ESC + '[24'; - if (button === 0) data += '1'; - else if (button === 1) data += '3'; - else if (button === 2) data += '5'; - else if (button === 3) return; - else data += '0'; - data += '~[' + pos.x + ',' + pos.y + ']\r'; - self.send(data); - return; - } - - if (self.decLocator) { - // NOTE: Unstable. - button &= 3; - pos.x -= 32; - pos.y -= 32; - if (button === 0) button = 2; - else if (button === 1) button = 4; - else if (button === 2) button = 6; - else if (button === 3) button = 3; - self.send(C0.ESC + '[' - + button - + ';' - + (button === 3 ? 4 : 0) - + ';' - + pos.y - + ';' - + pos.x - + ';' - + (pos.page || 0) - + '&w'); - return; - } - - if (self.urxvtMouse) { - pos.x -= 32; - pos.y -= 32; - pos.x++; - pos.y++; - self.send(C0.ESC + '[' + button + ';' + pos.x + ';' + pos.y + 'M'); - return; - } - - if (self.sgrMouse) { - pos.x -= 32; - pos.y -= 32; - self.send(C0.ESC + '[<' - + (((button & 3) === 3 ? button & ~3 : button) - 32) - + ';' - + pos.x - + ';' - + pos.y - + ((button & 3) === 3 ? 'm' : 'M')); - return; - } - - var data = []; - - encode(data, button); - encode(data, pos.x); - encode(data, pos.y); - - self.send(C0.ESC + '[M' + String.fromCharCode.apply(String, data)); - } - - function getButton(ev) { - var button - , shift - , meta - , ctrl - , mod; - - // two low bits: - // 0 = left - // 1 = middle - // 2 = right - // 3 = release - // wheel up/down: - // 1, and 2 - with 64 added - switch (ev.overrideType || ev.type) { - case 'mousedown': - button = ev.button != null - ? +ev.button - : ev.which != null - ? ev.which - 1 - : null; - - if (self.browser.isMSIE) { - button = button === 1 ? 0 : button === 4 ? 1 : button; - } - break; - case 'mouseup': - button = 3; - break; - case 'DOMMouseScroll': - button = ev.detail < 0 - ? 64 - : 65; - break; - case 'wheel': - button = ev.wheelDeltaY > 0 - ? 64 - : 65; - break; - } - - // next three bits are the modifiers: - // 4 = shift, 8 = meta, 16 = control - shift = ev.shiftKey ? 4 : 0; - meta = ev.metaKey ? 8 : 0; - ctrl = ev.ctrlKey ? 16 : 0; - mod = shift | meta | ctrl; - - // no mods - if (self.vt200Mouse) { - // ctrl only - mod &= ctrl; - } else if (!self.normalMouse) { - mod = 0; - } - - // increment to SP - button = (32 + (mod << 2)) + button; - - return button; - } - - on(el, 'mousedown', function(ev) { - - // Prevent the focus on the textarea from getting lost - // and make sure we get focused on mousedown - ev.preventDefault(); - self.focus(); - - if (!self.mouseEvents) return; - - // send the button - sendButton(ev); - - // fix for odd bug - //if (self.vt200Mouse && !self.normalMouse) { - if (self.vt200Mouse) { - ev.overrideType = 'mouseup'; - sendButton(ev); - return self.cancel(ev); - } - - // bind events - if (self.normalMouse) on(self.document, 'mousemove', sendMove); - - // x10 compatibility mode can't send button releases - if (!self.x10Mouse) { - on(self.document, 'mouseup', function up(ev) { - sendButton(ev); - if (self.normalMouse) off(self.document, 'mousemove', sendMove); - off(self.document, 'mouseup', up); - return self.cancel(ev); + // TODO: Can this be removed? + if (!(this instanceof Terminal)) { + return new Terminal({ + cols: arguments[0], + rows: arguments[1], + handler: arguments[2] }); } - return self.cancel(ev); - }); - - //if (self.normalMouse) { - // on(self.document, 'mousemove', sendMove); - //} - - on(el, 'wheel', function(ev) { - if (!self.mouseEvents) return; - if (self.x10Mouse - || self.vt300Mouse - || self.decLocator) return; - sendButton(ev); - return self.cancel(ev); - }); - - // allow wheel scrolling in - // the shell for example - on(el, 'wheel', function(ev) { - if (self.mouseEvents) return; - self.viewport.onWheel(ev); - return self.cancel(ev); - }); - - on(el, 'touchstart', function(ev) { - if (self.mouseEvents) return; - self.viewport.onTouchStart(ev); - return self.cancel(ev); - }); - - on(el, 'touchmove', function(ev) { - if (self.mouseEvents) return; - self.viewport.onTouchMove(ev); - return self.cancel(ev); - }); -}; - -/** - * Destroys the terminal. - */ -Terminal.prototype.destroy = function() { - this.readable = false; - this.writable = false; - this._events = {}; - this.handler = function() {}; - this.write = function() {}; - if (this.element && this.element.parentNode) { - this.element.parentNode.removeChild(this.element); - } - //this.emit('close'); -}; - -/** - * Tells the renderer to refresh terminal content between two rows (inclusive) at the next - * opportunity. - * @param {number} start The row to start from (between 0 and this.rows - 1). - * @param {number} end The row to end at (between start and this.rows - 1). - */ -Terminal.prototype.refresh = function(start, end) { - if (this.renderer) { - this.renderer.queueRefresh(start, end); - } -}; - -/** - * Queues linkification for the specified rows. - * @param {number} start The row to start from (between 0 and this.rows - 1). - * @param {number} end The row to end at (between start and this.rows - 1). - */ -Terminal.prototype.queueLinkification = function(start, end) { - if (this.linkifier) { - for (let i = start; i <= end; i++) { - this.linkifier.linkifyRow(i); - } - } -}; - -/** - * Display the cursor element - */ -Terminal.prototype.showCursor = function() { - if (!this.cursorState) { - this.cursorState = 1; - this.refresh(this.buffer.y, this.buffer.y); - } -}; - -/** - * Scroll the terminal down 1 row, creating a blank line. - * @param {boolean} isWrapped Whether the new line is wrapped from the previous - * line. - */ -Terminal.prototype.scroll = function(isWrapped) { - var row; - - // Make room for the new row in lines - if (this.buffer.lines.length === this.buffer.lines.maxLength) { - this.buffer.lines.trimStart(1); - this.buffer.ybase--; - if (this.buffer.ydisp !== 0) { - this.buffer.ydisp--; - } - } - - this.buffer.ybase++; - - // TODO: Why is this done twice? - if (!this.userScrolling) { - this.buffer.ydisp = this.buffer.ybase; - } - - // last line - row = this.buffer.ybase + this.rows - 1; - - // subtract the bottom scroll region - row -= this.rows - 1 - this.buffer.scrollBottom; - - if (row === this.buffer.lines.length) { - // Optimization: pushing is faster than splicing when they amount to the same behavior - this.buffer.lines.push(this.blankLine(undefined, isWrapped)); - } else { - // add our new line - this.buffer.lines.splice(row, 0, this.blankLine(undefined, isWrapped)); - } - - if (this.buffer.scrollTop !== 0) { - if (this.buffer.ybase !== 0) { - this.buffer.ybase--; - if (!this.userScrolling) { - this.buffer.ydisp = this.buffer.ybase; + Object.keys(DEFAULT_OPTIONS).forEach(function(key) { + if (options[key] == null) { + if (Terminal[key] !== DEFAULT_OPTIONS[key]) { + options[key] = Terminal[key]; + } } - } - this.buffer.lines.splice(this.buffer.ybase + this.buffer.scrollTop, 1); - } + self[key] = options[key]; + }); - // this.maxRange(); - this.updateRange(this.buffer.scrollTop); - this.updateRange(this.buffer.scrollBottom); + if (options.colors.length === 8) { + options.colors = options.colors.concat(_colors.slice(8)); + } else if (options.colors.length === 16) { + options.colors = options.colors.concat(_colors.slice(16)); + } else if (options.colors.length === 10) { + options.colors = options.colors.slice(0, -2).concat( + _colors.slice(8, -2), options.colors.slice(-2)); + } else if (options.colors.length === 18) { + options.colors = options.colors.concat( + _colors.slice(16, -2), options.colors.slice(-2)); + } + this.colors = options.colors; + + // this.context = options.context || window; + // this.document = options.document || document; + // TODO: WHy not document.body? + this.parent = document ? document.getElementsByTagName('body')[0] : null; + + this.cols = options.cols || options.geometry[0]; + this.rows = options.rows || options.geometry[1]; + this.geometry = [this.cols, this.rows]; + + if (options.handler) { + this.on('data', options.handler); + } + + this.inputHandler = new InputHandler(this); + this.parser = new Parser(this.inputHandler, this); + // Reuse renderer if the Terminal is being recreated via a Terminal.reset call. + this.renderer = this.renderer || null; + this.selectionManager = this.selectionManager || null; + this.linkifier = this.linkifier || new Linkifier(); + + // Create the terminal's buffers and set the current buffer + this.buffers = new BufferSet(this); + this.buffer = this.buffers.active; // Convenience shortcut; + this.buffers.on('activate', function (buffer) { + this._terminal.buffer = buffer; + }); + + let i = this.rows; + + while (i--) { + this.buffer.lines.push(this.blankLine()); + } + // Ensure the selection manager has the correct buffer + if (this.selectionManager) { + this.selectionManager.setBuffer(this.buffer.lines); + } + + this.setupStops(); + } /** - * This event is emitted whenever the terminal is scrolled. - * The one parameter passed is the new y display position. - * - * @event scroll + * back_color_erase feature for xterm. */ - this.emit('scroll', this.buffer.ydisp); -}; + public eraseAttr(): any { + // if (this.is('screen')) return this.defAttr; + return (this.defAttr & ~0x1ff) | (this.curAttr & 0x1ff); + } -/** - * Scroll the display of the terminal - * @param {number} disp The number of lines to scroll down (negatives scroll up). - * @param {boolean} suppressScrollEvent Don't emit the scroll event as scrollDisp. This is used - * to avoid unwanted events being handled by the veiwport when the event was triggered from the - * viewport originally. - */ -Terminal.prototype.scrollDisp = function(disp, suppressScrollEvent) { - if (disp < 0) { - if (this.buffer.ydisp === 0) { - return; + /** + * Focus the terminal. Delegates focus handling to the terminal's DOM element. + */ + public focus(): void { + this.textarea.focus(); + } + + /** + * Retrieves an option's value from the terminal. + * @param {string} key The option key. + */ + public getOption(key: string): any { + if (!(key in DEFAULT_OPTIONS)) { + throw new Error('No option with key "' + key + '"'); } - this.userScrolling = true; - } else if (disp + this.buffer.ydisp >= this.buffer.ybase) { - this.userScrolling = false; + + if (typeof this.options[key] !== 'undefined') { + return this.options[key]; + } + + return this[key]; } - const oldYdisp = this.buffer.ydisp; - this.buffer.ydisp = Math.max(Math.min(this.buffer.ydisp + disp, this.buffer.ybase), 0); + /** + * Sets an option on the terminal. + * @param {string} key The option key. + * @param {any} value The option value. + */ + public setOption(key: string, value: any) { + // TODO: Give value a better type (boolean | string, ...) + if (!(key in DEFAULT_OPTIONS)) { + throw new Error('No option with key "' + key + '"'); + } + switch (key) { + case 'scrollback': + if (value < this.rows) { + let msg = 'Setting the scrollback value less than the number of rows '; - // No change occurred, don't trigger scroll/refresh - if (oldYdisp === this.buffer.ydisp) { - return; + msg += `(${this.rows}) is not allowed.`; + + console.warn(msg); + return false; + } + + if (this.options[key] !== value) { + if (this.buffer.lines.length > value) { + const amountToTrim = this.buffer.lines.length - value; + const needsRefresh = (this.buffer.ydisp - amountToTrim < 0); + this.buffer.lines.trimStart(amountToTrim); + this.buffer.ybase = Math.max(this.buffer.ybase - amountToTrim, 0); + this.buffer.ydisp = Math.max(this.buffer.ydisp - amountToTrim, 0); + if (needsRefresh) { + this.refresh(0, this.rows - 1); + } + } + this.buffer.lines.maxLength = value; + this.viewport.syncScrollArea(); + } + break; + } + this[key] = value; + this.options[key] = value; + switch (key) { + case 'cursorBlink': this.setCursorBlinking(value); break; + case 'cursorStyle': + this.element.classList.toggle(`xterm-cursor-style-block`, value === 'block'); + this.element.classList.toggle(`xterm-cursor-style-underline`, value === 'underline'); + this.element.classList.toggle(`xterm-cursor-style-bar`, value === 'bar'); + break; + case 'tabStopWidth': this.setupStops(); break; + } } - if (!suppressScrollEvent) { + private restartCursorBlinking() { + this.setCursorBlinking(this.options.cursorBlink); + } + + private setCursorBlinking(enabled) { + this.element.classList.toggle('xterm-cursor-blink', enabled); + this.clearCursorBlinkingInterval(); + if (enabled) { + this.cursorBlinkInterval = setInterval(() => { + this.element.classList.toggle('xterm-cursor-blink-on'); + }, CURSOR_BLINK_INTERVAL); + } + } + + private clearCursorBlinkingInterval() { + this.element.classList.remove('xterm-cursor-blink-on'); + if (this.cursorBlinkInterval) { + clearInterval(this.cursorBlinkInterval); + this.cursorBlinkInterval = null; + } + } + + /** + * Binds the desired focus behavior on a given terminal object. + */ + private bindFocus() { + globalOn(this.textarea, 'focus', (ev) => { + if (this.sendFocus) { + this.send(C0.ESC + '[I'); + } + this.element.classList.add('focus'); + this.showCursor(); + this.restartCursorBlinking.apply(this); + // TODO: Why pass terminal here? + this.emit('focus', {terminal: this}); + }); + }; + + /** + * Blur the terminal. Delegates blur handling to the terminal's DOM element. + */ + private blur() { + return this.textarea.blur(); + } + + /** + * Binds the desired blur behavior on a given terminal object. + */ + private bindBlur() { + on(this.textarea, 'blur', (ev) => { + this.refresh(this.buffer.y, this.buffer.y); + if (this.sendFocus) { + this.send(C0.ESC + '[O'); + } + this.element.classList.remove('focus'); + this.clearCursorBlinkingInterval.apply(this); + // TODO: Why pass terminal here? + this.emit('blur', {terminal: this}); + }); + } + + /** + * Initialize default behavior + */ + private initGlobal() { + this.bindKeys(); + this.bindFocus(); + this.bindBlur(); + + // Bind clipboard functionality + on(this.element, 'copy', (event: ClipboardEvent) => { + // If mouse events are active it means the selection manager is disabled and + // copy should be handled by the host program. + if (!this.hasSelection()) { + return; + } + copyHandler(event, this, this.selectionManager); + }); + const pasteHandlerWrapper = event => pasteHandler(event, this); + on(this.textarea, 'paste', pasteHandlerWrapper); + on(this.element, 'paste', pasteHandlerWrapper); + + // Handle right click context menus + if (Browser.isFirefox) { + // 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); + } + }); + } else { + on(this.element, 'contextmenu', (event: MouseEvent) => { + rightClickHandler(event, this.textarea, this.selectionManager); + }); + } + + // Move the textarea under the cursor when middle clicking on Linux to ensure + // middle click to paste selection works. This only appears to work in Chrome + // at the time is writing. + if (Browser.isLinux) { + // Use auxclick event over mousedown the latter doesn't seem to work. Note + // that the regular click event doesn't fire for the middle mouse button. + on(this.element, 'auxclick', (event: MouseEvent) => { + if (event.button === 1) { + moveTextAreaUnderMouseCursor(event, this.textarea); + } + }); + } + } + + /** + * Apply key handling to the terminal + */ + private bindKeys() { + const self = this; + on(this.element, 'keydown', function (ev) { + if (document.activeElement !== this) { + return; + } + self.keyDown(ev); + }, true); + + on(this.element, 'keypress', function (ev) { + if (document.activeElement !== this) { + return; + } + self.keyPress(ev); + }, true); + + on(this.element, 'keyup', function(ev) { + if (!wasMondifierKeyOnlyEvent(ev)) { + this.focus(this); + } + }, true); + + on(this.textarea, 'keydown', function(ev) { + this.keyDown(ev); + }, true); + + on(this.textarea, 'keypress', function(ev) { + this.keyPress(ev); + // Truncate the textarea's value, since it is not needed + this.value = ''; + }, true); + + on(this.textarea, 'compositionstart', this.compositionHelper.compositionstart.bind(this.compositionHelper)); + on(this.textarea, 'compositionupdate', this.compositionHelper.compositionupdate.bind(this.compositionHelper)); + on(this.textarea, 'compositionend', this.compositionHelper.compositionend.bind(this.compositionHelper)); + this.on('refresh', this.compositionHelper.updateCompositionElements.bind(this.compositionHelper)); + this.on('refresh', (data) => this.queueLinkification(data.start, data.end)); + } + + /** + * Insert the given row to the terminal or produce a new one + * if no row argument is passed. Return the inserted row. + * @param {HTMLElement} row (optional) The row to append to the terminal. + */ + private insertRow(row?: HTMLElement) { + if (typeof row !== 'object') { + row = document.createElement('div'); + } + + this.rowContainer.appendChild(row); + this.children.push(row); + + return row; + }; + + /** + * Opens the terminal within an element. + * + * @param {HTMLElement} parent The element to create the terminal within. + * @param {boolean} focus Focus the terminal, after it gets instantiated in the DOM + */ + private open(parent, focus) { + let i = 0; + let div; + + this.parent = parent || this.parent; + + if (!this.parent) { + throw new Error('Terminal requires a parent element.'); + } + + // Grab global elements + this.context = this.parent.ownerDocument.defaultView; + this.document = this.parent.ownerDocument; + this.body = this.document.getElementsByTagName('body')[0]; + + // Create main element container + this.element = this.document.createElement('div'); + this.element.classList.add('terminal'); + this.element.classList.add('xterm'); + this.element.classList.add(`xterm-cursor-style-${this.options.cursorStyle}`); + this.setCursorBlinking(this.options.cursorBlink); + + this.element.setAttribute('tabindex', '0'); + + this.viewportElement = document.createElement('div'); + this.viewportElement.classList.add('xterm-viewport'); + this.element.appendChild(this.viewportElement); + this.viewportScrollArea = document.createElement('div'); + this.viewportScrollArea.classList.add('xterm-scroll-area'); + this.viewportElement.appendChild(this.viewportScrollArea); + + // Create the selection container. + this.selectionContainer = document.createElement('div'); + this.selectionContainer.classList.add('xterm-selection'); + this.element.appendChild(this.selectionContainer); + + // Create the container that will hold the lines of the terminal and then + // produce the lines the lines. + this.rowContainer = document.createElement('div'); + this.rowContainer.classList.add('xterm-rows'); + this.element.appendChild(this.rowContainer); + this.children = []; + this.linkifier.attachToDom(document, this.children); + + // 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'); + // TODO: This should probably be inserted once it's filled to prevent an additional layout + this.element.appendChild(this.helperContainer); + this.textarea = document.createElement('textarea'); + this.textarea.classList.add('xterm-helper-textarea'); + this.textarea.setAttribute('autocorrect', 'off'); + this.textarea.setAttribute('autocapitalize', 'off'); + this.textarea.setAttribute('spellcheck', 'false'); + this.textarea.tabIndex = 0; + this.textarea.addEventListener('focus', () => { + // TODO: Do we need terminal passed here? + this.emit('focus', {terminal: this}); + }); + this.textarea.addEventListener('blur', () => { + // TODO: Do we need terminal passed here? + this.emit('blur', {terminal: this}); + }); + this.helperContainer.appendChild(this.textarea); + + this.compositionView = document.createElement('div'); + this.compositionView.classList.add('composition-view'); + this.compositionHelper = new CompositionHelper(this.textarea, this.compositionView, this); + this.helperContainer.appendChild(this.compositionView); + + this.charSizeStyleElement = document.createElement('style'); + this.helperContainer.appendChild(this.charSizeStyleElement); + + for (; i < this.rows; i++) { + this.insertRow(); + } + this.parent.appendChild(this.element); + + this.charMeasure = new CharMeasure(document, this.helperContainer); + this.charMeasure.on('charsizechanged', () => { + this.updateCharSizeStyles(); + }); + this.charMeasure.measure(); + + this.viewport = new Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasure); + this.renderer = new Renderer(this); + this.selectionManager = new SelectionManager(this, this.buffer.lines, this.rowContainer, this.charMeasure); + this.selectionManager.on('refresh', data => { + this.renderer.refreshSelection(data.start, data.end); + }); + this.selectionManager.on('newselection', text => { + // If there's a new selection, put it into the textarea, focus and select it + // in order to register it as a selection on the OS. This event is fired + // only on Linux to enable middle click to paste selection. + this.textarea.value = text; + this.textarea.focus(); + this.textarea.select(); + }); + this.on('scroll', () => this.selectionManager.refresh()); + this.viewportElement.addEventListener('scroll', () => this.selectionManager.refresh()); + + // Setup loop that draws to screen + this.refresh(0, this.rows - 1); + + // Initialize global actions that + // need to be taken on the document. + this.initGlobal(); + + /** + * Automatic focus functionality. + * TODO: Default to `false` starting with xterm.js 3.0. + */ + if (typeof focus == 'undefined') { + let message = 'You did not pass the `focus` argument in `Terminal.prototype.open()`.\n'; + + message += 'The `focus` argument now defaults to `true` but starting with xterm.js 3.0 '; + message += 'it will default to `false`.'; + + console.warn(message); + focus = true; + } + + if (focus) { + this.focus(); + } + + // Listen for mouse events and translate + // them into terminal mouse protocols. + this.bindMouse(); + + /** + * This event is emitted when terminal has completed opening. + * + * @event open + */ + this.emit('open'); + } + + /** + * Attempts to load an add-on using CommonJS or RequireJS (whichever is available). + * @param {string} addon The name of the addon to load + * @static + */ + public loadAddon(addon, callback) { + if (typeof exports === 'object' && typeof module === 'object') { + // CommonJS + return require('./addons/' + addon + '/' + addon); + } else if (typeof define === 'function') { + // RequireJS + return (require)(['./addons/' + addon + '/' + addon], callback); + } else { + console.error('Cannot load a module without a CommonJS or RequireJS environment.'); + return false; + } + } + + /** + * Updates the helper CSS class with any changes necessary after the terminal's + * character width has been changed. + */ + public updateCharSizeStyles() { + this.charSizeStyleElement.textContent = + `.xterm-wide-char{width:${this.charMeasure.width * 2}px;}` + + `.xterm-normal-char{width:${this.charMeasure.width}px;}` + + `.xterm-rows > div{height:${this.charMeasure.height}px;}`; + } + + /** + * XTerm mouse events + * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking + * To better understand these + * the xterm code is very helpful: + * Relevant files: + * button.c, charproc.c, misc.c + * Relevant functions in xterm/button.c: + * BtnCode, EmitButtonCode, EditorButton, SendMousePosition + */ + public bindMouse() { + var el = this.element, self = this, pressed = 32; + + // mouseup, mousedown, wheel + // left click: ^[[M 3<^[[M#3< + // wheel up: ^[[M`3> + function sendButton(ev) { + var button + , pos; + + // get the xterm-style button + button = getButton(ev); + + // get mouse coordinates + pos = getRawByteCoords(ev, self.rowContainer, self.charMeasure, self.cols, self.rows); + if (!pos) return; + + sendEvent(button, pos); + + switch (ev.overrideType || ev.type) { + case 'mousedown': + pressed = button; + break; + case 'mouseup': + // keep it at the left + // button, just in case. + pressed = 32; + break; + case 'wheel': + // nothing. don't + // interfere with + // `pressed`. + break; + } + } + + // motion example of a left click: + // ^[[M 3<^[[M@4<^[[M@5<^[[M@6<^[[M@7<^[[M#7< + function sendMove(ev) { + let button = pressed + , pos; + + pos = getRawByteCoords(ev, self.rowContainer, self.charMeasure, self.cols, self.rows); + if (!pos) return; + + // buttons marked as motions + // are incremented by 32 + button += 32; + + sendEvent(button, pos); + } + + // encode button and + // position to characters + function encode(data, ch) { + if (!self.utfMouse) { + if (ch === 255) return data.push(0); + if (ch > 127) ch = 127; + data.push(ch); + } else { + if (ch === 2047) return data.push(0); + if (ch < 127) { + data.push(ch); + } else { + if (ch > 2047) ch = 2047; + data.push(0xC0 | (ch >> 6)); + data.push(0x80 | (ch & 0x3F)); + } + } + } + + // send a mouse event: + // regular/utf8: ^[[M Cb Cx Cy + // urxvt: ^[[ Cb ; Cx ; Cy M + // sgr: ^[[ Cb ; Cx ; Cy M/m + // vt300: ^[[ 24(1/3/5)~ [ Cx , Cy ] \r + // locator: CSI P e ; P b ; P r ; P c ; P p & w + function sendEvent(button, pos) { + // self.emit('mouse', { + // x: pos.x - 32, + // y: pos.x - 32, + // button: button + // }); + + if (self.vt300Mouse) { + // NOTE: Unstable. + // http://www.vt100.net/docs/vt3xx-gp/chapter15.html + button &= 3; + pos.x -= 32; + pos.y -= 32; + let data = C0.ESC + '[24'; + if (button === 0) data += '1'; + else if (button === 1) data += '3'; + else if (button === 2) data += '5'; + else if (button === 3) return; + else data += '0'; + data += '~[' + pos.x + ',' + pos.y + ']\r'; + self.send(data); + return; + } + + if (self.decLocator) { + // NOTE: Unstable. + button &= 3; + pos.x -= 32; + pos.y -= 32; + if (button === 0) button = 2; + else if (button === 1) button = 4; + else if (button === 2) button = 6; + else if (button === 3) button = 3; + self.send(C0.ESC + '[' + + button + + ';' + + (button === 3 ? 4 : 0) + + ';' + + pos.y + + ';' + + pos.x + + ';' + + (pos.page || 0) + + '&w'); + return; + } + + if (self.urxvtMouse) { + pos.x -= 32; + pos.y -= 32; + pos.x++; + pos.y++; + self.send(C0.ESC + '[' + button + ';' + pos.x + ';' + pos.y + 'M'); + return; + } + + if (self.sgrMouse) { + pos.x -= 32; + pos.y -= 32; + self.send(C0.ESC + '[<' + + (((button & 3) === 3 ? button & ~3 : button) - 32) + + ';' + + pos.x + + ';' + + pos.y + + ((button & 3) === 3 ? 'm' : 'M')); + return; + } + + let data = []; + + encode(data, button); + encode(data, pos.x); + encode(data, pos.y); + + self.send(C0.ESC + '[M' + String.fromCharCode.apply(String, data)); + } + + function getButton(ev) { + let button + , shift + , meta + , ctrl + , mod; + + // two low bits: + // 0 = left + // 1 = middle + // 2 = right + // 3 = release + // wheel up/down: + // 1, and 2 - with 64 added + switch (ev.overrideType || ev.type) { + case 'mousedown': + button = ev.button != null + ? +ev.button + : ev.which != null + ? ev.which - 1 + : null; + + if (Browser.isMSIE) { + button = button === 1 ? 0 : button === 4 ? 1 : button; + } + break; + case 'mouseup': + button = 3; + break; + case 'DOMMouseScroll': + button = ev.detail < 0 + ? 64 + : 65; + break; + case 'wheel': + button = ev.wheelDeltaY > 0 + ? 64 + : 65; + break; + } + + // next three bits are the modifiers: + // 4 = shift, 8 = meta, 16 = control + shift = ev.shiftKey ? 4 : 0; + meta = ev.metaKey ? 8 : 0; + ctrl = ev.ctrlKey ? 16 : 0; + mod = shift | meta | ctrl; + + // no mods + if (self.vt200Mouse) { + // ctrl only + mod &= ctrl; + } else if (!self.normalMouse) { + mod = 0; + } + + // increment to SP + button = (32 + (mod << 2)) + button; + + return button; + } + + on(el, 'mousedown', (ev) => { + + // Prevent the focus on the textarea from getting lost + // and make sure we get focused on mousedown + ev.preventDefault(); + this.focus(); + + if (!this.mouseEvents) return; + + // send the button + sendButton(ev); + + // fix for odd bug + //if (this.vt200Mouse && !this.normalMouse) { + if (this.vt200Mouse) { + (ev).overrideType = 'mouseup'; + sendButton(ev); + return this.cancel(ev); + } + + // bind events + if (this.normalMouse) on(this.document, 'mousemove', sendMove); + + // x10 compatibility mode can't send button releases + if (!this.x10Mouse) { + on(this.document, 'mouseup', (ev: MouseEvent) => { + sendButton(ev); + if (this.normalMouse) off(this.document, 'mousemove', sendMove); + off(this.document, 'mouseup', up); + return this.cancel(ev); + }); + } + + return this.cancel(ev); + }); + + //if (this.normalMouse) { + // on(this.document, 'mousemove', sendMove); + //} + + on(el, 'wheel', (ev) => { + if (!this.mouseEvents) return; + if (this.x10Mouse || this.vt300Mouse || this.decLocator) return; + sendButton(ev); + return this.cancel(ev); + }); + + // allow wheel scrolling in + // the shell for example + on(el, 'wheel', (ev: WheelEvent) => { + if (this.mouseEvents) return; + this.viewport.onWheel(ev); + return this.cancel(ev); + }); + + on(el, 'touchstart', (ev: TouchEvent) => { + if (this.mouseEvents) return; + this.viewport.onTouchStart(ev); + return this.cancel(ev); + }); + + on(el, 'touchmove', (ev: TouchEvent) => { + if (this.mouseEvents) return; + this.viewport.onTouchMove(ev); + return this.cancel(ev); + }); + } + + /** + * Destroys the terminal. + */ + public destroy(): void { + super.destroy(); + this.readable = false; + this.writable = false; + this.handler = function() {}; + this.write = function() {}; + if (this.element && this.element.parentNode) { + this.element.parentNode.removeChild(this.element); + } + // this.emit('close'); + } + + /** + * Tells the renderer to refresh terminal content between two rows (inclusive) at the next + * opportunity. + * @param {number} start The row to start from (between 0 and this.rows - 1). + * @param {number} end The row to end at (between start and this.rows - 1). + */ + public refresh(start: number, end: number): void { + if (this.renderer) { + this.renderer.queueRefresh(start, end); + } + } + + /** + * Queues linkification for the specified rows. + * @param {number} start The row to start from (between 0 and this.rows - 1). + * @param {number} end The row to end at (between start and this.rows - 1). + */ + private queueLinkification(start: number, end: number): void { + if (this.linkifier) { + for (let i = start; i <= end; i++) { + this.linkifier.linkifyRow(i); + } + } + } + + /** + * Display the cursor element + */ + private showCursor() { + if (!this.cursorState) { + this.cursorState = 1; + this.refresh(this.buffer.y, this.buffer.y); + } + } + + /** + * Scroll the terminal down 1 row, creating a blank line. + * @param {boolean} isWrapped Whether the new line is wrapped from the previous + * line. + */ + public scroll(isWrapped?: boolean): void { + var row; + + // Make room for the new row in lines + if (this.buffer.lines.length === this.buffer.lines.maxLength) { + this.buffer.lines.trimStart(1); + this.buffer.ybase--; + if (this.buffer.ydisp !== 0) { + this.buffer.ydisp--; + } + } + + this.buffer.ybase++; + + // TODO: Why is this done twice? + if (!this.userScrolling) { + this.buffer.ydisp = this.buffer.ybase; + } + + // last line + row = this.buffer.ybase + this.rows - 1; + + // subtract the bottom scroll region + row -= this.rows - 1 - this.buffer.scrollBottom; + + if (row === this.buffer.lines.length) { + // Optimization: pushing is faster than splicing when they amount to the same behavior + this.buffer.lines.push(this.blankLine(undefined, isWrapped)); + } else { + // add our new line + this.buffer.lines.splice(row, 0, this.blankLine(undefined, isWrapped)); + } + + if (this.buffer.scrollTop !== 0) { + if (this.buffer.ybase !== 0) { + this.buffer.ybase--; + if (!this.userScrolling) { + this.buffer.ydisp = this.buffer.ybase; + } + } + this.buffer.lines.splice(this.buffer.ybase + this.buffer.scrollTop, 1); + } + + // this.maxRange(); + this.updateRange(this.buffer.scrollTop); + this.updateRange(this.buffer.scrollBottom); + + /** + * This event is emitted whenever the terminal is scrolled. + * The one parameter passed is the new y display position. + * + * @event scroll + */ this.emit('scroll', this.buffer.ydisp); } - this.refresh(0, this.rows - 1); -}; - -/** - * Scroll the display of the terminal by a number of pages. - * @param {number} pageCount The number of pages to scroll (negative scrolls up). - */ -Terminal.prototype.scrollPages = function(pageCount) { - this.scrollDisp(pageCount * (this.rows - 1)); -}; - -/** - * Scrolls the display of the terminal to the top. - */ -Terminal.prototype.scrollToTop = function() { - this.scrollDisp(-this.buffer.ydisp); -}; - -/** - * Scrolls the display of the terminal to the bottom. - */ -Terminal.prototype.scrollToBottom = function() { - this.scrollDisp(this.buffer.ybase - this.buffer.ydisp); -}; - -/** - * Writes text to the terminal. - * @param {string} data The text to write to the terminal. - */ -Terminal.prototype.write = function(data) { - this.writeBuffer.push(data); - - // Send XOFF to pause the pty process if the write buffer becomes too large so - // xterm.js can catch up before more data is sent. This is necessary in order - // to keep signals such as ^C responsive. - if (this.options.useFlowControl && !this.xoffSentToCatchUp && this.writeBuffer.length >= WRITE_BUFFER_PAUSE_THRESHOLD) { - // XOFF - stop pty pipe - // XON will be triggered by emulator before processing data chunk - this.send(C0.DC3); - this.xoffSentToCatchUp = true; - } - - if (!this.writeInProgress && this.writeBuffer.length > 0) { - // Kick off a write which will write all data in sequence recursively - this.writeInProgress = true; - // Kick off an async innerWrite so more writes can come in while processing data - var self = this; - setTimeout(function () { - self.innerWrite(); - }); - } -}; - -Terminal.prototype.innerWrite = function() { - var writeBatch = this.writeBuffer.splice(0, WRITE_BATCH_SIZE); - while (writeBatch.length > 0) { - var data = writeBatch.shift(); - var l = data.length, i = 0, j, cs, ch, code, low, ch_width, row; - - // If XOFF was sent in order to catch up with the pty process, resume it if - // the writeBuffer is empty to allow more data to come in. - if (this.xoffSentToCatchUp && writeBatch.length === 0 && this.writeBuffer.length === 0) { - this.send(C0.DC1); - this.xoffSentToCatchUp = false; + /** + * Scroll the display of the terminal + * @param {number} disp The number of lines to scroll down (negatives scroll up). + * @param {boolean} suppressScrollEvent Don't emit the scroll event as scrollDisp. This is used + * to avoid unwanted events being handled by the veiwport when the event was triggered from the + * viewport originally. + */ + public scrollDisp(disp: number, suppressScrollEvent?: boolean): void { + if (disp < 0) { + if (this.buffer.ydisp === 0) { + return; + } + this.userScrolling = true; + } else if (disp + this.buffer.ydisp >= this.buffer.ybase) { + this.userScrolling = false; } - this.refreshStart = this.buffer.y; - this.refreshEnd = this.buffer.y; + const oldYdisp = this.buffer.ydisp; + this.buffer.ydisp = Math.max(Math.min(this.buffer.ydisp + disp, this.buffer.ybase), 0); - // HACK: Set the parser state based on it's state at the time of return. - // This works around the bug #662 which saw the parser state reset in the - // middle of parsing escape sequence in two chunks. For some reason the - // state of the parser resets to 0 after exiting parser.parse. This change - // just sets the state back based on the correct return statement. - var state = this.parser.parse(data); - this.parser.setState(state); + // No change occurred, don't trigger scroll/refresh + if (oldYdisp === this.buffer.ydisp) { + return; + } - this.updateRange(this.buffer.y); - this.refresh(this.refreshStart, this.refreshEnd); - } - if (this.writeBuffer.length > 0) { - // Allow renderer to catch up before processing the next batch - var self = this; - setTimeout(function () { - self.innerWrite(); - }, 0); - } else { - this.writeInProgress = false; - } -}; + if (!suppressScrollEvent) { + this.emit('scroll', this.buffer.ydisp); + } -/** - * Writes text to the terminal, followed by a break line character (\n). - * @param {string} data The text to write to the terminal. - */ -Terminal.prototype.writeln = function(data) { - this.write(data + '\r\n'); -}; - -/** - * DEPRECATED: only for backward compatibility. Please use attachCustomKeyEventHandler() instead. - * @param {function} customKeydownHandler The custom KeyboardEvent handler to attach. This is a - * function that takes a KeyboardEvent, allowing consumers to stop propogation and/or prevent - * the default action. The function returns whether the event should be processed by xterm.js. - */ -Terminal.prototype.attachCustomKeydownHandler = function(customKeydownHandler) { - let message = 'attachCustomKeydownHandler() is DEPRECATED and will be removed soon. Please use attachCustomKeyEventHandler() instead.'; - console.warn(message); - this.attachCustomKeyEventHandler(customKeydownHandler); -}; - -/** - * Attaches a custom key event handler which is run before keys are processed, giving consumers of - * xterm.js ultimate control as to what keys should be processed by the terminal and what keys - * should not. - * @param {function} customKeyEventHandler The custom KeyboardEvent handler to attach. This is a - * function that takes a KeyboardEvent, allowing consumers to stop propogation and/or prevent - * the default action. The function returns whether the event should be processed by xterm.js. - */ -Terminal.prototype.attachCustomKeyEventHandler = function(customKeyEventHandler) { - this.customKeyEventHandler = customKeyEventHandler; -}; - -/** - * Attaches a http(s) link handler, forcing web links to behave differently to - * regular tags. This will trigger a refresh as links potentially need to be - * reconstructed. Calling this with null will remove the handler. - * @param {LinkMatcherHandler} handler The handler callback function. - */ -Terminal.prototype.setHypertextLinkHandler = function(handler) { - if (!this.linkifier) { - throw new Error('Cannot attach a hypertext link handler before Terminal.open is called'); - } - this.linkifier.setHypertextLinkHandler(handler); - // Refresh to force links to refresh - this.refresh(0, this.rows - 1); -}; - -/** - * Attaches a validation callback for hypertext links. This is useful to use - * validation logic or to do something with the link's element and url. - * @param {LinkMatcherValidationCallback} callback The callback to use, this can - * be cleared with null. - */ -Terminal.prototype.setHypertextValidationCallback = function(callback) { - if (!this.linkifier) { - throw new Error('Cannot attach a hypertext validation callback before Terminal.open is called'); - } - this.linkifier.setHypertextValidationCallback(callback); - // Refresh to force links to refresh - this.refresh(0, this.rows - 1); -}; - -/** - * Registers a link matcher, allowing custom link patterns to be matched and - * handled. - * @param {RegExp} regex The regular expression to search for, specifically - * this searches the textContent of the rows. You will want to use \s to match - * a space ' ' character for example. - * @param {LinkMatcherHandler} handler The callback when the link is called. - * @param {LinkMatcherOptions} [options] Options for the link matcher. - * @return {number} The ID of the new matcher, this can be used to deregister. - */ -Terminal.prototype.registerLinkMatcher = function(regex, handler, options) { - if (this.linkifier) { - var matcherId = this.linkifier.registerLinkMatcher(regex, handler, options); this.refresh(0, this.rows - 1); - return matcherId; } -}; -/** - * Deregisters a link matcher if it has been registered. - * @param {number} matcherId The link matcher's ID (returned after register) - */ -Terminal.prototype.deregisterLinkMatcher = function(matcherId) { - if (this.linkifier) { - if (this.linkifier.deregisterLinkMatcher(matcherId)) { + /** + * 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.scrollDisp(pageCount * (this.rows - 1)); + } + + /** + * Scrolls the display of the terminal to the top. + */ + public scrollToTop(): void { + this.scrollDisp(-this.buffer.ydisp); + } + + /** + * Scrolls the display of the terminal to the bottom. + */ + public scrollToBottom(): void { + this.scrollDisp(this.buffer.ybase - this.buffer.ydisp); + } + + /** + * Writes text to the terminal. + * @param {string} data The text to write to the terminal. + */ + public write(data: string): void { + this.writeBuffer.push(data); + + // Send XOFF to pause the pty process if the write buffer becomes too large so + // xterm.js can catch up before more data is sent. This is necessary in order + // to keep signals such as ^C responsive. + if (this.options.useFlowControl && !this.xoffSentToCatchUp && this.writeBuffer.length >= WRITE_BUFFER_PAUSE_THRESHOLD) { + // XOFF - stop pty pipe + // XON will be triggered by emulator before processing data chunk + this.send(C0.DC3); + this.xoffSentToCatchUp = true; + } + + if (!this.writeInProgress && this.writeBuffer.length > 0) { + // Kick off a write which will write all data in sequence recursively + this.writeInProgress = true; + // Kick off an async innerWrite so more writes can come in while processing data + setTimeout(() => { + this.innerWrite(); + }); + } + } + + private innerWrite() { + var writeBatch = this.writeBuffer.splice(0, WRITE_BATCH_SIZE); + while (writeBatch.length > 0) { + var data = writeBatch.shift(); + var l = data.length, i = 0, j, cs, ch, code, low, ch_width, row; + + // If XOFF was sent in order to catch up with the pty process, resume it if + // the writeBuffer is empty to allow more data to come in. + if (this.xoffSentToCatchUp && writeBatch.length === 0 && this.writeBuffer.length === 0) { + this.send(C0.DC1); + this.xoffSentToCatchUp = false; + } + + this.refreshStart = this.buffer.y; + this.refreshEnd = this.buffer.y; + + // HACK: Set the parser state based on it's state at the time of return. + // This works around the bug #662 which saw the parser state reset in the + // middle of parsing escape sequence in two chunks. For some reason the + // state of the parser resets to 0 after exiting parser.parse. This change + // just sets the state back based on the correct return statement. + var state = this.parser.parse(data); + this.parser.setState(state); + + this.updateRange(this.buffer.y); + this.refresh(this.refreshStart, this.refreshEnd); + } + if (this.writeBuffer.length > 0) { + // Allow renderer to catch up before processing the next batch + var self = this; + setTimeout(function () { + self.innerWrite(); + }, 0); + } else { + this.writeInProgress = false; + } + } + + /** + * Writes text to the terminal, followed by a break line character (\n). + * @param {string} data The text to write to the terminal. + */ + public writeln(data): void { + this.write(data + '\r\n'); + } + + /** + * DEPRECATED: only for backward compatibility. Please use attachCustomKeyEventHandler() instead. + * @param {function} customKeydownHandler The custom KeyboardEvent handler to attach. This is a + * function that takes a KeyboardEvent, allowing consumers to stop propogation and/or prevent + * the default action. The function returns whether the event should be processed by xterm.js. + */ + public attachCustomKeydownHandler(customKeydownHandler: CustomKeyEventHandler): void { + const message = 'attachCustomKeydownHandler() is DEPRECATED and will be removed soon. Please use attachCustomKeyEventHandler() instead.'; + console.warn(message); + this.attachCustomKeyEventHandler(customKeydownHandler); + } + + /** + * Attaches a custom key event handler which is run before keys are processed, giving consumers of + * xterm.js ultimate control as to what keys should be processed by the terminal and what keys + * should not. + * @param {function} customKeyEventHandler The custom KeyboardEvent handler to attach. This is a + * function that takes a KeyboardEvent, allowing consumers to stop propogation and/or prevent + * the default action. The function returns whether the event should be processed by xterm.js. + */ + public attachCustomKeyEventHandler(customKeyEventHandler: CustomKeyEventHandler): void { + this.customKeyEventHandler = customKeyEventHandler; + } + + /** + * Attaches a http(s) link handler, forcing web links to behave differently to + * regular tags. This will trigger a refresh as links potentially need to be + * reconstructed. Calling this with null will remove the handler. + * @param {LinkMatcherHandler} handler The handler callback function. + */ + public setHypertextLinkHandler(handler) { + if (!this.linkifier) { + throw new Error('Cannot attach a hypertext link handler before Terminal.open is called'); + } + this.linkifier.setHypertextLinkHandler(handler); + // Refresh to force links to refresh + this.refresh(0, this.rows - 1); + } + + /** + * Attaches a validation callback for hypertext links. This is useful to use + * validation logic or to do something with the link's element and url. + * @param {LinkMatcherValidationCallback} callback The callback to use, this can + * be cleared with null. + */ + public setHypertextValidationCallback(callback) { + if (!this.linkifier) { + throw new Error('Cannot attach a hypertext validation callback before Terminal.open is called'); + } + this.linkifier.setHypertextValidationCallback(callback); + // Refresh to force links to refresh + this.refresh(0, this.rows - 1); + } + + /** + * Registers a link matcher, allowing custom link patterns to be matched and + * handled. + * @param {RegExp} regex The regular expression to search for, specifically + * this searches the textContent of the rows. You will want to use \s to match + * a space ' ' character for example. + * @param {LinkMatcherHandler} handler The callback when the link is called. + * @param {LinkMatcherOptions} [options] Options for the link matcher. + * @return {number} The ID of the new matcher, this can be used to deregister. + */ + public registerLinkMatcher(regex, handler, options) { + if (this.linkifier) { + var matcherId = this.linkifier.registerLinkMatcher(regex, handler, options); this.refresh(0, this.rows - 1); + return matcherId; } } -}; -/** - * Gets whether the terminal has an active selection. - */ -Terminal.prototype.hasSelection = function() { - return this.selectionManager ? this.selectionManager.hasSelection : false; -}; - -/** - * Gets the terminal's current selection, this is useful for implementing copy - * behavior outside of xterm.js. - */ -Terminal.prototype.getSelection = function() { - return this.selectionManager ? this.selectionManager.selectionText : ''; -}; - -/** - * Clears the current terminal selection. - */ -Terminal.prototype.clearSelection = function() { - if (this.selectionManager) { - this.selectionManager.clearSelection(); - } -}; - -/** - * Selects all text within the terminal. - */ -Terminal.prototype.selectAll = function() { - if (this.selectionManager) { - this.selectionManager.selectAll(); - } -}; - -/** - * Handle a keydown event - * Key Resources: - * - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent - * @param {KeyboardEvent} ev The keydown event to be handled. - */ -Terminal.prototype.keyDown = function(ev) { - if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) { - return false; - } - - this.restartCursorBlinking(); - - if (!this.compositionHelper.keydown.bind(this.compositionHelper)(ev)) { - if (this.buffer.ybase !== this.buffer.ydisp) { - this.scrollToBottom(); + /** + * Deregisters a link matcher if it has been registered. + * @param {number} matcherId The link matcher's ID (returned after register) + */ + public deregisterLinkMatcher(matcherId) { + if (this.linkifier) { + if (this.linkifier.deregisterLinkMatcher(matcherId)) { + this.refresh(0, this.rows - 1); + } } - return false; } - var self = this; - var result = this.evaluateKeyEscapeSequence(ev); - - if (result.key === C0.DC3) { // XOFF - this.writeStopped = true; - } else if (result.key === C0.DC1) { // XON - this.writeStopped = false; + /** + * Gets whether the terminal has an active selection. + */ + public hasSelection() { + return this.selectionManager ? this.selectionManager.hasSelection : false; } - if (result.scrollDisp) { - this.scrollDisp(result.scrollDisp); + /** + * Gets the terminal's current selection, this is useful for implementing copy + * behavior outside of xterm.js. + */ + public getSelection() { + return this.selectionManager ? this.selectionManager.selectionText : ''; + } + + /** + * Clears the current terminal selection. + */ + public clearSelection() { + if (this.selectionManager) { + this.selectionManager.clearSelection(); + } + } + + /** + * Selects all text within the terminal. + */ + public selectAll() { + if (this.selectionManager) { + this.selectionManager.selectAll(); + } + } + + /** + * Handle a keydown event + * Key Resources: + * - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent + * @param {KeyboardEvent} ev The keydown event to be handled. + */ + private keyDown(ev) { + if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) { + return false; + } + + this.restartCursorBlinking(); + + if (!this.compositionHelper.keydown.bind(this.compositionHelper)(ev)) { + if (this.buffer.ybase !== this.buffer.ydisp) { + this.scrollToBottom(); + } + return false; + } + + const result = this.evaluateKeyEscapeSequence(ev); + + if (result.key === C0.DC3) { // XOFF + this.writeStopped = true; + } else if (result.key === C0.DC1) { // XON + this.writeStopped = false; + } + + if (result.scrollDisp) { + this.scrollDisp(result.scrollDisp); + return this.cancel(ev, true); + } + + if (isThirdLevelShift(this, ev)) { + return true; + } + + if (result.cancel) { + // The event is canceled at the end already, is this necessary? + this.cancel(ev, true); + } + + if (!result.key) { + return true; + } + + this.emit('keydown', ev); + this.emit('key', result.key, ev); + this.showCursor(); + this.handler(result.key); + return this.cancel(ev, true); } - if (isThirdLevelShift(this, ev)) { - return true; - } - - if (result.cancel) { - // The event is canceled at the end already, is this necessary? - this.cancel(ev, true); - } - - if (!result.key) { - return true; - } - - this.emit('keydown', ev); - this.emit('key', result.key, ev); - this.showCursor(); - this.handler(result.key); - - return this.cancel(ev, true); -}; - -/** - * Returns an object that determines how a KeyboardEvent should be handled. The key of the - * returned value is the new key code to pass to the PTY. - * - * Reference: http://invisible-island.net/xterm/ctlseqs/ctlseqs.html - * @param {KeyboardEvent} ev The keyboard event to be translated to key escape sequence. - */ -Terminal.prototype.evaluateKeyEscapeSequence = function(ev) { - var result = { - // Whether to cancel event propogation (NOTE: this may not be needed since the event is - // canceled at the end of keyDown - cancel: false, - // The new key even to emit - key: undefined, - // The number of characters to scroll, if this is defined it will cancel the event - scrollDisp: undefined - }; - var modifiers = ev.shiftKey << 0 | ev.altKey << 1 | ev.ctrlKey << 2 | ev.metaKey << 3; - switch (ev.keyCode) { - case 8: - // backspace - if (ev.shiftKey) { - result.key = C0.BS; // ^H - break; - } - result.key = C0.DEL; // ^? - break; - case 9: - // tab - if (ev.shiftKey) { - result.key = C0.ESC + '[Z'; - break; - } - result.key = C0.HT; - result.cancel = true; - break; - case 13: - // return/enter - result.key = C0.CR; - result.cancel = true; - break; - case 27: - // escape - result.key = C0.ESC; - result.cancel = true; - break; - case 37: - // left-arrow - if (modifiers) { - result.key = C0.ESC + '[1;' + (modifiers + 1) + 'D'; - // HACK: Make Alt + left-arrow behave like Ctrl + left-arrow: move one word backwards - // http://unix.stackexchange.com/a/108106 - // macOS uses different escape sequences than linux - if (result.key == C0.ESC + '[1;3D') { - result.key = (this.browser.isMac) ? C0.ESC + 'b' : C0.ESC + '[1;5D'; - } - } else if (this.applicationCursor) { - result.key = C0.ESC + 'OD'; - } else { - result.key = C0.ESC + '[D'; - } - break; - case 39: - // right-arrow - if (modifiers) { - result.key = C0.ESC + '[1;' + (modifiers + 1) + 'C'; - // HACK: Make Alt + right-arrow behave like Ctrl + right-arrow: move one word forward - // http://unix.stackexchange.com/a/108106 - // macOS uses different escape sequences than linux - if (result.key == C0.ESC + '[1;3C') { - result.key = (this.browser.isMac) ? C0.ESC + 'f' : C0.ESC + '[1;5C'; - } - } else if (this.applicationCursor) { - result.key = C0.ESC + 'OC'; - } else { - result.key = C0.ESC + '[C'; - } - break; - case 38: - // up-arrow - if (modifiers) { - result.key = C0.ESC + '[1;' + (modifiers + 1) + 'A'; - // HACK: Make Alt + up-arrow behave like Ctrl + up-arrow - // http://unix.stackexchange.com/a/108106 - if (result.key == C0.ESC + '[1;3A') { - result.key = C0.ESC + '[1;5A'; - } - } else if (this.applicationCursor) { - result.key = C0.ESC + 'OA'; - } else { - result.key = C0.ESC + '[A'; - } - break; - case 40: - // down-arrow - if (modifiers) { - result.key = C0.ESC + '[1;' + (modifiers + 1) + 'B'; - // HACK: Make Alt + down-arrow behave like Ctrl + down-arrow - // http://unix.stackexchange.com/a/108106 - if (result.key == C0.ESC + '[1;3B') { - result.key = C0.ESC + '[1;5B'; - } - } else if (this.applicationCursor) { - result.key = C0.ESC + 'OB'; - } else { - result.key = C0.ESC + '[B'; - } - break; - case 45: - // insert - if (!ev.shiftKey && !ev.ctrlKey) { - // or + are used to - // copy-paste on some systems. - result.key = C0.ESC + '[2~'; - } - break; - case 46: - // delete - if (modifiers) { - result.key = C0.ESC + '[3;' + (modifiers + 1) + '~'; - } else { - result.key = C0.ESC + '[3~'; - } - break; - case 36: - // home - if (modifiers) - result.key = C0.ESC + '[1;' + (modifiers + 1) + 'H'; - else if (this.applicationCursor) - result.key = C0.ESC + 'OH'; - else - result.key = C0.ESC + '[H'; - break; - case 35: - // end - if (modifiers) - result.key = C0.ESC + '[1;' + (modifiers + 1) + 'F'; - else if (this.applicationCursor) - result.key = C0.ESC + 'OF'; - else - result.key = C0.ESC + '[F'; - break; - case 33: - // page up - if (ev.shiftKey) { - result.scrollDisp = -(this.rows - 1); - } else { - result.key = C0.ESC + '[5~'; - } - break; - case 34: - // page down - if (ev.shiftKey) { - result.scrollDisp = this.rows - 1; - } else { - result.key = C0.ESC + '[6~'; - } - break; - case 112: - // F1-F12 - if (modifiers) { - result.key = C0.ESC + '[1;' + (modifiers + 1) + 'P'; - } else { - result.key = C0.ESC + 'OP'; - } - break; - case 113: - if (modifiers) { - result.key = C0.ESC + '[1;' + (modifiers + 1) + 'Q'; - } else { - result.key = C0.ESC + 'OQ'; - } - break; - case 114: - if (modifiers) { - result.key = C0.ESC + '[1;' + (modifiers + 1) + 'R'; - } else { - result.key = C0.ESC + 'OR'; - } - break; - case 115: - if (modifiers) { - result.key = C0.ESC + '[1;' + (modifiers + 1) + 'S'; - } else { - result.key = C0.ESC + 'OS'; - } - break; - case 116: - if (modifiers) { - result.key = C0.ESC + '[15;' + (modifiers + 1) + '~'; - } else { - result.key = C0.ESC + '[15~'; - } - break; - case 117: - if (modifiers) { - result.key = C0.ESC + '[17;' + (modifiers + 1) + '~'; - } else { - result.key = C0.ESC + '[17~'; - } - break; - case 118: - if (modifiers) { - result.key = C0.ESC + '[18;' + (modifiers + 1) + '~'; - } else { - result.key = C0.ESC + '[18~'; - } - break; - case 119: - if (modifiers) { - result.key = C0.ESC + '[19;' + (modifiers + 1) + '~'; - } else { - result.key = C0.ESC + '[19~'; - } - break; - case 120: - if (modifiers) { - result.key = C0.ESC + '[20;' + (modifiers + 1) + '~'; - } else { - result.key = C0.ESC + '[20~'; - } - break; - case 121: - if (modifiers) { - result.key = C0.ESC + '[21;' + (modifiers + 1) + '~'; - } else { - result.key = C0.ESC + '[21~'; - } - break; - case 122: - if (modifiers) { - result.key = C0.ESC + '[23;' + (modifiers + 1) + '~'; - } else { - result.key = C0.ESC + '[23~'; - } - break; - case 123: - if (modifiers) { - result.key = C0.ESC + '[24;' + (modifiers + 1) + '~'; - } else { - result.key = C0.ESC + '[24~'; - } - break; - default: - // a-z and space - if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) { - if (ev.keyCode >= 65 && ev.keyCode <= 90) { - result.key = String.fromCharCode(ev.keyCode - 64); - } else if (ev.keyCode === 32) { - // NUL - result.key = String.fromCharCode(0); - } else if (ev.keyCode >= 51 && ev.keyCode <= 55) { - // escape, file sep, group sep, record sep, unit sep - result.key = String.fromCharCode(ev.keyCode - 51 + 27); - } else if (ev.keyCode === 56) { - // delete - result.key = String.fromCharCode(127); - } else if (ev.keyCode === 219) { - // ^[ - Control Sequence Introducer (CSI) - result.key = String.fromCharCode(27); - } else if (ev.keyCode === 220) { - // ^\ - String Terminator (ST) - result.key = String.fromCharCode(28); - } else if (ev.keyCode === 221) { - // ^] - Operating System Command (OSC) - result.key = String.fromCharCode(29); - } - } else if (!this.browser.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) { - // On Mac this is a third level shift. 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 + '`'; - } else if (ev.keyCode >= 48 && ev.keyCode <= 57) { - result.key = C0.ESC + (ev.keyCode - 48); - } - } else if (this.browser.isMac && !ev.altKey && !ev.ctrlKey && ev.metaKey) { - if (ev.keyCode === 65) { // cmd + a - this.selectAll(); - } - } - break; - } - - return result; -}; - -/** - * Set the G level of the terminal - * @param g - */ -Terminal.prototype.setgLevel = function(g) { - this.glevel = g; - this.charset = this.charsets[g]; -}; - -/** - * Set the charset for the given G level of the terminal - * @param g - * @param charset - */ -Terminal.prototype.setgCharset = function(g, charset) { - this.charsets[g] = charset; - if (this.glevel === g) { - this.charset = charset; - } -}; - -/** - * Handle a keypress event. - * Key Resources: - * - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent - * @param {KeyboardEvent} ev The keypress event to be handled. - */ -Terminal.prototype.keyPress = function(ev) { - var key; - - if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) { - return false; - } - - this.cancel(ev); - - if (ev.charCode) { - key = ev.charCode; - } else if (ev.which == null) { - key = ev.keyCode; - } else if (ev.which !== 0 && ev.charCode !== 0) { - key = ev.which; - } else { - return false; - } - - if (!key || ( - (ev.altKey || ev.ctrlKey || ev.metaKey) && !isThirdLevelShift(this, ev) - )) { - return false; - } - - key = String.fromCharCode(key); - - this.emit('keypress', key, ev); - this.emit('key', key, ev); - this.showCursor(); - this.handler(key); - - return true; -}; - -/** - * Send data for handling to the terminal - * @param {string} data - */ -Terminal.prototype.send = function(data) { - var self = this; - - if (!this.queue) { - setTimeout(function() { - self.handler(self.queue); - self.queue = ''; - }, 1); - } - - this.queue += data; -}; - -/** - * Ring the bell. - * Note: We could do sweet things with webaudio here - */ -Terminal.prototype.bell = function() { - if (!this.visualBell) return; - var self = this; - this.element.style.borderColor = 'white'; - setTimeout(function() { - self.element.style.borderColor = ''; - }, 10); - if (this.popOnBell) this.focus(); -}; - -/** - * Log the current state to the console. - */ -Terminal.prototype.log = function() { - if (!this.debug) return; - if (!this.context.console || !this.context.console.log) return; - var args = Array.prototype.slice.call(arguments); - this.context.console.log.apply(this.context.console, args); -}; - -/** - * Log the current state as error to the console. - */ -Terminal.prototype.error = function() { - if (!this.debug) return; - if (!this.context.console || !this.context.console.error) return; - var args = Array.prototype.slice.call(arguments); - this.context.console.error.apply(this.context.console, args); -}; - -/** - * Resizes the terminal. - * - * @param {number} x The number of columns to resize to. - * @param {number} y The number of rows to resize to. - */ -Terminal.prototype.resize = function(x, y) { - if (isNaN(x) || isNaN(y)) { - return; - } - - if (y > this.getOption('scrollback')) { - this.setOption('scrollback', y) - } - - var line - , el - , i - , j - , ch - , addToY; - - if (x === this.cols && y === this.rows) { - // Check if we still need to measure the char size (fixes #785). - if (!this.charMeasure.width || !this.charMeasure.height) { - this.charMeasure.measure(); - } - return; - } - - if (x < 1) x = 1; - if (y < 1) y = 1; - - // resize cols - j = this.cols; - if (j < x) { - ch = [this.defAttr, ' ', 1]; // does xterm use the default attr? - i = this.buffer.lines.length; - while (i--) { - if (this.buffer.lines.get(i) === undefined) { - this.buffer.lines.set(i, this.blankLine()); - } - while (this.buffer.lines.get(i).length < x) { - this.buffer.lines.get(i).push(ch); - } - } - } - - this.cols = x; - this.setupStops(this.cols); - - // resize rows - j = this.rows; - addToY = 0; - if (j < y) { - el = this.element; - while (j++ < y) { - // y is rows, not this.buffer.y - if (this.buffer.lines.length < y + this.buffer.ybase) { - if (this.buffer.ybase > 0 && this.buffer.lines.length <= this.buffer.ybase + this.buffer.y + addToY + 1) { - // There is room above the buffer and there are no empty elements below the line, - // scroll up - this.buffer.ybase--; - addToY++; - if (this.buffer.ydisp > 0) { - // Viewport is at the top of the buffer, must increase downwards - this.buffer.ydisp--; - } - } else { - // Add a blank line if there is no buffer left at the top to scroll to, or if there - // are blank lines after the cursor - this.buffer.lines.push(this.blankLine()); - } - } - if (this.children.length < y) { - this.insertRow(); - } - } - } else { // (j > y) - while (j-- > y) { - if (this.buffer.lines.length > y + this.buffer.ybase) { - if (this.buffer.lines.length > this.buffer.ybase + this.buffer.y + 1) { - // The line is a blank line below the cursor, remove it - this.buffer.lines.pop(); - } else { - // The line is the cursor, scroll down - this.buffer.ybase++; - this.buffer.ydisp++; - } - } - if (this.children.length > y) { - el = this.children.shift(); - if (!el) continue; - el.parentNode.removeChild(el); - } - } - } - this.rows = y; - - // Make sure that the cursor stays on screen - if (this.buffer.y >= y) { - this.buffer.y = y - 1; - } - if (addToY) { - this.buffer.y += addToY; - } - - if (this.buffer.x >= x) { - this.buffer.x = x - 1; - } - - this.buffer.scrollTop = 0; - this.buffer.scrollBottom = y - 1; - - this.charMeasure.measure(); - - this.refresh(0, this.rows - 1); - - this.geometry = [this.cols, this.rows]; - this.emit('resize', {terminal: this, cols: x, rows: y}); -}; - -/** - * Updates the range of rows to refresh - * @param {number} y The number of rows to refresh next. - */ -Terminal.prototype.updateRange = function(y) { - if (y < this.refreshStart) this.refreshStart = y; - if (y > this.refreshEnd) this.refreshEnd = y; - // if (y > this.refreshEnd) { - // this.refreshEnd = y; - // if (y > this.rows - 1) { - // this.refreshEnd = this.rows - 1; - // } - // } -}; - -/** - * Set the range of refreshing to the maximum value - */ -Terminal.prototype.maxRange = function() { - this.refreshStart = 0; - this.refreshEnd = this.rows - 1; -}; - - - -/** - * Setup the tab stops. - * @param {number} i - */ -Terminal.prototype.setupStops = function(i) { - if (i != null) { - if (!this.buffer.tabs[i]) { - i = this.prevStop(i); - } - } else { - this.buffer.tabs = {}; - i = 0; - } - - for (; i < this.cols; i += this.getOption('tabStopWidth')) { - this.buffer.tabs[i] = true; - } -}; - - -/** - * Move the cursor to the previous tab stop from the given position (default is current). - * @param {number} x The position to move the cursor to the previous tab stop. - */ -Terminal.prototype.prevStop = function(x) { - if (x == null) x = this.buffer.x; - while (!this.buffer.tabs[--x] && x > 0); - return x >= this.cols - ? this.cols - 1 - : x < 0 ? 0 : x; -}; - - -/** - * Move the cursor one tab stop forward from the given position (default is current). - * @param {number} x The position to move the cursor one tab stop forward. - */ -Terminal.prototype.nextStop = function(x) { - if (x == null) x = this.buffer.x; - while (!this.buffer.tabs[++x] && x < this.cols); - return x >= this.cols - ? this.cols - 1 - : x < 0 ? 0 : x; -}; - - -/** - * Erase in the identified line everything from "x" to the end of the line (right). - * @param {number} x The column from which to start erasing to the end of the line. - * @param {number} y The line in which to operate. - */ -Terminal.prototype.eraseRight = function(x, y) { - var line = this.buffer.lines.get(this.buffer.ybase + y); - if (!line) { - return; - } - var ch = [this.eraseAttr(), ' ', 1]; // xterm - for (; x < this.cols; x++) { - line[x] = ch; - } - this.updateRange(y); -}; - - - -/** - * Erase in the identified line everything from "x" to the start of the line (left). - * @param {number} x The column from which to start erasing to the start of the line. - * @param {number} y The line in which to operate. - */ -Terminal.prototype.eraseLeft = function(x, y) { - var line = this.buffer.lines.get(this.buffer.ybase + y); - if (!line) { - return; - } - var ch = [this.eraseAttr(), ' ', 1]; // xterm - x++; - while (x--) { - line[x] = ch; - } - this.updateRange(y); -}; - -/** - * Clears the entire buffer, making the prompt line the new first line. - */ -Terminal.prototype.clear = function() { - if (this.buffer.ybase === 0 && this.buffer.y === 0) { - // Don't clear if it's already clear - return; - } - this.buffer.lines.set(0, this.buffer.lines.get(this.buffer.ybase + this.buffer.y)); - this.buffer.lines.length = 1; - this.buffer.ydisp = 0; - this.buffer.ybase = 0; - this.buffer.y = 0; - for (var i = 1; i < this.rows; i++) { - this.buffer.lines.push(this.blankLine()); - } - this.refresh(0, this.rows - 1); - this.emit('scroll', this.buffer.ydisp); -}; - -/** - * Erase all content in the given line - * @param {number} y The line to erase all of its contents. - */ -Terminal.prototype.eraseLine = function(y) { - this.eraseRight(0, y); -}; - - -/** - * Return the data array of a blank line - * @param {number} cur First bunch of data for each "blank" character. - * @param {boolean} isWrapped Whether the new line is wrapped from the previous line. - */ -Terminal.prototype.blankLine = function(cur, isWrapped) { - var attr = cur - ? this.eraseAttr() - : this.defAttr; - - var ch = [attr, ' ', 1] // width defaults to 1 halfwidth character - , line = [] - , i = 0; - - // TODO: It is not ideal that this is a property on an array, a buffer line - // class should be added that will hold this data and other useful functions. - if (isWrapped) { - line.isWrapped = isWrapped; - } - - for (; i < this.cols; i++) { - line[i] = ch; - } - - return line; -}; - - -/** - * If cur return the back color xterm feature attribute. Else return defAttr. - * @param {object} cur - */ -Terminal.prototype.ch = function(cur) { - return cur - ? [this.eraseAttr(), ' ', 1] - : [this.defAttr, ' ', 1]; -}; - - -/** - * Evaluate if the current terminal is the given argument. - * @param {object} term The terminal to evaluate - */ -Terminal.prototype.is = function(term) { - var name = this.termName; - return (name + '').indexOf(term) === 0; -}; - - -/** - * Emit the 'data' event and populate the given data. - * @param {string} data The data to populate in the event. - */ -Terminal.prototype.handler = function(data) { - // Prevents all events to pty process if stdin is disabled - if (this.options.disableStdin) { - return; - } - - // Clear the selection if the selection manager is available and has an active selection - if (this.selectionManager && this.selectionManager.hasSelection) { - this.selectionManager.clearSelection(); - } - - // Input is being sent to the terminal, the terminal should focus the prompt. - if (this.buffer.ybase !== this.buffer.ydisp) { - this.scrollToBottom(); - } - this.emit('data', data); -}; - - -/** - * Emit the 'title' event and populate the given title. - * @param {string} title The title to populate in the event. - */ -Terminal.prototype.handleTitle = function(title) { /** - * This event is emitted when the title of the terminal is changed - * from inside the terminal. The parameter is the new title. + * Returns an object that determines how a KeyboardEvent should be handled. The key of the + * returned value is the new key code to pass to the PTY. * - * @event title + * Reference: http://invisible-island.net/xterm/ctlseqs/ctlseqs.html + * @param {KeyboardEvent} ev The keyboard event to be translated to key escape sequence. */ - this.emit('title', title); -}; + private evaluateKeyEscapeSequence(ev) { + var result = { + // Whether to cancel event propogation (NOTE: this may not be needed since the event is + // canceled at the end of keyDown + cancel: false, + // The new key even to emit + key: undefined, + // The number of characters to scroll, if this is defined it will cancel the event + scrollDisp: undefined + }; + var modifiers = ev.shiftKey << 0 | ev.altKey << 1 | ev.ctrlKey << 2 | ev.metaKey << 3; + switch (ev.keyCode) { + case 8: + // backspace + if (ev.shiftKey) { + result.key = C0.BS; // ^H + break; + } + result.key = C0.DEL; // ^? + break; + case 9: + // tab + if (ev.shiftKey) { + result.key = C0.ESC + '[Z'; + break; + } + result.key = C0.HT; + result.cancel = true; + break; + case 13: + // return/enter + result.key = C0.CR; + result.cancel = true; + break; + case 27: + // escape + result.key = C0.ESC; + result.cancel = true; + break; + case 37: + // left-arrow + if (modifiers) { + result.key = C0.ESC + '[1;' + (modifiers + 1) + 'D'; + // HACK: Make Alt + left-arrow behave like Ctrl + left-arrow: move one word backwards + // http://unix.stackexchange.com/a/108106 + // macOS uses different escape sequences than linux + if (result.key == C0.ESC + '[1;3D') { + result.key = (this.browser.isMac) ? C0.ESC + 'b' : C0.ESC + '[1;5D'; + } + } else if (this.applicationCursor) { + result.key = C0.ESC + 'OD'; + } else { + result.key = C0.ESC + '[D'; + } + break; + case 39: + // right-arrow + if (modifiers) { + result.key = C0.ESC + '[1;' + (modifiers + 1) + 'C'; + // HACK: Make Alt + right-arrow behave like Ctrl + right-arrow: move one word forward + // http://unix.stackexchange.com/a/108106 + // macOS uses different escape sequences than linux + if (result.key == C0.ESC + '[1;3C') { + result.key = (this.browser.isMac) ? C0.ESC + 'f' : C0.ESC + '[1;5C'; + } + } else if (this.applicationCursor) { + result.key = C0.ESC + 'OC'; + } else { + result.key = C0.ESC + '[C'; + } + break; + case 38: + // up-arrow + if (modifiers) { + result.key = C0.ESC + '[1;' + (modifiers + 1) + 'A'; + // HACK: Make Alt + up-arrow behave like Ctrl + up-arrow + // http://unix.stackexchange.com/a/108106 + if (result.key == C0.ESC + '[1;3A') { + result.key = C0.ESC + '[1;5A'; + } + } else if (this.applicationCursor) { + result.key = C0.ESC + 'OA'; + } else { + result.key = C0.ESC + '[A'; + } + break; + case 40: + // down-arrow + if (modifiers) { + result.key = C0.ESC + '[1;' + (modifiers + 1) + 'B'; + // HACK: Make Alt + down-arrow behave like Ctrl + down-arrow + // http://unix.stackexchange.com/a/108106 + if (result.key == C0.ESC + '[1;3B') { + result.key = C0.ESC + '[1;5B'; + } + } else if (this.applicationCursor) { + result.key = C0.ESC + 'OB'; + } else { + result.key = C0.ESC + '[B'; + } + break; + case 45: + // insert + if (!ev.shiftKey && !ev.ctrlKey) { + // or + are used to + // copy-paste on some systems. + result.key = C0.ESC + '[2~'; + } + break; + case 46: + // delete + if (modifiers) { + result.key = C0.ESC + '[3;' + (modifiers + 1) + '~'; + } else { + result.key = C0.ESC + '[3~'; + } + break; + case 36: + // home + if (modifiers) + result.key = C0.ESC + '[1;' + (modifiers + 1) + 'H'; + else if (this.applicationCursor) + result.key = C0.ESC + 'OH'; + else + result.key = C0.ESC + '[H'; + break; + case 35: + // end + if (modifiers) + result.key = C0.ESC + '[1;' + (modifiers + 1) + 'F'; + else if (this.applicationCursor) + result.key = C0.ESC + 'OF'; + else + result.key = C0.ESC + '[F'; + break; + case 33: + // page up + if (ev.shiftKey) { + result.scrollDisp = -(this.rows - 1); + } else { + result.key = C0.ESC + '[5~'; + } + break; + case 34: + // page down + if (ev.shiftKey) { + result.scrollDisp = this.rows - 1; + } else { + result.key = C0.ESC + '[6~'; + } + break; + case 112: + // F1-F12 + if (modifiers) { + result.key = C0.ESC + '[1;' + (modifiers + 1) + 'P'; + } else { + result.key = C0.ESC + 'OP'; + } + break; + case 113: + if (modifiers) { + result.key = C0.ESC + '[1;' + (modifiers + 1) + 'Q'; + } else { + result.key = C0.ESC + 'OQ'; + } + break; + case 114: + if (modifiers) { + result.key = C0.ESC + '[1;' + (modifiers + 1) + 'R'; + } else { + result.key = C0.ESC + 'OR'; + } + break; + case 115: + if (modifiers) { + result.key = C0.ESC + '[1;' + (modifiers + 1) + 'S'; + } else { + result.key = C0.ESC + 'OS'; + } + break; + case 116: + if (modifiers) { + result.key = C0.ESC + '[15;' + (modifiers + 1) + '~'; + } else { + result.key = C0.ESC + '[15~'; + } + break; + case 117: + if (modifiers) { + result.key = C0.ESC + '[17;' + (modifiers + 1) + '~'; + } else { + result.key = C0.ESC + '[17~'; + } + break; + case 118: + if (modifiers) { + result.key = C0.ESC + '[18;' + (modifiers + 1) + '~'; + } else { + result.key = C0.ESC + '[18~'; + } + break; + case 119: + if (modifiers) { + result.key = C0.ESC + '[19;' + (modifiers + 1) + '~'; + } else { + result.key = C0.ESC + '[19~'; + } + break; + case 120: + if (modifiers) { + result.key = C0.ESC + '[20;' + (modifiers + 1) + '~'; + } else { + result.key = C0.ESC + '[20~'; + } + break; + case 121: + if (modifiers) { + result.key = C0.ESC + '[21;' + (modifiers + 1) + '~'; + } else { + result.key = C0.ESC + '[21~'; + } + break; + case 122: + if (modifiers) { + result.key = C0.ESC + '[23;' + (modifiers + 1) + '~'; + } else { + result.key = C0.ESC + '[23~'; + } + break; + case 123: + if (modifiers) { + result.key = C0.ESC + '[24;' + (modifiers + 1) + '~'; + } else { + result.key = C0.ESC + '[24~'; + } + break; + default: + // a-z and space + if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) { + if (ev.keyCode >= 65 && ev.keyCode <= 90) { + result.key = String.fromCharCode(ev.keyCode - 64); + } else if (ev.keyCode === 32) { + // NUL + result.key = String.fromCharCode(0); + } else if (ev.keyCode >= 51 && ev.keyCode <= 55) { + // escape, file sep, group sep, record sep, unit sep + result.key = String.fromCharCode(ev.keyCode - 51 + 27); + } else if (ev.keyCode === 56) { + // delete + result.key = String.fromCharCode(127); + } else if (ev.keyCode === 219) { + // ^[ - Control Sequence Introducer (CSI) + result.key = String.fromCharCode(27); + } else if (ev.keyCode === 220) { + // ^\ - String Terminator (ST) + result.key = String.fromCharCode(28); + } else if (ev.keyCode === 221) { + // ^] - Operating System Command (OSC) + result.key = String.fromCharCode(29); + } + } else if (!this.browser.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) { + // On Mac this is a third level shift. 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 + '`'; + } else if (ev.keyCode >= 48 && ev.keyCode <= 57) { + result.key = C0.ESC + (ev.keyCode - 48); + } + } else if (this.browser.isMac && !ev.altKey && !ev.ctrlKey && ev.metaKey) { + if (ev.keyCode === 65) { // cmd + a + this.selectAll(); + } + } + break; + } - -/** - * ESC - */ - -/** - * ESC D Index (IND is 0x84). - */ -Terminal.prototype.index = function() { - this.buffer.y++; - if (this.buffer.y > this.buffer.scrollBottom) { - this.buffer.y--; - this.scroll(); + return result; } - // If the end of the line is hit, prevent this action from wrapping around to the next line. - if (this.buffer.x >= this.cols) { - this.buffer.x--; + + /** + * Set the G level of the terminal + * @param g + */ + public setgLevel = function(g: number) { + this.glevel = g; + this.charset = this.charsets[g]; } -}; + /** + * Set the charset for the given G level of the terminal + * @param g + * @param charset + */ + public setgCharset(g: number, charset: Charset) { + this.charsets[g] = charset; + if (this.glevel === g) { + this.charset = charset; + } + } -/** - * ESC M Reverse Index (RI is 0x8d). - * - * Move the cursor up one row, inserting a new blank line if necessary. - */ -Terminal.prototype.reverseIndex = function() { - var j; - if (this.buffer.y === this.buffer.scrollTop) { - // possibly move the code below to term.reverseScroll(); - // test: echo -ne '\e[1;1H\e[44m\eM\e[0m' - // blankLine(true) is xterm/linux behavior - this.buffer.lines.shiftElements(this.buffer.y + this.buffer.ybase, this.rows - 1, 1); - this.buffer.lines.set(this.buffer.y + this.buffer.ybase, this.blankLine(true)); - this.updateRange(this.buffer.scrollTop); - this.updateRange(this.buffer.scrollBottom); - } else { - this.buffer.y--; + /** + * Handle a keypress event. + * Key Resources: + * - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent + * @param {KeyboardEvent} ev The keypress event to be handled. + */ + private keyPress(ev) { + var key; + + if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) { + return false; + } + + this.cancel(ev); + + if (ev.charCode) { + key = ev.charCode; + } else if (ev.which == null) { + key = ev.keyCode; + } else if (ev.which !== 0 && ev.charCode !== 0) { + key = ev.which; + } else { + return false; + } + + if (!key || ( + (ev.altKey || ev.ctrlKey || ev.metaKey) && !isThirdLevelShift(this, ev) + )) { + return false; + } + + key = String.fromCharCode(key); + + this.emit('keypress', key, ev); + this.emit('key', key, ev); + this.showCursor(); + this.handler(key); + + return true; + } + + /** + * Send data for handling to the terminal + * @param {string} data + */ + private send(data) { + if (!this.queue) { + setTimeout(() => { + this.handler(this.queue); + this.queue = ''; + }, 1); + } + + this.queue += data; + } + + /** + * Ring the bell. + * Note: We could do sweet things with webaudio here + */ + public bell() { + if (!this.options.visualBell) return; + this.element.style.borderColor = 'white'; + setTimeout(() => { + this.element.style.borderColor = ''; + }, 10); + if (this.options.popOnBell) this.focus(); + } + + /** + * Log the current state to the console. + */ + public log(): void { + if (!this.options.debug) return; + if (!this.context.console || !this.context.console.log) return; + const args = Array.prototype.slice.call(arguments); + this.context.console.log.apply(this.context.console, args); + } + + /** + * Log the current state as error to the console. + */ + public error(): void { + if (!this.options.debug) return; + if (!this.context.console || !this.context.console.error) return; + const args = Array.prototype.slice.call(arguments); + this.context.console.error.apply(this.context.console, args); + } + + /** + * Resizes the terminal. + * + * @param {number} x The number of columns to resize to. + * @param {number} y The number of rows to resize to. + */ + public resize(x: number, y: number) { + if (isNaN(x) || isNaN(y)) { + return; + } + + if (y > this.getOption('scrollback')) { + this.setOption('scrollback', y); + } + + var line + , el + , i + , j + , ch + , addToY; + + if (x === this.cols && y === this.rows) { + // Check if we still need to measure the char size (fixes #785). + if (!this.charMeasure.width || !this.charMeasure.height) { + this.charMeasure.measure(); + } + return; + } + + if (x < 1) x = 1; + if (y < 1) y = 1; + + // resize cols + j = this.cols; + if (j < x) { + ch = [this.defAttr, ' ', 1]; // does xterm use the default attr? + i = this.buffer.lines.length; + while (i--) { + if (this.buffer.lines.get(i) === undefined) { + this.buffer.lines.set(i, this.blankLine()); + } + while (this.buffer.lines.get(i).length < x) { + this.buffer.lines.get(i).push(ch); + } + } + } + + this.cols = x; + this.setupStops(this.cols); + + // resize rows + j = this.rows; + addToY = 0; + if (j < y) { + el = this.element; + while (j++ < y) { + // y is rows, not this.buffer.y + if (this.buffer.lines.length < y + this.buffer.ybase) { + if (this.buffer.ybase > 0 && this.buffer.lines.length <= this.buffer.ybase + this.buffer.y + addToY + 1) { + // There is room above the buffer and there are no empty elements below the line, + // scroll up + this.buffer.ybase--; + addToY++; + if (this.buffer.ydisp > 0) { + // Viewport is at the top of the buffer, must increase downwards + this.buffer.ydisp--; + } + } else { + // Add a blank line if there is no buffer left at the top to scroll to, or if there + // are blank lines after the cursor + this.buffer.lines.push(this.blankLine()); + } + } + if (this.children.length < y) { + this.insertRow(); + } + } + } else { // (j > y) + while (j-- > y) { + if (this.buffer.lines.length > y + this.buffer.ybase) { + if (this.buffer.lines.length > this.buffer.ybase + this.buffer.y + 1) { + // The line is a blank line below the cursor, remove it + this.buffer.lines.pop(); + } else { + // The line is the cursor, scroll down + this.buffer.ybase++; + this.buffer.ydisp++; + } + } + if (this.children.length > y) { + el = this.children.shift(); + if (!el) continue; + el.parentNode.removeChild(el); + } + } + } + this.rows = y; + + // Make sure that the cursor stays on screen + if (this.buffer.y >= y) { + this.buffer.y = y - 1; + } + if (addToY) { + this.buffer.y += addToY; + } + + if (this.buffer.x >= x) { + this.buffer.x = x - 1; + } + + this.buffer.scrollTop = 0; + this.buffer.scrollBottom = y - 1; + + this.charMeasure.measure(); + + this.refresh(0, this.rows - 1); + + this.geometry = [this.cols, this.rows]; + this.emit('resize', {terminal: this, cols: x, rows: y}); + } + + /** + * Updates the range of rows to refresh + * @param {number} y The number of rows to refresh next. + */ + public updateRange(y) { + if (y < this.refreshStart) this.refreshStart = y; + if (y > this.refreshEnd) this.refreshEnd = y; + // if (y > this.refreshEnd) { + // this.refreshEnd = y; + // if (y > this.rows - 1) { + // this.refreshEnd = this.rows - 1; + // } + // } + } + + /** + * Set the range of refreshing to the maximum value + */ + public maxRange() { + this.refreshStart = 0; + this.refreshEnd = this.rows - 1; + } + + /** + * Setup the tab stops. + * @param {number} i + */ + public setupStops(i?: number) { + if (i != null) { + if (!this.buffer.tabs[i]) { + i = this.prevStop(i); + } + } else { + this.buffer.tabs = {}; + i = 0; + } + + for (; i < this.cols; i += this.getOption('tabStopWidth')) { + this.buffer.tabs[i] = true; + } + } + + /** + * Move the cursor to the previous tab stop from the given position (default is current). + * @param {number} x The position to move the cursor to the previous tab stop. + */ + public prevStop(x) { + if (x == null) x = this.buffer.x; + while (!this.buffer.tabs[--x] && x > 0); + return x >= this.cols ? this.cols - 1 : x < 0 ? 0 : x; + } + + /** + * Move the cursor one tab stop forward from the given position (default is current). + * @param {number} x The position to move the cursor one tab stop forward. + */ + public nextStop(x) { + if (x == null) x = this.buffer.x; + while (!this.buffer.tabs[++x] && x < this.cols); + return x >= this.cols + ? this.cols - 1 + : x < 0 ? 0 : x; + } + + /** + * Erase in the identified line everything from "x" to the end of the line (right). + * @param {number} x The column from which to start erasing to the end of the line. + * @param {number} y The line in which to operate. + */ + public eraseRight(x: number, y: number): void { + const line = this.buffer.lines.get(this.buffer.ybase + y); + if (!line) { + return; + } + const ch: [number, string, number] = [this.eraseAttr(), ' ', 1]; // xterm + for (; x < this.cols; x++) { + line[x] = ch; + } + this.updateRange(y); + } + + /** + * Erase in the identified line everything from "x" to the start of the line (left). + * @param {number} x The column from which to start erasing to the start of the line. + * @param {number} y The line in which to operate. + */ + public eraseLeft(x: number, y: number) { + const line = this.buffer.lines.get(this.buffer.ybase + y); + if (!line) { + return; + } + const ch: [number, string, number] = [this.eraseAttr(), ' ', 1]; // xterm + x++; + while (x--) { + line[x] = ch; + } + this.updateRange(y); + } + + /** + * Clears the entire buffer, making the prompt line the new first line. + */ + public clear() { + if (this.buffer.ybase === 0 && this.buffer.y === 0) { + // Don't clear if it's already clear + return; + } + this.buffer.lines.set(0, this.buffer.lines.get(this.buffer.ybase + this.buffer.y)); + this.buffer.lines.length = 1; + this.buffer.ydisp = 0; + this.buffer.ybase = 0; + this.buffer.y = 0; + for (var i = 1; i < this.rows; i++) { + this.buffer.lines.push(this.blankLine()); + } + this.refresh(0, this.rows - 1); + this.emit('scroll', this.buffer.ydisp); + } + + /** + * Erase all content in the given line + * @param {number} y The line to erase all of its contents. + */ + public eraseLine(y): void { + this.eraseRight(0, y); + } + + /** + * Return the data array of a blank line + * @param {boolean} cur First bunch of data for each "blank" character. + * @param {boolean} isWrapped Whether the new line is wrapped from the previous line. + */ + public blankLine(cur?, isWrapped?: boolean) { + const attr = cur ? this.eraseAttr() : this.defAttr; + + const ch = [attr, ' ', 1]; // width defaults to 1 halfwidth character + const line = []; + + // TODO: It is not ideal that this is a property on an array, a buffer line + // class should be added that will hold this data and other useful functions. + if (isWrapped) { + (line).isWrapped = isWrapped; + } + + for (let i = 0; i < this.cols; i++) { + line[i] = ch; + } + + return line; + } + + /** + * If cur return the back color xterm feature attribute. Else return defAttr. + * @param {object} cur + */ + public ch(cur) { + return cur ? [this.eraseAttr(), ' ', 1] : [this.defAttr, ' ', 1]; + } + + /** + * Evaluate if the current terminal is the given argument. + * @param {object} term The terminal to evaluate + */ + private is(term) { + // TODO: Do we need this? + const name = this.options.termName; + return (name + '').indexOf(term) === 0; + } + + /** + * Emit the 'data' event and populate the given data. + * @param {string} data The data to populate in the event. + */ + private handler(data: string): void { + // Prevents all events to pty process if stdin is disabled + if (this.options.disableStdin) { + return; + } + + // Clear the selection if the selection manager is available and has an active selection + if (this.selectionManager && this.selectionManager.hasSelection) { + this.selectionManager.clearSelection(); + } + + // Input is being sent to the terminal, the terminal should focus the prompt. + if (this.buffer.ybase !== this.buffer.ydisp) { + this.scrollToBottom(); + } + this.emit('data', data); + } + + /** + * Emit the 'title' event and populate the given title. + * @param {string} title The title to populate in the event. + */ + private handleTitle(title: string) { + /** + * This event is emitted when the title of the terminal is changed + * from inside the terminal. The parameter is the new title. + * + * @event title + */ + this.emit('title', title); + } + + /** + * ESC + */ + + /** + * ESC D Index (IND is 0x84). + */ + public index() { + this.buffer.y++; + if (this.buffer.y > this.buffer.scrollBottom) { + this.buffer.y--; + this.scroll(); + } + // If the end of the line is hit, prevent this action from wrapping around to the next line. + if (this.buffer.x >= this.cols) { + this.buffer.x--; + } + } + + /** + * ESC M Reverse Index (RI is 0x8d). + * + * Move the cursor up one row, inserting a new blank line if necessary. + */ + public reverseIndex() { + var j; + if (this.buffer.y === this.buffer.scrollTop) { + // possibly move the code below to term.reverseScroll(); + // test: echo -ne '\e[1;1H\e[44m\eM\e[0m' + // blankLine(true) is xterm/linux behavior + this.buffer.lines.shiftElements(this.buffer.y + this.buffer.ybase, this.rows - 1, 1); + this.buffer.lines.set(this.buffer.y + this.buffer.ybase, this.blankLine(true)); + this.updateRange(this.buffer.scrollTop); + this.updateRange(this.buffer.scrollBottom); + } else { + this.buffer.y--; + } + } + + /** + * ESC c Full Reset (RIS). + */ + public reset() { + this.options.rows = this.rows; + this.options.cols = this.cols; + var customKeyEventHandler = this.customKeyEventHandler; + var cursorBlinkInterval = this.cursorBlinkInterval; + var inputHandler = this.inputHandler; + var buffers = this.buffers; + Terminal.call(this, this.options); + this.customKeyEventHandler = customKeyEventHandler; + this.cursorBlinkInterval = cursorBlinkInterval; + this.inputHandler = inputHandler; + this.buffers = buffers; + this.refresh(0, this.rows - 1); + this.viewport.syncScrollArea(); } -}; -/** - * ESC c Full Reset (RIS). - */ -Terminal.prototype.reset = function() { - this.options.rows = this.rows; - this.options.cols = this.cols; - var customKeyEventHandler = this.customKeyEventHandler; - var cursorBlinkInterval = this.cursorBlinkInterval; - var inputHandler = this.inputHandler; - var buffers = this.buffers; - Terminal.call(this, this.options); - this.customKeyEventHandler = customKeyEventHandler; - this.cursorBlinkInterval = cursorBlinkInterval; - this.inputHandler = inputHandler; - this.buffers = buffers; - this.refresh(0, this.rows - 1); - this.viewport.syncScrollArea(); -}; + /** + * ESC H Tab Set (HTS is 0x88). + */ + private tabSet() { + this.buffer.tabs[this.buffer.x] = true; + } + + private cancel(ev: Event, force?: boolean) { + if (!this.options.cancelEvents && !force) { + return; + } + ev.preventDefault(); + ev.stopPropagation(); + return false; + } + + // Expose to InputHandler + // TODO: Revise when truecolor is introduced. + public matchColor(r1, g1, b1) { + var hash = (r1 << 16) | (g1 << 8) | b1; + + if (matchColorCache[hash] != null) { + return matchColorCache[hash]; + } + + let ldiff = Infinity; + let li = -1; + let i = 0; + let c; + let r2; + let g2; + let b2; + let diff; + + for (; i < vcolors.length; i++) { + c = vcolors[i]; + r2 = c[0]; + g2 = c[1]; + b2 = c[2]; + + diff = matchColorDistance(r1, g1, b1, r2, g2, b2); + + if (diff === 0) { + li = i; + break; + } + + if (diff < ldiff) { + ldiff = diff; + li = i; + } + } + + return matchColorCache[hash] = li; + } +} -/** - * ESC H Tab Set (HTS is 0x88). - */ -Terminal.prototype.tabSet = function() { - this.buffer.tabs[this.buffer.x] = true; -}; + +// TODO: Make sure things work after this is removed +// each(keys(Terminal.defaults), function(key) { +// Terminal[key] = Terminal.defaults[key]; +// Terminal.options[key] = Terminal.defaults[key]; +// }); + /** * Helpers */ -function on(el, type, handler, capture) { +function globalOn(el: HTMLElement | HTMLElement[], type: string, handler: (event: Event) => any, capture?: boolean) { if (!Array.isArray(el)) { el = [el]; } @@ -2323,20 +2376,13 @@ function on(el, type, handler, capture) { element.addEventListener(type, handler, capture || false); }); } +// TODO: Remove once everything is typed +const on = globalOn; function off(el, type, handler, capture) { el.removeEventListener(type, handler, capture || false); } -function cancel(ev, force) { - if (!this.cancelEvents && !force) { - return; - } - ev.preventDefault(); - ev.stopPropagation(); - return false; -} - function inherits(child, parent) { function f() { this.constructor = child; @@ -2358,7 +2404,7 @@ function isThirdLevelShift(term, ev) { (term.browser.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) || (term.browser.isMSWindows && ev.altKey && ev.ctrlKey && !ev.metaKey); - if (ev.type == 'keypress') { + if (ev.type === 'keypress') { return thirdLevelKey; } @@ -2366,51 +2412,10 @@ function isThirdLevelShift(term, ev) { return thirdLevelKey && (!ev.keyCode || ev.keyCode > 47); } -// Expose to InputHandler (temporary) -Terminal.prototype.matchColor = matchColor; - -function matchColor(r1, g1, b1) { - var hash = (r1 << 16) | (g1 << 8) | b1; - - if (matchColor._cache[hash] != null) { - return matchColor._cache[hash]; - } - - var ldiff = Infinity - , li = -1 - , i = 0 - , c - , r2 - , g2 - , b2 - , diff; - - for (; i < Terminal.vcolors.length; i++) { - c = Terminal.vcolors[i]; - r2 = c[0]; - g2 = c[1]; - b2 = c[2]; - - diff = matchColor.distance(r1, g1, b1, r2, g2, b2); - - if (diff === 0) { - li = i; - break; - } - - if (diff < ldiff) { - ldiff = diff; - li = i; - } - } - - return matchColor._cache[hash] = li; -} - -matchColor._cache = {}; +const matchColorCache = {}; // http://stackoverflow.com/questions/1633828 -matchColor.distance = function(r1, g1, b1, r2, g2, b2) { +const matchColorDistance = function(r1, g1, b1, r2, g2, b2) { return Math.pow(30 * (r1 - r2), 2) + Math.pow(59 * (g1 - g2), 2) + Math.pow(11 * (b1 - b2), 2); @@ -2418,7 +2423,7 @@ matchColor.distance = function(r1, g1, b1, r2, g2, b2) { function each(obj, iter, con) { if (obj.forEach) return obj.forEach(iter, con); - for (var i = 0; i < obj.length; i++) { + for (let i = 0; i < obj.length; i++) { iter.call(con, obj[i], i, obj); } } @@ -2431,7 +2436,7 @@ function wasMondifierKeyOnlyEvent(ev) { function keys(obj) { if (Object.keys) return Object.keys(obj); - var key, keys = []; + let key, keys = []; for (key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { keys.push(key); @@ -2444,9 +2449,9 @@ function keys(obj) { * Expose */ -Terminal.translateBufferLineToString = translateBufferLineToString; -Terminal.EventEmitter = EventEmitter; -Terminal.inherits = inherits; +// Terminal.translateBufferLineToString = translateBufferLineToString; +// Terminal.EventEmitter = EventEmitter; +// Terminal.inherits = inherits; /** * Adds an event listener to the terminal. @@ -2454,8 +2459,7 @@ Terminal.inherits = inherits; * @param {string} event The name of the event. TODO: Document all event types * @param {function} callback The function to call when the event is triggered. */ -Terminal.on = on; -Terminal.off = off; -Terminal.cancel = cancel; +// Terminal.on = on; +// Terminal.off = off; module.exports = Terminal; diff --git a/src/Types.ts b/src/Types.ts index 896b729a..9e4b1e1a 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -12,3 +12,8 @@ export type LinkMatcher = { }; export type LinkMatcherHandler = (event: MouseEvent, uri: string) => boolean | void; export type LinkMatcherValidationCallback = (uri: string, element: HTMLElement, callback: (isValid: boolean) => void) => void; + +// TODO: Make this type more specific +export type TerminalOptions = {[key: string]: any}; +export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean; +export type Charset = {[key: string]: string}; diff --git a/src/utils/CircularList.ts b/src/utils/CircularList.ts index d0b2f685..54850ab7 100644 --- a/src/utils/CircularList.ts +++ b/src/utils/CircularList.ts @@ -5,8 +5,9 @@ * @license MIT */ import { EventEmitter } from '../EventEmitter'; +import { ICircularList } from '../Interfaces'; -export class CircularList extends EventEmitter { +export class CircularList extends EventEmitter implements ICircularList { private _array: T[]; private _startIndex: number; private _length: number; diff --git a/src/xterm.ts b/src/xterm.ts new file mode 100644 index 00000000..0c2b7319 --- /dev/null +++ b/src/xterm.ts @@ -0,0 +1,3 @@ +import { Terminal } from './Terminal'; + +module.exports = Terminal; From 41c335c0e635d463245e6f512d144ff3467a1f3d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 5 Aug 2017 02:22:43 -0700 Subject: [PATCH 003/378] The terminal works! --- src/Buffer.test.ts | 2 +- src/Buffer.ts | 3 +- src/Interfaces.ts | 4 +- src/SelectionManager.test.ts | 4 +- src/SelectionModel.test.ts | 4 +- src/Terminal.ts | 76 ++++++++++++++++++------------------ 6 files changed, 48 insertions(+), 45 deletions(-) diff --git a/src/Buffer.test.ts b/src/Buffer.test.ts index f68baa82..c4c3a267 100644 --- a/src/Buffer.test.ts +++ b/src/Buffer.test.ts @@ -22,7 +22,7 @@ describe('Buffer', () => { describe('constructor', () => { it('should create a CircularList with max length equal to scrollback, for its lines', () => { assert.instanceOf(buffer.lines, CircularList); - assert.equal(buffer.lines.maxLength, terminal.scrollback); + assert.equal(buffer.lines.maxLength, terminal.options.scrollback); }); it('should set the Buffer\'s scrollBottom value equal to the terminal\'s rows -1', () => { assert.equal(buffer.scrollBottom, terminal.rows - 1); diff --git a/src/Buffer.ts b/src/Buffer.ts index 12b2137d..b76cdc4d 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -36,7 +36,8 @@ export class Buffer { public scrollTop: number = 0, public tabs: any = {}, ) { - this.lines = new CircularList<[number, string, number][]>(this.terminal.scrollback); + // TODO: Don't cast terminal to any + this.lines = new CircularList<[number, string, number][]>(this.terminal.options.scrollback); this.scrollBottom = this.terminal.rows - 1; } } diff --git a/src/Interfaces.ts b/src/Interfaces.ts index c675f7a2..c24aa2ef 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -3,7 +3,7 @@ */ import { LinkMatcherOptions } from './Interfaces'; -import { LinkMatcherHandler, LinkMatcherValidationCallback } from './Types'; +import { LinkMatcherHandler, LinkMatcherValidationCallback, TerminalOptions } from './Types'; export interface IBrowser { isNode: boolean; @@ -32,7 +32,7 @@ export interface ITerminal { cursorHidden: boolean; cursorState: number; defAttr: number; - scrollback: number; + options: TerminalOptions; buffers: IBufferSet; buffer: IBuffer; diff --git a/src/SelectionManager.test.ts b/src/SelectionManager.test.ts index c2173d77..94d58c3e 100644 --- a/src/SelectionManager.test.ts +++ b/src/SelectionManager.test.ts @@ -46,8 +46,8 @@ describe('SelectionManager', () => { window = dom.window; document = window.document; rowContainer = document.createElement('div'); - terminal = { cols: 80, rows: 2 }; - terminal.scrollback = 100; + terminal = { cols: 80, rows: 2, options: {} }; + terminal.options.scrollback = 100; terminal.buffers = new BufferSet(terminal); terminal.buffer = terminal.buffers.active; bufferLines = terminal.buffer.lines; diff --git a/src/SelectionModel.test.ts b/src/SelectionModel.test.ts index 6da3874b..9ec36e0d 100644 --- a/src/SelectionModel.test.ts +++ b/src/SelectionModel.test.ts @@ -22,8 +22,8 @@ describe('SelectionManager', () => { let model: TestSelectionModel; beforeEach(() => { - terminal = { cols: 80, rows: 2, ybase: 0 }; - terminal.scrollback = 10; + terminal = { cols: 80, rows: 2, ybase: 0, options: {} }; + terminal.options.scrollback = 10; terminal.buffers = new BufferSet(terminal); terminal.buffer = terminal.buffers.active; diff --git a/src/Terminal.ts b/src/Terminal.ts index 18fcd000..19f10e7d 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -30,7 +30,7 @@ import { CHARSETS } from './Charsets'; import { getRawByteCoords } from './utils/Mouse'; import { translateBufferLineToString } from './utils/BufferLine'; import { TerminalOptions, CustomKeyEventHandler, Charset } from './Types'; -import { ITerminal, IBrowser } from "./Interfaces"; +import { ITerminal, IBrowser } from './Interfaces'; // Declare for RequireJS in loadAddon declare var define: any; @@ -192,7 +192,7 @@ export class Terminal extends EventEmitter implements ITerminal { private body: HTMLBodyElement; private viewportScrollArea: HTMLElement; private viewportElement: HTMLElement; - private selectionContainer: HTMLElement; + public selectionContainer: HTMLElement; private helperContainer: HTMLElement; private compositionView: HTMLElement; private charSizeStyleElement: HTMLStyleElement; @@ -201,12 +201,13 @@ export class Terminal extends EventEmitter implements ITerminal { // in Browser for themselves. public browser: IBrowser = Browser; - private options: TerminalOptions; + // TODO: Options should be private, remove from interface in favor of getOption + public options: TerminalOptions; private colors: any; // TODO: This can be changed to an enum or boolean, 0 and 1 seem to be the only options - private cursorState: number = 0; - private cursorHidden: boolean = false; + public cursorState: number = 0; + public cursorHidden: boolean = false; private convertEol: boolean; // TODO: This is the data queue for send, improve name and documentation private queue: string = ''; @@ -243,7 +244,7 @@ export class Terminal extends EventEmitter implements ITerminal { private urxvtMouse; // misc - private children; + public children: HTMLElement[]; private refreshStart; private refreshEnd; private savedX; @@ -254,13 +255,13 @@ export class Terminal extends EventEmitter implements ITerminal { private readable: boolean = true; private writable: boolean = true; - private defAttr: number = (0 << 18) | (257 << 9) | (256 << 0); - private curAttr: number = (0 << 18) | (257 << 9) | (256 << 0); + public defAttr: number = (0 << 18) | (257 << 9) | (256 << 0); + public curAttr: number = (0 << 18) | (257 << 9) | (256 << 0); - private params: (string | number)[] = []; - private currentParam: string | number = 0; - private prefix: string = ''; - private postfix: string = ''; + public params: (string | number)[] = []; + public currentParam: string | number = 0; + public prefix: string = ''; + public postfix: string = ''; // user input states public writeBuffer: string[] = []; @@ -286,13 +287,13 @@ export class Terminal extends EventEmitter implements ITerminal { private inputHandler: InputHandler; private parser: Parser; private renderer: Renderer; - private selectionManager: SelectionManager;; + public selectionManager: SelectionManager;; private linkifier: Linkifier; - private buffers: BufferSet; - private buffer: Buffer; + public buffers: BufferSet; + public buffer: Buffer; private viewport: Viewport; private compositionHelper: CompositionHelper; - private charMeasure: CharMeasure; + public charMeasure: CharMeasure; public cols: number; public rows: number; @@ -328,10 +329,9 @@ export class Terminal extends EventEmitter implements ITerminal { Object.keys(DEFAULT_OPTIONS).forEach(function(key) { if (options[key] == null) { - if (Terminal[key] !== DEFAULT_OPTIONS[key]) { - options[key] = Terminal[key]; - } + options[key] = DEFAULT_OPTIONS[key]; } + // TODO: We should move away from duplicate options on the Terminal object self[key] = options[key]; }); @@ -346,6 +346,7 @@ export class Terminal extends EventEmitter implements ITerminal { options.colors = options.colors.concat( _colors.slice(16, -2), options.colors.slice(-2)); } + this.options = options; this.colors = options.colors; // this.context = options.context || window; @@ -598,20 +599,20 @@ export class Terminal extends EventEmitter implements ITerminal { self.keyPress(ev); }, true); - on(this.element, 'keyup', function(ev) { + on(this.element, 'keyup', (ev) => { if (!wasMondifierKeyOnlyEvent(ev)) { - this.focus(this); + this.focus(); } }, true); - on(this.textarea, 'keydown', function(ev) { + on(this.textarea, 'keydown', (ev) => { this.keyDown(ev); }, true); - on(this.textarea, 'keypress', function(ev) { + on(this.textarea, 'keypress', (ev) => { this.keyPress(ev); // Truncate the textarea's value, since it is not needed - this.value = ''; + this.textarea.value = ''; }, true); on(this.textarea, 'compositionstart', this.compositionHelper.compositionstart.bind(this.compositionHelper)); @@ -1063,20 +1064,21 @@ export class Terminal extends EventEmitter implements ITerminal { // x10 compatibility mode can't send button releases if (!this.x10Mouse) { - on(this.document, 'mouseup', (ev: MouseEvent) => { + const handler = (ev: MouseEvent) => { sendButton(ev); if (this.normalMouse) off(this.document, 'mousemove', sendMove); - off(this.document, 'mouseup', up); + off(this.document, 'mouseup', handler); return this.cancel(ev); - }); + }; + on(this.document, 'mouseup', handler); } return this.cancel(ev); }); - //if (this.normalMouse) { + // if (this.normalMouse) { // on(this.document, 'mousemove', sendMove); - //} + // } on(el, 'wheel', (ev) => { if (!this.mouseEvents) return; @@ -1149,7 +1151,7 @@ export class Terminal extends EventEmitter implements ITerminal { /** * Display the cursor element */ - private showCursor() { + public showCursor() { if (!this.cursorState) { this.cursorState = 1; this.refresh(this.buffer.y, this.buffer.y); @@ -2207,7 +2209,7 @@ export class Terminal extends EventEmitter implements ITerminal { * Emit the 'data' event and populate the given data. * @param {string} data The data to populate in the event. */ - private handler(data: string): void { + public handler(data: string): void { // Prevents all events to pty process if stdin is disabled if (this.options.disableStdin) { return; @@ -2305,7 +2307,7 @@ export class Terminal extends EventEmitter implements ITerminal { this.buffer.tabs[this.buffer.x] = true; } - private cancel(ev: Event, force?: boolean) { + public cancel(ev: Event, force?: boolean) { if (!this.options.cancelEvents && !force) { return; } @@ -2368,7 +2370,7 @@ export class Terminal extends EventEmitter implements ITerminal { * Helpers */ -function globalOn(el: HTMLElement | HTMLElement[], type: string, handler: (event: Event) => any, capture?: boolean) { +function globalOn(el: any, type: string, handler: (event: Event) => any, capture?: boolean) { if (!Array.isArray(el)) { el = [el]; } @@ -2379,8 +2381,8 @@ function globalOn(el: HTMLElement | HTMLElement[], type: string, handler: (event // TODO: Remove once everything is typed const on = globalOn; -function off(el, type, handler, capture) { - el.removeEventListener(type, handler, capture || false); +function off(el, type, handler, capture: boolean = false) { + el.removeEventListener(type, handler, capture); } function inherits(child, parent) { @@ -2392,7 +2394,7 @@ function inherits(child, parent) { } function indexOf(obj, el) { - var i = obj.length; + let i = obj.length; while (i--) { if (obj[i] === el) return i; } @@ -2462,4 +2464,4 @@ function keys(obj) { // Terminal.on = on; // Terminal.off = off; -module.exports = Terminal; +// module.exports = Terminal; From 8903bddb173c89da8f5c0f5435953855ea5696d1 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 5 Aug 2017 02:28:20 -0700 Subject: [PATCH 004/378] Fix a bunch of warnings, remove dead code --- src/Terminal.ts | 96 ++++++++++++++++++++++--------------------------- 1 file changed, 42 insertions(+), 54 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 19f10e7d..509152b9 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -47,36 +47,29 @@ declare var define: any; */ // Let it work inside Node.js for automated testing purposes. -var document = (typeof window != 'undefined') ? window.document : null; +const document = (typeof window !== 'undefined') ? window.document : null; /** * The amount of write requests to queue before sending an XOFF signal to the * pty process. This number must be small in order for ^C and similar sequences * to be responsive. */ -var WRITE_BUFFER_PAUSE_THRESHOLD = 5; +const WRITE_BUFFER_PAUSE_THRESHOLD = 5; /** * The number of writes to perform in a single batch before allowing the * renderer to catch up with a 0ms setTimeout. */ -var WRITE_BATCH_SIZE = 300; +const WRITE_BATCH_SIZE = 300; /** * The time between cursor blinks. This is driven by JS rather than a CSS * animation due to a bug in Chromium that causes it to use excessive CPU time. * See https://github.com/Microsoft/vscode/issues/22900 */ -var CURSOR_BLINK_INTERVAL = 600; - - - - - - - - +const CURSOR_BLINK_INTERVAL = 600; +// TODO: Most of the color code should be removed after truecolor is implemented // Colors 0-15 const tangoColors = [ // dark: @@ -135,13 +128,11 @@ const defaultColors = (function() { const _colors = defaultColors.slice(); const vcolors = (function() { - var out = [] - , colors = defaultColors - , i = 0 - , color; + const out = []; + let color; - for (; i < 256; i++) { - color = parseInt(colors[i].substring(1), 16); + for (let i = 0; i < 256; i++) { + color = parseInt(defaultColors[i].substring(1), 16); out.push([ (color >> 16) & 0xff, (color >> 8) & 0xff, @@ -287,7 +278,7 @@ export class Terminal extends EventEmitter implements ITerminal { private inputHandler: InputHandler; private parser: Parser; private renderer: Renderer; - public selectionManager: SelectionManager;; + public selectionManager: SelectionManager; private linkifier: Linkifier; public buffers: BufferSet; public buffer: Buffer; @@ -757,7 +748,7 @@ export class Terminal extends EventEmitter implements ITerminal { * Automatic focus functionality. * TODO: Default to `false` starting with xterm.js 3.0. */ - if (typeof focus == 'undefined') { + if (typeof focus === 'undefined') { let message = 'You did not pass the `focus` argument in `Terminal.prototype.open()`.\n'; message += 'The `focus` argument now defaults to `true` but starting with xterm.js 3.0 '; @@ -823,14 +814,16 @@ export class Terminal extends EventEmitter implements ITerminal { * BtnCode, EmitButtonCode, EditorButton, SendMousePosition */ public bindMouse() { - var el = this.element, self = this, pressed = 32; + const el = this.element; + const self = this; + let pressed = 32; // mouseup, mousedown, wheel // left click: ^[[M 3<^[[M#3< // wheel up: ^[[M`3> function sendButton(ev) { - var button - , pos; + let button; + let pos; // get the xterm-style button button = getButton(ev); @@ -861,8 +854,8 @@ export class Terminal extends EventEmitter implements ITerminal { // motion example of a left click: // ^[[M 3<^[[M@4<^[[M@5<^[[M@6<^[[M@7<^[[M#7< function sendMove(ev) { - let button = pressed - , pos; + let button = pressed; + let pos; pos = getRawByteCoords(ev, self.rowContainer, self.charMeasure, self.cols, self.rows); if (!pos) return; @@ -1052,7 +1045,7 @@ export class Terminal extends EventEmitter implements ITerminal { sendButton(ev); // fix for odd bug - //if (this.vt200Mouse && !this.normalMouse) { + // if (this.vt200Mouse && !this.normalMouse) { if (this.vt200Mouse) { (ev).overrideType = 'mouseup'; sendButton(ev); @@ -1164,7 +1157,7 @@ export class Terminal extends EventEmitter implements ITerminal { * line. */ public scroll(isWrapped?: boolean): void { - var row; + let row; // Make room for the new row in lines if (this.buffer.lines.length === this.buffer.lines.maxLength) { @@ -1301,10 +1294,9 @@ export class Terminal extends EventEmitter implements ITerminal { } private innerWrite() { - var writeBatch = this.writeBuffer.splice(0, WRITE_BATCH_SIZE); + const writeBatch = this.writeBuffer.splice(0, WRITE_BATCH_SIZE); while (writeBatch.length > 0) { - var data = writeBatch.shift(); - var l = data.length, i = 0, j, cs, ch, code, low, ch_width, row; + const data = writeBatch.shift(); // If XOFF was sent in order to catch up with the pty process, resume it if // the writeBuffer is empty to allow more data to come in. @@ -1321,7 +1313,7 @@ export class Terminal extends EventEmitter implements ITerminal { // middle of parsing escape sequence in two chunks. For some reason the // state of the parser resets to 0 after exiting parser.parse. This change // just sets the state back based on the correct return statement. - var state = this.parser.parse(data); + const state = this.parser.parse(data); this.parser.setState(state); this.updateRange(this.buffer.y); @@ -1329,10 +1321,7 @@ export class Terminal extends EventEmitter implements ITerminal { } if (this.writeBuffer.length > 0) { // Allow renderer to catch up before processing the next batch - var self = this; - setTimeout(function () { - self.innerWrite(); - }, 0); + setTimeout(() => this.innerWrite(), 0); } else { this.writeInProgress = false; } @@ -1412,7 +1401,7 @@ export class Terminal extends EventEmitter implements ITerminal { */ public registerLinkMatcher(regex, handler, options) { if (this.linkifier) { - var matcherId = this.linkifier.registerLinkMatcher(regex, handler, options); + const matcherId = this.linkifier.registerLinkMatcher(regex, handler, options); this.refresh(0, this.rows - 1); return matcherId; } @@ -1525,7 +1514,7 @@ export class Terminal extends EventEmitter implements ITerminal { * @param {KeyboardEvent} ev The keyboard event to be translated to key escape sequence. */ private evaluateKeyEscapeSequence(ev) { - var result = { + const result = { // Whether to cancel event propogation (NOTE: this may not be needed since the event is // canceled at the end of keyDown cancel: false, @@ -1534,7 +1523,7 @@ export class Terminal extends EventEmitter implements ITerminal { // The number of characters to scroll, if this is defined it will cancel the event scrollDisp: undefined }; - var modifiers = ev.shiftKey << 0 | ev.altKey << 1 | ev.ctrlKey << 2 | ev.metaKey << 3; + const modifiers = ev.shiftKey << 0 | ev.altKey << 1 | ev.ctrlKey << 2 | ev.metaKey << 3; switch (ev.keyCode) { case 8: // backspace @@ -1570,7 +1559,7 @@ export class Terminal extends EventEmitter implements ITerminal { // HACK: Make Alt + left-arrow behave like Ctrl + left-arrow: move one word backwards // http://unix.stackexchange.com/a/108106 // macOS uses different escape sequences than linux - if (result.key == C0.ESC + '[1;3D') { + if (result.key === C0.ESC + '[1;3D') { result.key = (this.browser.isMac) ? C0.ESC + 'b' : C0.ESC + '[1;5D'; } } else if (this.applicationCursor) { @@ -1586,7 +1575,7 @@ export class Terminal extends EventEmitter implements ITerminal { // HACK: Make Alt + right-arrow behave like Ctrl + right-arrow: move one word forward // http://unix.stackexchange.com/a/108106 // macOS uses different escape sequences than linux - if (result.key == C0.ESC + '[1;3C') { + if (result.key === C0.ESC + '[1;3C') { result.key = (this.browser.isMac) ? C0.ESC + 'f' : C0.ESC + '[1;5C'; } } else if (this.applicationCursor) { @@ -1601,7 +1590,7 @@ export class Terminal extends EventEmitter implements ITerminal { result.key = C0.ESC + '[1;' + (modifiers + 1) + 'A'; // HACK: Make Alt + up-arrow behave like Ctrl + up-arrow // http://unix.stackexchange.com/a/108106 - if (result.key == C0.ESC + '[1;3A') { + if (result.key === C0.ESC + '[1;3A') { result.key = C0.ESC + '[1;5A'; } } else if (this.applicationCursor) { @@ -1616,7 +1605,7 @@ export class Terminal extends EventEmitter implements ITerminal { result.key = C0.ESC + '[1;' + (modifiers + 1) + 'B'; // HACK: Make Alt + down-arrow behave like Ctrl + down-arrow // http://unix.stackexchange.com/a/108106 - if (result.key == C0.ESC + '[1;3B') { + if (result.key === C0.ESC + '[1;3B') { result.key = C0.ESC + '[1;5B'; } } else if (this.applicationCursor) { @@ -1808,7 +1797,7 @@ export class Terminal extends EventEmitter implements ITerminal { * Set the G level of the terminal * @param g */ - public setgLevel = function(g: number) { + public setgLevel(g: number): void { this.glevel = g; this.charset = this.charsets[g]; } @@ -1818,7 +1807,7 @@ export class Terminal extends EventEmitter implements ITerminal { * @param g * @param charset */ - public setgCharset(g: number, charset: Charset) { + public setgCharset(g: number, charset: Charset): void { this.charsets[g] = charset; if (this.glevel === g) { this.charset = charset; @@ -1832,7 +1821,7 @@ export class Terminal extends EventEmitter implements ITerminal { * @param {KeyboardEvent} ev The keypress event to be handled. */ private keyPress(ev) { - var key; + let key; if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) { return false; @@ -1929,7 +1918,7 @@ export class Terminal extends EventEmitter implements ITerminal { this.setOption('scrollback', y); } - var line + let line , el , i , j @@ -2148,7 +2137,7 @@ export class Terminal extends EventEmitter implements ITerminal { this.buffer.ydisp = 0; this.buffer.ybase = 0; this.buffer.y = 0; - for (var i = 1; i < this.rows; i++) { + for (let i = 1; i < this.rows; i++) { this.buffer.lines.push(this.blankLine()); } this.refresh(0, this.rows - 1); @@ -2266,7 +2255,6 @@ export class Terminal extends EventEmitter implements ITerminal { * Move the cursor up one row, inserting a new blank line if necessary. */ public reverseIndex() { - var j; if (this.buffer.y === this.buffer.scrollTop) { // possibly move the code below to term.reverseScroll(); // test: echo -ne '\e[1;1H\e[44m\eM\e[0m' @@ -2286,10 +2274,10 @@ export class Terminal extends EventEmitter implements ITerminal { public reset() { this.options.rows = this.rows; this.options.cols = this.cols; - var customKeyEventHandler = this.customKeyEventHandler; - var cursorBlinkInterval = this.cursorBlinkInterval; - var inputHandler = this.inputHandler; - var buffers = this.buffers; + const customKeyEventHandler = this.customKeyEventHandler; + const cursorBlinkInterval = this.cursorBlinkInterval; + const inputHandler = this.inputHandler; + const buffers = this.buffers; Terminal.call(this, this.options); this.customKeyEventHandler = customKeyEventHandler; this.cursorBlinkInterval = cursorBlinkInterval; @@ -2319,7 +2307,7 @@ export class Terminal extends EventEmitter implements ITerminal { // Expose to InputHandler // TODO: Revise when truecolor is introduced. public matchColor(r1, g1, b1) { - var hash = (r1 << 16) | (g1 << 8) | b1; + const hash = (r1 << 16) | (g1 << 8) | b1; if (matchColorCache[hash] != null) { return matchColorCache[hash]; @@ -2402,7 +2390,7 @@ function indexOf(obj, el) { } function isThirdLevelShift(term, ev) { - var thirdLevelKey = + const thirdLevelKey = (term.browser.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) || (term.browser.isMSWindows && ev.altKey && ev.ctrlKey && !ev.metaKey); From 7d1c5d1ac6f660c800c9c8de9943cfe9dff41d83 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 5 Aug 2017 02:34:21 -0700 Subject: [PATCH 005/378] Fix tests --- src/Buffer.test.ts | 4 +++- src/BufferSet.test.ts | 4 +++- src/Terminal.ts | 2 +- src/Viewport.test.ts | 4 +++- src/test/escape-sequences-test.js | 2 +- 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Buffer.test.ts b/src/Buffer.test.ts index c4c3a267..9f0d8446 100644 --- a/src/Buffer.test.ts +++ b/src/Buffer.test.ts @@ -14,7 +14,9 @@ describe('Buffer', () => { terminal = { cols: 80, rows: 24, - scrollback: 1000 + options: { + scrollback: 1000 + } }; buffer = new Buffer(terminal); }); diff --git a/src/BufferSet.test.ts b/src/BufferSet.test.ts index 2101fbc1..19d943b9 100644 --- a/src/BufferSet.test.ts +++ b/src/BufferSet.test.ts @@ -14,7 +14,9 @@ describe('BufferSet', () => { terminal = { cols: 80, rows: 24, - scrollback: 1000 + options: { + scrollback: 1000 + } }; bufferSet = new BufferSet(terminal); }); diff --git a/src/Terminal.ts b/src/Terminal.ts index 509152b9..839ad568 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -779,7 +779,7 @@ export class Terminal extends EventEmitter implements ITerminal { * @param {string} addon The name of the addon to load * @static */ - public loadAddon(addon, callback) { + public static loadAddon(addon, callback) { if (typeof exports === 'object' && typeof module === 'object') { // CommonJS return require('./addons/' + addon + '/' + addon); diff --git a/src/Viewport.test.ts b/src/Viewport.test.ts index 6d09ea86..7c89da29 100644 --- a/src/Viewport.test.ts +++ b/src/Viewport.test.ts @@ -26,7 +26,9 @@ describe('Viewport', () => { height: 0 } }, - scrollback: 10 + options: { + scrollback: 10 + } }; terminal.buffers = new BufferSet(terminal); terminal.buffer = terminal.buffers.active; diff --git a/src/test/escape-sequences-test.js b/src/test/escape-sequences-test.js index b4a53a17..d0447451 100644 --- a/src/test/escape-sequences-test.js +++ b/src/test/escape-sequences-test.js @@ -80,7 +80,7 @@ describe('xterm output comparison', function() { var xterm; beforeEach(function () { - xterm = new Terminal(COLS, ROWS); + xterm = new Terminal({ cols: COLS, rows: ROWS }); xterm.refresh = function() {}; xterm.viewport = { syncScrollArea: function() {} From e6b431d92f20a48421e5721f77c7f33f4906989b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 5 Aug 2017 02:42:49 -0700 Subject: [PATCH 006/378] Add one-variable-per-declaration tslint rule Multiple declarations per line makes it more difficult to read what exactly is declared and generally makes code more verbose and less likely to fill in types --- src/InputHandler.ts | 66 +++++++++++++++------------------- src/Parser.ts | 7 +++- src/Terminal.ts | 32 ++++++++--------- src/handlers/Clipboard.test.ts | 6 ++-- tslint.json | 4 +++ 5 files changed, 57 insertions(+), 58 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 64da3b3f..e119df32 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -189,14 +189,12 @@ export class InputHandler implements IInputHandler { * Insert Ps (Blank) Character(s) (default = 1) (ICH). */ public insertChars(params: number[]): void { - let param, row, j, ch; - - param = params[0]; + let param = params[0]; if (param < 1) param = 1; - row = this._terminal.buffer.y + this._terminal.buffer.ybase; - j = this._terminal.buffer.x; - ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm + const row = this._terminal.buffer.y + this._terminal.buffer.ybase; + let j = this._terminal.buffer.x; + const ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm while (param-- && j < this._terminal.cols) { this._terminal.buffer.lines.get(row).splice(j++, 0, ch); @@ -325,9 +323,8 @@ export class InputHandler implements IInputHandler { * Cursor Position [row;column] (default = [1,1]) (CUP). */ public cursorPosition(params: number[]): void { - let row, col; - - row = params[0] - 1; + let col: number; + let row: number = params[0] - 1; if (params.length >= 2) { col = params[1] - 1; @@ -439,14 +436,13 @@ export class InputHandler implements IInputHandler { * Insert Ps Line(s) (default = 1) (IL). */ public insertLines(params: number[]): void { - let param, row, j; - - param = params[0]; + let param: number = params[0]; if (param < 1) { param = 1; } - row = this._terminal.buffer.y + this._terminal.buffer.ybase; + let row: number = this._terminal.buffer.y + this._terminal.buffer.ybase; + let j: number; j = this._terminal.rows - 1 - this._terminal.buffer.scrollBottom; j = this._terminal.rows - 1 + this._terminal.buffer.ybase - j + 1; @@ -475,14 +471,13 @@ export class InputHandler implements IInputHandler { * Delete Ps Line(s) (default = 1) (DL). */ public deleteLines(params: number[]): void { - let param, row, j; - - param = params[0]; + let param = params[0]; if (param < 1) { param = 1; } - row = this._terminal.buffer.y + this._terminal.buffer.ybase; + const row: number = this._terminal.buffer.y + this._terminal.buffer.ybase; + let j: number; j = this._terminal.rows - 1 - this._terminal.buffer.scrollBottom; j = this._terminal.rows - 1 + this._terminal.buffer.ybase - j; @@ -509,15 +504,13 @@ export class InputHandler implements IInputHandler { * Delete Ps Character(s) (default = 1) (DCH). */ public deleteChars(params: number[]): void { - let param, row, ch; - - param = params[0]; + let param: number = params[0]; if (param < 1) { param = 1; } - row = this._terminal.buffer.y + this._terminal.buffer.ybase; - ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm + const row = this._terminal.buffer.y + this._terminal.buffer.ybase; + const ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm while (param--) { this._terminal.buffer.lines.get(row).splice(this._terminal.buffer.x, 1); @@ -558,16 +551,14 @@ export class InputHandler implements IInputHandler { * Erase Ps Character(s) (default = 1) (ECH). */ public eraseChars(params: number[]): void { - let param, row, j, ch; - - param = params[0]; + let param = params[0]; if (param < 1) { param = 1; } - row = this._terminal.buffer.y + this._terminal.buffer.ybase; - j = this._terminal.buffer.x; - ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm + const row = this._terminal.buffer.y + this._terminal.buffer.ybase; + let j = this._terminal.buffer.x; + const ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm while (param-- && j < this._terminal.cols) { this._terminal.buffer.lines.get(row)[j++] = ch; @@ -619,9 +610,9 @@ export class InputHandler implements IInputHandler { * CSI Ps b Repeat the preceding graphic character Ps times (REP). */ public repeatPrecedingCharacter(params: number[]): void { - let param = params[0] || 1 - , line = this._terminal.buffer.lines.get(this._terminal.buffer.ybase + this._terminal.buffer.y) - , ch = line[this._terminal.buffer.x - 1] || [this._terminal.defAttr, ' ', 1]; + let param = params[0] || 1; + const line = this._terminal.buffer.lines.get(this._terminal.buffer.ybase + this._terminal.buffer.y); + const ch = line[this._terminal.buffer.x - 1] || [this._terminal.defAttr, ' ', 1]; while (param--) { line[this._terminal.buffer.x++] = ch; @@ -1203,14 +1194,13 @@ export class InputHandler implements IInputHandler { return; } - let l = params.length - , i = 0 - , flags = this._terminal.curAttr >> 18 - , fg = (this._terminal.curAttr >> 9) & 0x1ff - , bg = this._terminal.curAttr & 0x1ff - , p; + const l = params.length; + let flags = this._terminal.curAttr >> 18; + let fg = (this._terminal.curAttr >> 9) & 0x1ff; + let bg = this._terminal.curAttr & 0x1ff; + let p; - for (; i < l; i++) { + for (let i = 0; i < l; i++) { p = params[i]; if (p >= 30 && p <= 37) { // fg color 8 diff --git a/src/Parser.ts b/src/Parser.ts index 1c2fb6b0..bec0af7b 100644 --- a/src/Parser.ts +++ b/src/Parser.ts @@ -182,7 +182,12 @@ export class Parser { * @param data The data to parse. */ public parse(data: string): ParserState { - let l = data.length, j, cs, ch, code, low; + const l = data.length; + let j; + let cs; + let ch; + let code; + let low; if (this._terminal.debug) { this._terminal.log('data: ' + data); diff --git a/src/Terminal.ts b/src/Terminal.ts index 839ad568..e1633033 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -95,9 +95,9 @@ const tangoColors = [ // Colors 0-15 + 16-255 // Much thanks to TooTallNate for writing this. const defaultColors = (function() { - let colors = tangoColors.slice() - , r = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff] - , i; + let colors = tangoColors.slice(); + let r = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]; + let i; // 16-231 i = 0; @@ -971,11 +971,11 @@ export class Terminal extends EventEmitter implements ITerminal { } function getButton(ev) { - let button - , shift - , meta - , ctrl - , mod; + let button; + let shift; + let meta; + let ctrl; + let mod; // two low bits: // 0 = left @@ -1918,12 +1918,12 @@ export class Terminal extends EventEmitter implements ITerminal { this.setOption('scrollback', y); } - let line - , el - , i - , j - , ch - , addToY; + let line; + let el; + let i; + let j; + let ch; + let addToY; if (x === this.cols && y === this.rows) { // Check if we still need to measure the char size (fixes #785). @@ -2426,8 +2426,8 @@ function wasMondifierKeyOnlyEvent(ev) { function keys(obj) { if (Object.keys) return Object.keys(obj); - let key, keys = []; - for (key in obj) { + const keys = []; + for (let key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { keys.push(key); } diff --git a/src/handlers/Clipboard.test.ts b/src/handlers/Clipboard.test.ts index 740bee3d..a91ab87c 100644 --- a/src/handlers/Clipboard.test.ts +++ b/src/handlers/Clipboard.test.ts @@ -4,9 +4,9 @@ import * as Clipboard from './Clipboard'; describe('evaluatePastedTextProcessing', function () { it('should replace carriage return + line feed with line feed on windows', function () { - const pastedText = 'foo\r\nbar\r\n', - processedText = Clipboard.prepareTextForTerminal(pastedText, false), - windowsProcessedText = Clipboard.prepareTextForTerminal(pastedText, true); + const pastedText = 'foo\r\nbar\r\n'; + const processedText = Clipboard.prepareTextForTerminal(pastedText, false); + const windowsProcessedText = Clipboard.prepareTextForTerminal(pastedText, true); assert.equal(processedText, 'foo\r\nbar\r\n'); assert.equal(windowsProcessedText, 'foo\rbar\r'); diff --git a/tslint.json b/tslint.json index d571d9c5..f98fd4c4 100644 --- a/tslint.json +++ b/tslint.json @@ -13,6 +13,10 @@ "no-eval": true, "no-internal-module": true, "no-trailing-whitespace": true, + "one-variable-per-declaration": [ + true, + true + ], "no-unsafe-finally": true, "no-var-keyword": true, "quotemark": [ From da50e33aa8c9383f630c83c3defb26696292df0b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 5 Aug 2017 02:50:44 -0700 Subject: [PATCH 007/378] Finish types on Terminal's properties --- src/Terminal.ts | 35 +++++++++++++---------------------- 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index e1633033..ebb0c687 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -163,13 +163,6 @@ const DEFAULT_OPTIONS: TerminalOptions = { // focusKeys: false, }; - - - - - - - export class Terminal extends EventEmitter implements ITerminal { public textarea: HTMLTextAreaElement; public element: HTMLElement; @@ -223,24 +216,22 @@ export class Terminal extends EventEmitter implements ITerminal { private charsets: Charset[] = [null]; // mouse properties - private decLocator; - private x10Mouse; - private vt200Mouse; - private vt300Mouse; - private normalMouse; - private mouseEvents; - private sendFocus; - private utfMouse; - private sgrMouse; - private urxvtMouse; + private decLocator: boolean; // This is unstable and never set + private x10Mouse: boolean; + private vt200Mouse: boolean; + private vt300Mouse: boolean; // This is unstable and never set + private normalMouse: boolean; + private mouseEvents: boolean; + private sendFocus: boolean; + private utfMouse: boolean; + private sgrMouse: boolean; + private urxvtMouse: boolean; // misc public children: HTMLElement[]; - private refreshStart; - private refreshEnd; - private savedX; - private savedY; - private savedCols; + private refreshStart: number; + private refreshEnd: number; + private savedCols: boolean; // stream private readable: boolean = true; From 0b5ff47cc0f92d5fba77baa85b30adb74d37aa8e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 5 Aug 2017 02:57:34 -0700 Subject: [PATCH 008/378] Give terminal options a proper interface --- src/Interfaces.ts | 21 +++++++++++++++++++++ src/Terminal.ts | 19 ++++++++++--------- src/Types.ts | 2 -- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/Interfaces.ts b/src/Interfaces.ts index c24aa2ef..98c7626a 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -50,6 +50,27 @@ export interface ITerminal { showCursor(): void; } +export interface ITerminalOptions { + cancelEvents?: boolean; + colors?: string[]; + cols?: number; + convertEol?: boolean; + cursorBlink?: boolean; + cursorStyle?: string; + debug?: boolean; + disableStdin?: boolean; + geometry?: [number, number]; + handler?: (data: string) => void; + popOnBell?: boolean; + rows?: number; + screenKeys?: boolean; + scrollback?: number; + tabStopWidth?: number; + termName?: string; + useFlowControl?: boolean; + visualBell?: boolean; +} + export interface IBuffer { lines: ICircularList<[number, string, number][]>; ydisp: number; diff --git a/src/Terminal.ts b/src/Terminal.ts index ebb0c687..79b5972f 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -29,8 +29,8 @@ import * as Mouse from './utils/Mouse'; import { CHARSETS } from './Charsets'; import { getRawByteCoords } from './utils/Mouse'; import { translateBufferLineToString } from './utils/BufferLine'; -import { TerminalOptions, CustomKeyEventHandler, Charset } from './Types'; -import { ITerminal, IBrowser } from './Interfaces'; +import { CustomKeyEventHandler, Charset } from './Types'; +import { ITerminal, IBrowser, ITerminalOptions } from './Interfaces'; // Declare for RequireJS in loadAddon declare var define: any; @@ -71,7 +71,7 @@ const CURSOR_BLINK_INTERVAL = 600; // TODO: Most of the color code should be removed after truecolor is implemented // Colors 0-15 -const tangoColors = [ +const tangoColors: string[] = [ // dark: '#2e3436', '#cc0000', @@ -94,7 +94,7 @@ const tangoColors = [ // Colors 0-15 + 16-255 // Much thanks to TooTallNate for writing this. -const defaultColors = (function() { +const defaultColors: string[] = (function() { let colors = tangoColors.slice(); let r = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]; let i; @@ -125,9 +125,9 @@ const defaultColors = (function() { return colors; })(); -const _colors = defaultColors.slice(); +const _colors: string[] = defaultColors.slice(); -const vcolors = (function() { +const vcolors: number[][] = (function() { const out = []; let color; @@ -143,7 +143,7 @@ const vcolors = (function() { return out; })(); -const DEFAULT_OPTIONS: TerminalOptions = { +const DEFAULT_OPTIONS: ITerminalOptions = { colors: defaultColors, convertEol: false, termName: 'xterm', @@ -186,7 +186,7 @@ export class Terminal extends EventEmitter implements ITerminal { public browser: IBrowser = Browser; // TODO: Options should be private, remove from interface in favor of getOption - public options: TerminalOptions; + public options: ITerminalOptions; private colors: any; // TODO: This can be changed to an enum or boolean, 0 and 1 seem to be the only options @@ -294,7 +294,7 @@ export class Terminal extends EventEmitter implements ITerminal { * @alias module:xterm/src/xterm */ constructor( - options: any = {} + options: ITerminalOptions = {} ) { super(); @@ -2269,6 +2269,7 @@ export class Terminal extends EventEmitter implements ITerminal { const cursorBlinkInterval = this.cursorBlinkInterval; const inputHandler = this.inputHandler; const buffers = this.buffers; + // TODO: Need to make sure this still works Terminal.call(this, this.options); this.customKeyEventHandler = customKeyEventHandler; this.cursorBlinkInterval = cursorBlinkInterval; diff --git a/src/Types.ts b/src/Types.ts index 9e4b1e1a..34c6d94a 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -13,7 +13,5 @@ export type LinkMatcher = { export type LinkMatcherHandler = (event: MouseEvent, uri: string) => boolean | void; export type LinkMatcherValidationCallback = (uri: string, element: HTMLElement, callback: (isValid: boolean) => void) => void; -// TODO: Make this type more specific -export type TerminalOptions = {[key: string]: any}; export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean; export type Charset = {[key: string]: string}; From ad25905783bc8de772139c1d11b89687fc5186a1 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 5 Aug 2017 11:20:39 -0700 Subject: [PATCH 009/378] Fix reset --- src/Interfaces.ts | 4 +- src/Terminal.ts | 155 +++++++++++++++++++++++++++------------------- 2 files changed, 95 insertions(+), 64 deletions(-) diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 98c7626a..8851edac 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -3,7 +3,7 @@ */ import { LinkMatcherOptions } from './Interfaces'; -import { LinkMatcherHandler, LinkMatcherValidationCallback, TerminalOptions } from './Types'; +import { LinkMatcherHandler, LinkMatcherValidationCallback } from './Types'; export interface IBrowser { isNode: boolean; @@ -32,7 +32,7 @@ export interface ITerminal { cursorHidden: boolean; cursorState: number; defAttr: number; - options: TerminalOptions; + options: ITerminalOptions; buffers: IBufferSet; buffer: IBuffer; diff --git a/src/Terminal.ts b/src/Terminal.ts index 79b5972f..a7aabcd7 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -190,30 +190,30 @@ export class Terminal extends EventEmitter implements ITerminal { private colors: any; // TODO: This can be changed to an enum or boolean, 0 and 1 seem to be the only options - public cursorState: number = 0; - public cursorHidden: boolean = false; + public cursorState: number; + public cursorHidden: boolean; private convertEol: boolean; // TODO: This is the data queue for send, improve name and documentation - private queue: string = ''; - private customKeyEventHandler: CustomKeyEventHandler = null; + private queue: string; + private customKeyEventHandler: CustomKeyEventHandler; // The ID from a setInterval that tracks the blink animation. This animation // is done in JS due to a Chromium bug with CSS animations that thrashed the // CPU. - private cursorBlinkInterval: NodeJS.Timer = null; + private cursorBlinkInterval: NodeJS.Timer; // modes - private applicationKeypad: boolean = false; - private applicationCursor: boolean = false; - private originMode: boolean = false; - private insertMode: boolean = false; - private wraparoundMode: boolean = true; // defaults: xterm - true, vt100 - false + private applicationKeypad: boolean; + private applicationCursor: boolean; + private originMode: boolean; + private insertMode: boolean; + private wraparoundMode: boolean; // defaults: xterm - true, vt100 - false // charset // The current charset - private charset: Charset = null; - private gcharset: number = null; - private glevel: number = 0; - private charsets: Charset[] = [null]; + private charset: Charset; + private gcharset: number; + private glevel: number; + private charsets: Charset[]; // mouse properties private decLocator: boolean; // This is unstable and never set @@ -234,20 +234,20 @@ export class Terminal extends EventEmitter implements ITerminal { private savedCols: boolean; // stream - private readable: boolean = true; - private writable: boolean = true; + private readable: boolean; + private writable: boolean; - public defAttr: number = (0 << 18) | (257 << 9) | (256 << 0); - public curAttr: number = (0 << 18) | (257 << 9) | (256 << 0); + public defAttr: number; + public curAttr: number; - public params: (string | number)[] = []; - public currentParam: string | number = 0; - public prefix: string = ''; - public postfix: string = ''; + public params: (string | number)[]; + public currentParam: string | number; + public prefix: string; + public postfix: string; // user input states - public writeBuffer: string[] = []; - private writeInProgress: boolean = false; + public writeBuffer: string[]; + private writeInProgress: boolean; /** * Whether _xterm.js_ sent XOFF in order to catch up with the pty process. @@ -255,16 +255,16 @@ export class Terminal extends EventEmitter implements ITerminal { * XOFF via ^S that it will not automatically resume when the writeBuffer goes * below threshold. */ - private xoffSentToCatchUp: boolean = false; + private xoffSentToCatchUp: boolean; /** Whether writing has been stopped as a result of XOFF */ - private writeStopped: boolean = false; + private writeStopped: boolean; // leftover surrogate high from previous write invocation - private surrogate_high: string = ''; + private surrogate_high: string; // Store if user went browsing history in scrollback - private userScrolling: boolean = false; + private userScrolling: boolean; private inputHandler: InputHandler; private parser: Parser; @@ -297,56 +297,88 @@ export class Terminal extends EventEmitter implements ITerminal { options: ITerminalOptions = {} ) { super(); + this.options = options; + this.setup(); + } - let self: any = this; - - // TODO: Can this be removed? - if (!(this instanceof Terminal)) { - return new Terminal({ - cols: arguments[0], - rows: arguments[1], - handler: arguments[2] - }); - } - - Object.keys(DEFAULT_OPTIONS).forEach(function(key) { - if (options[key] == null) { - options[key] = DEFAULT_OPTIONS[key]; + private setup(): void { + Object.keys(DEFAULT_OPTIONS).forEach((key) => { + if (this.options[key] == null) { + this.options[key] = DEFAULT_OPTIONS[key]; } // TODO: We should move away from duplicate options on the Terminal object - self[key] = options[key]; + self[key] = this.options[key]; }); - if (options.colors.length === 8) { - options.colors = options.colors.concat(_colors.slice(8)); - } else if (options.colors.length === 16) { - options.colors = options.colors.concat(_colors.slice(16)); - } else if (options.colors.length === 10) { - options.colors = options.colors.slice(0, -2).concat( - _colors.slice(8, -2), options.colors.slice(-2)); - } else if (options.colors.length === 18) { - options.colors = options.colors.concat( - _colors.slice(16, -2), options.colors.slice(-2)); + if (this.options.colors.length === 8) { + this.options.colors = this.options.colors.concat(_colors.slice(8)); + } else if (this.options.colors.length === 16) { + this.options.colors = this.options.colors.concat(_colors.slice(16)); + } else if (this.options.colors.length === 10) { + this.options.colors = this.options.colors.slice(0, -2).concat( + _colors.slice(8, -2), this.options.colors.slice(-2)); + } else if (this.options.colors.length === 18) { + this.options.colors = this.options.colors.concat( + _colors.slice(16, -2), this.options.colors.slice(-2)); } - this.options = options; - this.colors = options.colors; + this.colors = this.options.colors; // this.context = options.context || window; // this.document = options.document || document; // TODO: WHy not document.body? this.parent = document ? document.getElementsByTagName('body')[0] : null; - this.cols = options.cols || options.geometry[0]; - this.rows = options.rows || options.geometry[1]; + this.cols = this.options.cols || this.options.geometry[0]; + this.rows = this.options.rows || this.options.geometry[1]; this.geometry = [this.cols, this.rows]; - if (options.handler) { - this.on('data', options.handler); + if (this.options.handler) { + this.on('data', this.options.handler); } + this.cursorState = 0; + this.cursorHidden = false; + this.queue = ''; + this.customKeyEventHandler = null; + this.cursorBlinkInterval = null; + + // modes + this.applicationKeypad = false; + this.applicationCursor = false; + this.originMode = false; + this.insertMode = false; + this.wraparoundMode = true; // defaults: xterm - true, vt100 - false + + // charset + this.charset = null; + this.gcharset = null; + this.glevel = 0; + // TODO: Can this be just []? + this.charsets = [null]; + + this.readable = true; + this.writable = true; + + this.defAttr = (0 << 18) | (257 << 9) | (256 << 0); + this.curAttr = (0 << 18) | (257 << 9) | (256 << 0); + + this.params = []; + this.currentParam = 0; + this.prefix = ''; + this.postfix = ''; + + // user input states + this.writeBuffer = []; + this.writeInProgress = false; + + this.xoffSentToCatchUp = false; + this.writeStopped = false; + this.surrogate_high = ''; + this.userScrolling = false; + this.inputHandler = new InputHandler(this); this.parser = new Parser(this.inputHandler, this); - // Reuse renderer if the Terminal is being recreated via a Terminal.reset call. + // Reuse renderer if the Terminal is being recreated via a reset call. this.renderer = this.renderer || null; this.selectionManager = this.selectionManager || null; this.linkifier = this.linkifier || new Linkifier(); @@ -2269,8 +2301,7 @@ export class Terminal extends EventEmitter implements ITerminal { const cursorBlinkInterval = this.cursorBlinkInterval; const inputHandler = this.inputHandler; const buffers = this.buffers; - // TODO: Need to make sure this still works - Terminal.call(this, this.options); + this.setup(); this.customKeyEventHandler = customKeyEventHandler; this.cursorBlinkInterval = cursorBlinkInterval; this.inputHandler = inputHandler; From 8a0bcf6b792dc3dd74c726978bdc2dab972559e6 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 5 Aug 2017 11:28:24 -0700 Subject: [PATCH 010/378] Clean up --- src/Terminal.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index a7aabcd7..6cc8864e 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -168,8 +168,9 @@ export class Terminal extends EventEmitter implements ITerminal { public element: HTMLElement; public rowContainer: HTMLElement; - // TODO: Do we need a reference to body? There is also a body variable? - // The body of the terminal + /** + * The HTMLElement that the terminal is created in, set by Terminal.open. + */ private parent: HTMLElement; private context: Window; private document: Document; @@ -307,7 +308,7 @@ export class Terminal extends EventEmitter implements ITerminal { this.options[key] = DEFAULT_OPTIONS[key]; } // TODO: We should move away from duplicate options on the Terminal object - self[key] = this.options[key]; + this[key] = this.options[key]; }); if (this.options.colors.length === 8) { @@ -326,7 +327,7 @@ export class Terminal extends EventEmitter implements ITerminal { // this.context = options.context || window; // this.document = options.document || document; // TODO: WHy not document.body? - this.parent = document ? document.getElementsByTagName('body')[0] : null; + this.parent = document ? document.body : null; this.cols = this.options.cols || this.options.geometry[0]; this.rows = this.options.rows || this.options.geometry[1]; @@ -671,7 +672,7 @@ export class Terminal extends EventEmitter implements ITerminal { // Grab global elements this.context = this.parent.ownerDocument.defaultView; this.document = this.parent.ownerDocument; - this.body = this.document.getElementsByTagName('body')[0]; + this.body = this.document.body; // Create main element container this.element = this.document.createElement('div'); @@ -763,8 +764,7 @@ export class Terminal extends EventEmitter implements ITerminal { // Setup loop that draws to screen this.refresh(0, this.rows - 1); - // Initialize global actions that - // need to be taken on the document. + // Initialize global actions that need to be taken on the document. this.initGlobal(); /** From aee474b53e36f559bbf854350018b54576a0905a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 5 Aug 2017 12:57:29 -0700 Subject: [PATCH 011/378] Type InputHandler._terminal --- src/Buffer.ts | 4 +- src/InputHandler.test.ts | 5 ++- src/InputHandler.ts | 20 ++++----- src/Interfaces.ts | 80 ++++++++++++++++++++++++++++++++-- src/SelectionManager.ts | 4 +- src/Terminal.ts | 92 +++++++++++++++++++--------------------- src/Viewport.ts | 4 +- 7 files changed, 139 insertions(+), 70 deletions(-) diff --git a/src/Buffer.ts b/src/Buffer.ts index b76cdc4d..dc0bc24f 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -2,7 +2,7 @@ * @license MIT */ -import { ITerminal } from './Interfaces'; +import { ITerminal, IBuffer } from './Interfaces'; import { CircularList } from './utils/CircularList'; /** @@ -12,7 +12,7 @@ import { CircularList } from './utils/CircularList'; * - cursor position * - scroll position */ -export class Buffer { +export class Buffer implements IBuffer { public lines: CircularList<[number, string, number][]>; public savedY: number; diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index 158c07c9..95624296 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -5,7 +5,8 @@ import { wcwidth } from './InputHandler'; describe('InputHandler', () => { describe('save and restore cursor', () => { let terminal = { buffer: { x: 1, y: 2 } }; - let inputHandler = new InputHandler(terminal); + // TODO: Create proper mock IInputHandlingTerminal test util object + let inputHandler = new InputHandler(terminal); // Save cursor position inputHandler.saveCursor([]); assert.equal(terminal.buffer.x, 1); @@ -24,7 +25,7 @@ describe('InputHandler', () => { let terminal = { setOption: (option, value) => options[option] = value }; - let inputHandler = new InputHandler(terminal); + let inputHandler = new InputHandler(terminal); inputHandler.setCursorStyle([0]); assert.equal(options['cursorStyle'], 'block'); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index e119df32..8f566cd9 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -2,7 +2,7 @@ * @license MIT */ -import { IInputHandler, ITerminal } from './Interfaces'; +import { IInputHandler, ITerminal, IInputHandlingTerminal } from './Interfaces'; import { C0 } from './EscapeSequences'; import { DEFAULT_CHARSET } from './Charsets'; @@ -15,7 +15,7 @@ import { DEFAULT_CHARSET } from './Charsets'; */ export class InputHandler implements IInputHandler { // TODO: We want to type _terminal when it's pulled into TS - constructor(private _terminal: any) { } + constructor(private _terminal: IInputHandlingTerminal) { } public addChar(char: string, code: number): void { if (char >= ' ') { @@ -61,7 +61,7 @@ export class InputHandler implements IInputHandler { } else { // The line already exists (eg. the initial viewport), mark it as a // wrapped line - this._terminal.buffer.lines.get(this._terminal.buffer.y).isWrapped = true; + (this._terminal.buffer.lines.get(this._terminal.buffer.y)).isWrapped = true; } } else { if (ch_width === 2) // FIXME: check for xterm behavior @@ -105,12 +105,12 @@ export class InputHandler implements IInputHandler { * Bell (Ctrl-G). */ public bell(): void { - if (!this._terminal.visualBell) { + if (!this._terminal.options.visualBell) { return; } this._terminal.element.style.borderColor = 'white'; setTimeout(() => this._terminal.element.style.borderColor = '', 10); - if (this._terminal.popOnBell) { + if (this._terminal.options.popOnBell) { this._terminal.focus(); } } @@ -194,7 +194,7 @@ export class InputHandler implements IInputHandler { const row = this._terminal.buffer.y + this._terminal.buffer.ybase; let j = this._terminal.buffer.x; - const ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm + const ch: [number, string, number] = [this._terminal.eraseAttr(), ' ', 1]; // xterm while (param-- && j < this._terminal.cols) { this._terminal.buffer.lines.get(row).splice(j++, 0, ch); @@ -510,7 +510,7 @@ export class InputHandler implements IInputHandler { } const row = this._terminal.buffer.y + this._terminal.buffer.ybase; - const ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm + const ch: [number, string, number] = [this._terminal.eraseAttr(), ' ', 1]; // xterm while (param--) { this._terminal.buffer.lines.get(row).splice(this._terminal.buffer.x, 1); @@ -558,7 +558,7 @@ export class InputHandler implements IInputHandler { const row = this._terminal.buffer.y + this._terminal.buffer.ybase; let j = this._terminal.buffer.x; - const ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm + const ch: [number, string, number] = [this._terminal.eraseAttr(), ' ', 1]; // xterm while (param-- && j < this._terminal.cols) { this._terminal.buffer.lines.get(row)[j++] = ch; @@ -858,7 +858,7 @@ export class InputHandler implements IInputHandler { this._terminal.insertMode = true; break; case 20: - // this._terminal.convertEol = true; + // this._t.convertEol = true; break; } } else if (this._terminal.prefix === '?') { @@ -1050,7 +1050,7 @@ export class InputHandler implements IInputHandler { this._terminal.insertMode = false; break; case 20: - // this._terminal.convertEol = false; + // this._t.convertEol = false; break; } } else if (this._terminal.prefix === '?') { diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 8851edac..b61c0057 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -3,7 +3,7 @@ */ import { LinkMatcherOptions } from './Interfaces'; -import { LinkMatcherHandler, LinkMatcherValidationCallback } from './Types'; +import { LinkMatcherHandler, LinkMatcherValidationCallback, Charset } from './Types'; export interface IBrowser { isNode: boolean; @@ -17,7 +17,7 @@ export interface IBrowser { isMSWindows: boolean; } -export interface ITerminal { +export interface ITerminal extends IEventEmitter { element: HTMLElement; rowContainer: HTMLElement; selectionContainer: HTMLElement; @@ -45,11 +45,73 @@ export interface ITerminal { scrollDisp(disp: number, suppressScrollEvent: boolean); cancel(ev: Event, force?: boolean); log(text: string): void; - emit(event: string, data: any); reset(): void; showCursor(): 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: Charset; + gcharset: number; + glevel: number; + charsets: Charset[]; + applicationKeypad: boolean; + applicationCursor: boolean; + originMode: boolean; + insertMode: boolean; + wraparoundMode: 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; + + focus(): void; + convertEol: boolean; + updateRange(y: number): void; + scroll(isWrapped?: boolean): void; + nextStop(x?: number): number; + setgLevel(g: number): void; + eraseAttr(): any; + eraseRight(x: number, y: number): void; + eraseLine(y: number): void; + eraseLeft(x: number, y: number): void; + blankLine(cur?: boolean, isWrapped?: boolean): [number, string, number][]; + prevStop(x?: number): number; + is(term: string): boolean; + send(data: string): void; + setgCharset(g: number, charset: Charset): 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, g1, b1): any; + error(text: string, data?: any): void; + setOption(key: string, value: any): void; +} + export interface ITerminalOptions { cancelEvents?: boolean; colors?: string[]; @@ -78,6 +140,10 @@ export interface IBuffer { y: number; x: number; tabs: any; + scrollBottom: number; + scrollTop: number; + savedY: number; + savedX: number; } export interface IBufferSet { @@ -89,11 +155,18 @@ export interface IBufferSet { activateAltBuffer(): void; } +export interface IViewport { + syncScrollArea(): void; +} + export interface ISelectionManager { selectionText: string; selectionStart: [number, number]; selectionEnd: [number, number]; + disable(): void; + enable(): void; + setBuffer(buffer: ICircularList<[number, string, number][]>): void; setSelection(row: number, col: number, length: number); } @@ -127,6 +200,7 @@ export interface ICircularList extends IEventEmitter { export interface IEventEmitter { on(type, listener): void; off(type, listener): void; + emit(type: string, data?: any): void; } export interface LinkMatcherOptions { diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index f6fb44da..bf594f70 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -133,7 +133,7 @@ export class SelectionManager extends EventEmitter { * Disables the selection manager. This is useful for when terminal mouse * are enabled. */ - public disable() { + public disable(): void { this.clearSelection(); this._enabled = false; } @@ -141,7 +141,7 @@ export class SelectionManager extends EventEmitter { /** * Enable the selection manager. */ - public enable() { + public enable(): void { this._enabled = true; } diff --git a/src/Terminal.ts b/src/Terminal.ts index 6cc8864e..df313ee3 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -30,7 +30,7 @@ import { CHARSETS } from './Charsets'; import { getRawByteCoords } from './utils/Mouse'; import { translateBufferLineToString } from './utils/BufferLine'; import { CustomKeyEventHandler, Charset } from './Types'; -import { ITerminal, IBrowser, ITerminalOptions } from './Interfaces'; +import { ITerminal, IBrowser, ITerminalOptions, IInputHandlingTerminal } from './Interfaces'; // Declare for RequireJS in loadAddon declare var define: any; @@ -163,7 +163,7 @@ const DEFAULT_OPTIONS: ITerminalOptions = { // focusKeys: false, }; -export class Terminal extends EventEmitter implements ITerminal { +export class Terminal extends EventEmitter implements ITerminal, IInputHandlingTerminal { public textarea: HTMLTextAreaElement; public element: HTMLElement; public rowContainer: HTMLElement; @@ -193,7 +193,7 @@ export class Terminal extends EventEmitter implements ITerminal { // TODO: This can be changed to an enum or boolean, 0 and 1 seem to be the only options public cursorState: number; public cursorHidden: boolean; - private convertEol: boolean; + public convertEol: boolean; // TODO: This is the data queue for send, improve name and documentation private queue: string; private customKeyEventHandler: CustomKeyEventHandler; @@ -203,36 +203,36 @@ export class Terminal extends EventEmitter implements ITerminal { private cursorBlinkInterval: NodeJS.Timer; // modes - private applicationKeypad: boolean; - private applicationCursor: boolean; - private originMode: boolean; - private insertMode: boolean; - private wraparoundMode: boolean; // defaults: xterm - true, vt100 - false + public applicationKeypad: boolean; + public applicationCursor: boolean; + public originMode: boolean; + public insertMode: boolean; + public wraparoundMode: boolean; // defaults: xterm - true, vt100 - false // charset // The current charset - private charset: Charset; - private gcharset: number; - private glevel: number; - private charsets: Charset[]; + public charset: Charset; + public gcharset: number; + public glevel: number; + public charsets: Charset[]; // mouse properties private decLocator: boolean; // This is unstable and never set - private x10Mouse: boolean; - private vt200Mouse: boolean; + public x10Mouse: boolean; + public vt200Mouse: boolean; private vt300Mouse: boolean; // This is unstable and never set - private normalMouse: boolean; - private mouseEvents: boolean; - private sendFocus: boolean; - private utfMouse: boolean; - private sgrMouse: boolean; - private urxvtMouse: boolean; + public normalMouse: boolean; + public mouseEvents: boolean; + public sendFocus: boolean; + public utfMouse: boolean; + public sgrMouse: boolean; + public urxvtMouse: boolean; // misc public children: HTMLElement[]; private refreshStart: number; private refreshEnd: number; - private savedCols: boolean; + public savedCols: number; // stream private readable: boolean; @@ -274,7 +274,7 @@ export class Terminal extends EventEmitter implements ITerminal { private linkifier: Linkifier; public buffers: BufferSet; public buffer: Buffer; - private viewport: Viewport; + public viewport: Viewport; private compositionHelper: CompositionHelper; public charMeasure: CharMeasure; @@ -440,7 +440,7 @@ export class Terminal extends EventEmitter implements ITerminal { * @param {string} key The option key. * @param {any} value The option value. */ - public setOption(key: string, value: any) { + public setOption(key: string, value: any): void { // TODO: Give value a better type (boolean | string, ...) if (!(key in DEFAULT_OPTIONS)) { throw new Error('No option with key "' + key + '"'); @@ -453,7 +453,7 @@ export class Terminal extends EventEmitter implements ITerminal { msg += `(${this.rows}) is not allowed.`; console.warn(msg); - return false; + return; } if (this.options[key] !== value) { @@ -1167,7 +1167,7 @@ export class Terminal extends EventEmitter implements ITerminal { /** * Display the cursor element */ - public showCursor() { + public showCursor(): void { if (!this.cursorState) { this.cursorState = 1; this.refresh(this.buffer.y, this.buffer.y); @@ -1882,7 +1882,7 @@ export class Terminal extends EventEmitter implements ITerminal { * Send data for handling to the terminal * @param {string} data */ - private send(data) { + public send(data: string): void { if (!this.queue) { setTimeout(() => { this.handler(this.queue); @@ -1909,21 +1909,19 @@ export class Terminal extends EventEmitter implements ITerminal { /** * Log the current state to the console. */ - public log(): void { + public log(text: string, data?: any): void { if (!this.options.debug) return; if (!this.context.console || !this.context.console.log) return; - const args = Array.prototype.slice.call(arguments); - this.context.console.log.apply(this.context.console, args); + this.context.console.log(text, data); } /** * Log the current state as error to the console. */ - public error(): void { + public error(text: string, data?: any): void { if (!this.options.debug) return; if (!this.context.console || !this.context.console.error) return; - const args = Array.prototype.slice.call(arguments); - this.context.console.error.apply(this.context.console, args); + this.context.console.error(text, data); } /** @@ -1932,7 +1930,7 @@ export class Terminal extends EventEmitter implements ITerminal { * @param {number} x The number of columns to resize to. * @param {number} y The number of rows to resize to. */ - public resize(x: number, y: number) { + public resize(x: number, y: number): void { if (isNaN(x) || isNaN(y)) { return; } @@ -2052,7 +2050,7 @@ export class Terminal extends EventEmitter implements ITerminal { * Updates the range of rows to refresh * @param {number} y The number of rows to refresh next. */ - public updateRange(y) { + public updateRange(y: number): void { if (y < this.refreshStart) this.refreshStart = y; if (y > this.refreshEnd) this.refreshEnd = y; // if (y > this.refreshEnd) { @@ -2094,7 +2092,7 @@ export class Terminal extends EventEmitter implements ITerminal { * Move the cursor to the previous tab stop from the given position (default is current). * @param {number} x The position to move the cursor to the previous tab stop. */ - public prevStop(x) { + public prevStop(x?: number): number { if (x == null) x = this.buffer.x; while (!this.buffer.tabs[--x] && x > 0); return x >= this.cols ? this.cols - 1 : x < 0 ? 0 : x; @@ -2104,12 +2102,10 @@ export class Terminal extends EventEmitter implements ITerminal { * Move the cursor one tab stop forward from the given position (default is current). * @param {number} x The position to move the cursor one tab stop forward. */ - public nextStop(x) { + public nextStop(x?: number): number { if (x == null) x = this.buffer.x; while (!this.buffer.tabs[++x] && x < this.cols); - return x >= this.cols - ? this.cols - 1 - : x < 0 ? 0 : x; + return x >= this.cols ? this.cols - 1 : x < 0 ? 0 : x; } /** @@ -2134,7 +2130,7 @@ export class Terminal extends EventEmitter implements ITerminal { * @param {number} x The column from which to start erasing to the start of the line. * @param {number} y The line in which to operate. */ - public eraseLeft(x: number, y: number) { + public eraseLeft(x: number, y: number): void { const line = this.buffer.lines.get(this.buffer.ybase + y); if (!line) { return; @@ -2171,7 +2167,7 @@ export class Terminal extends EventEmitter implements ITerminal { * Erase all content in the given line * @param {number} y The line to erase all of its contents. */ - public eraseLine(y): void { + public eraseLine(y: number): void { this.eraseRight(0, y); } @@ -2180,7 +2176,7 @@ export class Terminal extends EventEmitter implements ITerminal { * @param {boolean} cur First bunch of data for each "blank" character. * @param {boolean} isWrapped Whether the new line is wrapped from the previous line. */ - public blankLine(cur?, isWrapped?: boolean) { + public blankLine(cur?: boolean, isWrapped?: boolean): [number, string, number][] { const attr = cur ? this.eraseAttr() : this.defAttr; const ch = [attr, ' ', 1]; // width defaults to 1 halfwidth character @@ -2209,12 +2205,10 @@ export class Terminal extends EventEmitter implements ITerminal { /** * Evaluate if the current terminal is the given argument. - * @param {object} term The terminal to evaluate + * @param term The terminal name to evaluate */ - private is(term) { - // TODO: Do we need this? - const name = this.options.termName; - return (name + '').indexOf(term) === 0; + public is(term: string): boolean { + return (this.options.termName + '').indexOf(term) === 0; } /** @@ -2294,7 +2288,7 @@ export class Terminal extends EventEmitter implements ITerminal { /** * ESC c Full Reset (RIS). */ - public reset() { + public reset(): void { this.options.rows = this.rows; this.options.cols = this.cols; const customKeyEventHandler = this.customKeyEventHandler; @@ -2329,7 +2323,7 @@ export class Terminal extends EventEmitter implements ITerminal { // Expose to InputHandler // TODO: Revise when truecolor is introduced. - public matchColor(r1, g1, b1) { + public matchColor(r1, g1, b1): any { const hash = (r1 << 16) | (g1 << 8) | b1; if (matchColorCache[hash] != null) { diff --git a/src/Viewport.ts b/src/Viewport.ts index fe276935..bd667edb 100644 --- a/src/Viewport.ts +++ b/src/Viewport.ts @@ -2,14 +2,14 @@ * @license MIT */ -import { ITerminal } from './Interfaces'; +import { ITerminal, IViewport } from './Interfaces'; import { CharMeasure } from './utils/CharMeasure'; /** * 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 { +export class Viewport implements IViewport { private currentRowHeight: number; private lastRecordedBufferLength: number; private lastRecordedViewportHeight: number; From f9fce53cc45eaaba805c4387f3f690efa5e11dcd Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 5 Aug 2017 13:56:38 -0700 Subject: [PATCH 012/378] Add typedef tslint rule --- src/CompositionHelper.test.ts | 16 +-- src/CompositionHelper.ts | 16 +-- src/EventEmitter.ts | 25 ++-- src/InputHandler.test.ts | 39 +++--- src/InputHandler.ts | 24 ++-- src/Interfaces.ts | 26 ++-- src/Linkifier.test.ts | 24 ++-- src/Linkifier.ts | 8 +- src/Parser.ts | 2 +- src/Renderer.ts | 12 +- src/SelectionManager.ts | 16 +-- src/Terminal.ts | 236 +++++++++++++++------------------ src/Viewport.ts | 8 +- src/handlers/Clipboard.test.ts | 4 +- src/handlers/Clipboard.ts | 17 ++- src/utils/Generic.ts | 2 +- tslint.json | 5 + 17 files changed, 230 insertions(+), 250 deletions(-) diff --git a/src/CompositionHelper.test.ts b/src/CompositionHelper.test.ts index 4a96e12a..70b23183 100644 --- a/src/CompositionHelper.test.ts +++ b/src/CompositionHelper.test.ts @@ -36,7 +36,7 @@ describe('CompositionHelper', () => { return { offsetLeft: 0, offsetTop: 0 }; } }, - handler: function (text) { + handler: (text: string) => { handledText += text; } }; @@ -45,7 +45,7 @@ describe('CompositionHelper', () => { }); describe('Input', () => { - it('Should insert simple characters', function (done) { + it('Should insert simple characters', (done) => { // First character 'ㅇ' compositionHelper.compositionstart(); compositionHelper.compositionupdate({ data: 'ㅇ' }); @@ -69,7 +69,7 @@ describe('CompositionHelper', () => { }, 0); }); - it('Should insert complex characters', function (done) { + it('Should insert complex characters', (done) => { // First character '앙' compositionHelper.compositionstart(); compositionHelper.compositionupdate({ data: 'ㅇ' }); @@ -109,7 +109,7 @@ describe('CompositionHelper', () => { }, 0); }); - it('Should insert complex characters that change with following character', function (done) { + it('Should insert complex characters that change with following character', (done) => { // First character '아' compositionHelper.compositionstart(); compositionHelper.compositionupdate({ data: 'ㅇ' }); @@ -138,7 +138,7 @@ describe('CompositionHelper', () => { }, 0); }); - it('Should insert multi-characters compositions', function (done) { + it('Should insert multi-characters compositions', (done) => { // First character 'だ' compositionHelper.compositionstart(); compositionHelper.compositionupdate({ data: 'd' }); @@ -161,7 +161,7 @@ describe('CompositionHelper', () => { }, 0); }); - it('Should insert multi-character compositions that are converted to other characters with the same length', function (done) { + it('Should insert multi-character compositions that are converted to other characters with the same length', (done) => { // First character 'だ' compositionHelper.compositionstart(); compositionHelper.compositionupdate({ data: 'd' }); @@ -189,7 +189,7 @@ describe('CompositionHelper', () => { }, 0); }); - it('Should insert multi-character compositions that are converted to other characters with different lengths', function (done) { + it('Should insert multi-character compositions that are converted to other characters with different lengths', (done) => { // First character 'い' compositionHelper.compositionstart(); compositionHelper.compositionupdate({ data: 'い' }); @@ -217,7 +217,7 @@ describe('CompositionHelper', () => { }, 0); }); - it('Should insert non-composition characters input immediately after composition characters', function (done) { + it('Should insert non-composition characters input immediately after composition characters', (done) => { // First character 'ㅇ' compositionHelper.compositionstart(); compositionHelper.compositionupdate({ data: 'ㅇ' }); diff --git a/src/CompositionHelper.ts b/src/CompositionHelper.ts index 439e2085..223a90e7 100644 --- a/src/CompositionHelper.ts +++ b/src/CompositionHelper.ts @@ -51,7 +51,7 @@ export class CompositionHelper { /** * Handles the compositionstart event, activating the composition view. */ - public compositionstart() { + public compositionstart(): void { this.isComposing = true; this.compositionPosition.start = this.textarea.value.length; this.compositionView.textContent = ''; @@ -62,7 +62,7 @@ export class CompositionHelper { * Handles the compositionupdate event, updating the composition view. * @param {CompositionEvent} ev The event. */ - public compositionupdate(ev: CompositionEvent) { + public compositionupdate(ev: CompositionEvent): void { this.compositionView.textContent = ev.data; this.updateCompositionElements(); setTimeout(() => { @@ -74,7 +74,7 @@ export class CompositionHelper { * Handles the compositionend event, hiding the composition view and sending the composition to * the handler. */ - public compositionend() { + public compositionend(): void { this.finalizeComposition(true); } @@ -83,7 +83,7 @@ export class CompositionHelper { * @param ev The keydown event. * @return Whether the Terminal should continue processing the keydown event. */ - public keydown(ev: KeyboardEvent) { + public keydown(ev: KeyboardEvent): boolean { if (this.isComposing || this.isSendingComposition) { if (ev.keyCode === 229) { // Continue composing if the keyCode is the "composition character" @@ -116,7 +116,7 @@ export class CompositionHelper { * compositionend event is triggered, such as enter, so that the composition is send before * the command is executed. */ - private finalizeComposition(waitForPropogation: boolean) { + private finalizeComposition(waitForPropogation: boolean): void { this.compositionView.classList.remove('active'); this.isComposing = false; this.clearTextareaPosition(); @@ -169,7 +169,7 @@ export class CompositionHelper { * character" (229) is triggered, in order to allow non-composition text to be entered when an * IME is active. */ - private handleAnyTextareaChanges() { + private handleAnyTextareaChanges(): void { const oldValue = this.textarea.value; setTimeout(() => { // Ignore if a composition has started since the timeout @@ -189,7 +189,7 @@ export class CompositionHelper { * @param dontRecurse Whether to use setTimeout to recursively trigger another update, this is * necessary as the IME events across browsers are not consistently triggered. */ - public updateCompositionElements(dontRecurse?: boolean) { + public updateCompositionElements(dontRecurse?: boolean): void { if (!this.isComposing) { return; } @@ -222,7 +222,7 @@ export class CompositionHelper { * Clears the textarea's position so that the cursor does not blink on IE. * @private */ - private clearTextareaPosition() { + private clearTextareaPosition(): void { this.textarea.style.left = ''; this.textarea.style.top = ''; }; diff --git a/src/EventEmitter.ts b/src/EventEmitter.ts index 3d34da75..a1768d2d 100644 --- a/src/EventEmitter.ts +++ b/src/EventEmitter.ts @@ -2,15 +2,10 @@ * @license MIT */ -import { IEventEmitter } from './Interfaces'; - -interface ListenerType { - (): void; - listener?: () => void; -}; +import { IEventEmitter, IListenerType } from './Interfaces'; export class EventEmitter implements IEventEmitter { - private _events: {[type: string]: ListenerType[]}; + private _events: {[type: string]: IListenerType[]}; constructor() { // Restore the previous events if available, this will happen if the @@ -18,12 +13,12 @@ export class EventEmitter implements IEventEmitter { this._events = this._events || {}; } - public on(type, listener): void { + public on(type: string, listener: IListenerType): void { this._events[type] = this._events[type] || []; this._events[type].push(listener); } - public off(type, listener): void { + public off(type: string, listener: IListenerType): void { if (!this._events[type]) { return; } @@ -39,20 +34,20 @@ export class EventEmitter implements IEventEmitter { } } - public removeAllListeners(type): void { + public removeAllListeners(type: string): void { if (this._events[type]) { delete this._events[type]; } } - public once(type, listener): any { - function on() { + public once(type: string, listener: IListenerType): void { + function on(): void { let args = Array.prototype.slice.call(arguments); this.off(type, on); - return listener.apply(this, args); + listener.apply(this, args); } (on).listener = listener; - return this.on(type, on); + this.on(type, on); } public emit(type: string, ...args: any[]): void { @@ -65,7 +60,7 @@ export class EventEmitter implements IEventEmitter { } } - public listeners(type): ListenerType[] { + public listeners(type: string): IListenerType[] { return this._events[type] || []; } diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index 95624296..b6b218df 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -65,7 +65,7 @@ describe('InputHandler', () => { }); }); -const old_wcwidth = (function(opts) { +const old_wcwidth = (function(opts: {nul: number, control: number}): (ucs: number) => number { // extracted from https://www.cl.cam.ac.uk/%7Emgk25/ucs/wcwidth.c // combining characters const COMBINING = [ @@ -119,7 +119,7 @@ const old_wcwidth = (function(opts) { [0xE0100, 0xE01EF] ]; // binary search - function bisearch(ucs) { + function bisearch(ucs: number): boolean { let min = 0; let max = COMBINING.length - 1; let mid; @@ -136,23 +136,26 @@ const old_wcwidth = (function(opts) { } return false; } - function wcwidth(ucs) { - // test for 8-bit control characters - if (ucs === 0) - return opts.nul; - if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) - return opts.control; - // binary search in table of non-spacing characters - if (bisearch(ucs)) - return 0; - // if we arrive here, ucs is not a combining or C0/C1 control character - if (isWide(ucs)) { - return 2; - } - return 1; + function wcwidth(ucs: number): number { + // test for 8-bit control characters + if (ucs === 0) { + return opts.nul; + } + if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) { + return opts.control; + } + // binary search in table of non-spacing characters + if (bisearch(ucs)) { + return 0; + } + // if we arrive here, ucs is not a combining or C0/C1 control character + if (isWide(ucs)) { + return 2; + } + return 1; } - function isWide(ucs) { - return ( + function isWide(ucs: number): boolean { + return ( ucs >= 0x1100 && ( ucs <= 0x115f || // Hangul Jamo init. consonants ucs === 0x2329 || diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 8f566cd9..9376d182 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -221,7 +221,7 @@ export class InputHandler implements IInputHandler { * CSI Ps B * Cursor Down Ps Times (default = 1) (CUD). */ - public cursorDown(params: number[]) { + public cursorDown(params: number[]): void { let param = params[0]; if (param < 1) { param = 1; @@ -240,7 +240,7 @@ export class InputHandler implements IInputHandler { * CSI Ps C * Cursor Forward Ps Times (default = 1) (CUF). */ - public cursorForward(params: number[]) { + public cursorForward(params: number[]): void { let param = params[0]; if (param < 1) { param = 1; @@ -255,7 +255,7 @@ export class InputHandler implements IInputHandler { * CSI Ps D * Cursor Backward Ps Times (default = 1) (CUB). */ - public cursorBackward(params: number[]) { + public cursorBackward(params: number[]): void { let param = params[0]; if (param < 1) { param = 1; @@ -1460,7 +1460,7 @@ export class InputHandler implements IInputHandler { } } -export const wcwidth = (function(opts) { +export const wcwidth = (function(opts: {nul: number, control: number}): (ucs: number) => number { // extracted from https://www.cl.cam.ac.uk/%7Emgk25/ucs/wcwidth.c // combining characters const COMBINING_BMP = [ @@ -1516,7 +1516,7 @@ export const wcwidth = (function(opts) { [0xE0100, 0xE01EF] ]; // binary search - function bisearch(ucs, data) { + function bisearch(ucs: number, data: number[][]): boolean { let min = 0; let max = data.length - 1; let mid; @@ -1533,7 +1533,7 @@ export const wcwidth = (function(opts) { } return false; } - function wcwidthBMP(ucs) { + function wcwidthBMP(ucs: number): number { // test for 8-bit control characters if (ucs === 0) return opts.nul; @@ -1548,7 +1548,7 @@ export const wcwidth = (function(opts) { } return 1; } - function isWideBMP(ucs) { + function isWideBMP(ucs: number): boolean { return ( ucs >= 0x1100 && ( ucs <= 0x115f || // Hangul Jamo init. consonants @@ -1562,7 +1562,7 @@ export const wcwidth = (function(opts) { (ucs >= 0xff00 && ucs <= 0xff60) || // Fullwidth Forms (ucs >= 0xffe0 && ucs <= 0xffe6))); } - function wcwidthHigh(ucs) { + function wcwidthHigh(ucs: number): 0 | 1 | 2 { if (bisearch(ucs, COMBINING_HIGH)) return 0; if ((ucs >= 0x20000 && ucs <= 0x2fffd) || (ucs >= 0x30000 && ucs <= 0x3fffd)) { @@ -1571,8 +1571,8 @@ export const wcwidth = (function(opts) { return 1; } const control = opts.control | 0; - let table = null; - function init_table() { + let table: number[] | Uint32Array = null; + function init_table(): number[] | Uint32Array { // lookup table for BMP const CODEPOINTS = 65536; // BMP holds 65536 codepoints const BITWIDTH = 2; // a codepoint can have a width of 0, 1 or 2 @@ -1589,7 +1589,7 @@ export const wcwidth = (function(opts) { num = (num << 2) | wcwidthBMP(CODEPOINTS_PER_ITEM * i + pos); table[i] = num; } - return table; + return table; } // get width from lookup table // position in container : num / CODEPOINTS_PER_ITEM @@ -1603,7 +1603,7 @@ export const wcwidth = (function(opts) { // ==> n = n >> m e.g. m=12 000000000000FFEEDDCCBBAA99887766 // we are only interested in 2 LSBs, cut off higher bits // ==> n = n & 3 e.g. 000000000000000000000000000000XX - return function (num) { + return function (num: number): number { num = num | 0; // get asm.js like optimization under V8 if (num < 32) return control | 0; diff --git a/src/Interfaces.ts b/src/Interfaces.ts index b61c0057..875cbac8 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -2,7 +2,7 @@ * @license MIT */ -import { LinkMatcherOptions } from './Interfaces'; +import { ILinkMatcherOptions } from './Interfaces'; import { LinkMatcherHandler, LinkMatcherValidationCallback, Charset } from './Types'; export interface IBrowser { @@ -40,10 +40,9 @@ export interface ITerminal extends IEventEmitter { * Emit the 'data' event and populate the given data. * @param data The data to populate in the event. */ - handler(data: string); - on(event: string, callback: () => void); - scrollDisp(disp: number, suppressScrollEvent: boolean); - cancel(ev: Event, force?: boolean); + handler(data: string): void; + scrollDisp(disp: number, suppressScrollEvent?: boolean): void; + cancel(ev: Event, force?: boolean): boolean | void; log(text: string): void; reset(): void; showCursor(): void; @@ -107,7 +106,7 @@ export interface IInputHandlingTerminal extends IEventEmitter { reset(): void; showCursor(): void; refresh(start: number, end: number): void; - matchColor(r1, g1, b1): any; + matchColor(r1: number, g1: number, b1: number): any; error(text: string, data?: any): void; setOption(key: string, value: any): void; } @@ -167,7 +166,7 @@ export interface ISelectionManager { disable(): void; enable(): void; setBuffer(buffer: ICircularList<[number, string, number][]>): void; - setSelection(row: number, col: number, length: number); + setSelection(row: number, col: number, length: number): void; } export interface ICharMeasure { @@ -179,7 +178,7 @@ export interface ICharMeasure { export interface ILinkifier { linkifyRow(rowIndex: number): void; attachHypertextLinkHandler(handler: LinkMatcherHandler): void; - registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options?: LinkMatcherOptions): number; + registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options?: ILinkMatcherOptions): number; deregisterLinkMatcher(matcherId: number): boolean; } @@ -198,12 +197,17 @@ export interface ICircularList extends IEventEmitter { } export interface IEventEmitter { - on(type, listener): void; - off(type, listener): void; + on(type: string, listener: IListenerType): void; + off(type: string, listener: IListenerType): void; emit(type: string, data?: any): void; } -export interface LinkMatcherOptions { +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). diff --git a/src/Linkifier.test.ts b/src/Linkifier.test.ts index 132ce5f0..414238f9 100644 --- a/src/Linkifier.test.ts +++ b/src/Linkifier.test.ts @@ -32,7 +32,7 @@ describe('Linkifier', () => { linkifier = new TestLinkifier(); }); - function addRow(html: string) { + function addRow(html: string): void { const element = document.createElement('div'); element.innerHTML = html; container.appendChild(element); @@ -57,24 +57,24 @@ describe('Linkifier', () => { document.body.appendChild(container); }); - function clickElement(element: Node) { + function clickElement(element: Node): void { const event = document.createEvent('MouseEvent'); event.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); element.dispatchEvent(event); } - function assertLinkifiesEntireRow(uri: string, done: MochaDone) { - addRow(uri); - linkifier.linkifyRow(0); - setTimeout(() => { - assert.equal((rows[0].firstChild).tagName, 'A'); - assert.equal((rows[0].firstChild).textContent, uri); - done(); - }, 0); + function assertLinkifiesEntireRow(uri: string, done: MochaDone): void { + addRow(uri); + linkifier.linkifyRow(0); + setTimeout(() => { + assert.equal((rows[0].firstChild).tagName, 'A'); + assert.equal((rows[0].firstChild).textContent, uri); + done(); + }, 0); } describe('http links', () => { - function assertLinkifiesEntireRow(uri: string, done: MochaDone) { + function assertLinkifiesEntireRow(uri: string, done: MochaDone): void { addRow(uri); linkifier.linkifyRow(0); setTimeout(() => { @@ -87,7 +87,7 @@ describe('Linkifier', () => { }); describe('link matcher', () => { - function assertLinkifiesRow(rowText: string, linkMatcherRegex: RegExp, expectedHtml: string, done: MochaDone) { + function assertLinkifiesRow(rowText: string, linkMatcherRegex: RegExp, expectedHtml: string, done: MochaDone): void { addRow(rowText); linkifier.registerLinkMatcher(linkMatcherRegex, () => {}); linkifier.linkifyRow(0); diff --git a/src/Linkifier.ts b/src/Linkifier.ts index bc4949b1..2323f226 100644 --- a/src/Linkifier.ts +++ b/src/Linkifier.ts @@ -2,7 +2,7 @@ * @license MIT */ -import { LinkMatcherOptions } from './Interfaces'; +import { ILinkMatcherOptions } from './Interfaces'; import { LinkMatcher, LinkMatcherHandler, LinkMatcherValidationCallback } from './Types'; const INVALID_LINK_CLASS = 'xterm-invalid-link'; @@ -60,7 +60,7 @@ export class Linkifier { * @param document The document object. * @param rows The array of rows to apply links to. */ - public attachToDom(document: Document, rows: HTMLElement[]) { + public attachToDom(document: Document, rows: HTMLElement[]): void { this._document = document; this._rows = rows; } @@ -108,10 +108,10 @@ export class Linkifier { * this searches the textContent of the rows. You will want to use \s to match * a space ' ' character for example. * @param {LinkHandler} handler The callback when the link is called. - * @param {LinkMatcherOptions} [options] Options for the link matcher. + * @param {ILinkMatcherOptions} [options] Options for the link matcher. * @return {number} The ID of the new matcher, this can be used to deregister. */ - public registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options: LinkMatcherOptions = {}): number { + public registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options: ILinkMatcherOptions = {}): number { if (this._nextLinkMatcherId !== HYPERTEXT_LINK_MATCHER_ID && !handler) { throw new Error('handler must be defined'); } diff --git a/src/Parser.ts b/src/Parser.ts index bec0af7b..90d413a7 100644 --- a/src/Parser.ts +++ b/src/Parser.ts @@ -613,7 +613,7 @@ export class Parser { * * @param param the parameter. */ - public setParam(param: number) { + public setParam(param: number): void { this._terminal.currentParam = param; } diff --git a/src/Renderer.ts b/src/Renderer.ts index 165594fd..f706bffc 100644 --- a/src/Renderer.ts +++ b/src/Renderer.ts @@ -37,7 +37,7 @@ export class Renderer { // Figure out whether boldness affects // the character width of monospace fonts. if (brokenBold === null) { - brokenBold = checkBoldBroken((this._terminal).element); + brokenBold = checkBoldBroken(this._terminal.element); } this._spanElementObjectPool = new DomElementObjectPool('span'); @@ -327,7 +327,7 @@ export class Renderer { * @param start The selection start. * @param end The selection end. */ - public refreshSelection(start: [number, number], end: [number, number]) { + public refreshSelection(start: [number, number], end: [number, number]): void { // Remove all selections while (this._terminal.selectionContainer.children.length) { this._terminal.selectionContainer.removeChild(this._terminal.selectionContainer.children[0]); @@ -385,16 +385,16 @@ export class Renderer { // If bold is broken, we can't use it in the terminal. -function checkBoldBroken(terminal) { - const document = terminal.ownerDocument; +function checkBoldBroken(terminalElement: HTMLElement): boolean { + const document = terminalElement.ownerDocument; const el = document.createElement('span'); el.innerHTML = 'hello world'; - terminal.appendChild(el); + terminalElement.appendChild(el); const w1 = el.offsetWidth; const h1 = el.offsetHeight; el.style.fontWeight = 'bold'; const w2 = el.offsetWidth; const h2 = el.offsetHeight; - terminal.removeChild(el); + terminalElement.removeChild(el); return w1 !== w2 || h1 !== h2; } diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index bf594f70..e3770c06 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -7,7 +7,7 @@ import * as Browser from './utils/Browser'; import { CharMeasure } from './utils/CharMeasure'; import { CircularList } from './utils/CircularList'; import { EventEmitter } from './EventEmitter'; -import { ITerminal, ICircularList } from './Interfaces'; +import { ITerminal, ICircularList, ISelectionManager } from './Interfaces'; import { SelectionModel } from './SelectionModel'; import { translateBufferLineToString } from './utils/BufferLine'; @@ -66,7 +66,7 @@ enum SelectionMode { * not handled by the SelectionManager but a 'refresh' event is fired when the * selection is ready to be redrawn. */ -export class SelectionManager extends EventEmitter { +export class SelectionManager extends EventEmitter implements ISelectionManager { protected _model: SelectionModel; /** @@ -116,7 +116,7 @@ export class SelectionManager extends EventEmitter { /** * Initializes listener variables. */ - private _initListeners() { + private _initListeners(): void { this._mouseMoveListener = event => this._onMouseMove(event); this._mouseUpListener = event => this._onMouseUp(event); @@ -267,7 +267,7 @@ export class SelectionManager extends EventEmitter { * Handle the buffer being trimmed, adjust the selection position. * @param amount The amount the buffer is being trimmed. */ - private _onTrim(amount: number) { + private _onTrim(amount: number): void { const needsRefresh = this._model.onTrim(amount); if (needsRefresh) { this.refresh(); @@ -316,7 +316,7 @@ export class SelectionManager extends EventEmitter { * Handles te mousedown event, setting up for a new selection. * @param event The mousedown event. */ - private _onMouseDown(event: MouseEvent) { + private _onMouseDown(event: MouseEvent): void { // If we have selection, we want the context menu on right click even if the // terminal is in mouse mode. if (event.button === 2 && this.hasSelection) { @@ -455,7 +455,7 @@ export class SelectionManager extends EventEmitter { * end of the selection and refreshing the selection. * @param event The mousemove event. */ - private _onMouseMove(event: MouseEvent) { + private _onMouseMove(event: MouseEvent): void { // Record the previous position so we know whether to redraw the selection // at the end. const previousSelectionEnd = this._model.selectionEnd ? [this._model.selectionEnd[0], this._model.selectionEnd[1]] : null; @@ -511,7 +511,7 @@ export class SelectionManager extends EventEmitter { * The callback that occurs every DRAG_SCROLL_INTERVAL ms that does the * scrolling of the viewport. */ - private _dragScroll() { + private _dragScroll(): void { if (this._dragScrollAmount) { this._terminal.scrollDisp(this._dragScrollAmount, false); // Re-evaluate selection @@ -528,7 +528,7 @@ export class SelectionManager extends EventEmitter { * Handles the mouseup event, removing the mousedown listeners. * @param event The mouseup event. */ - private _onMouseUp(event: MouseEvent) { + private _onMouseUp(event: MouseEvent): void { this._removeMouseDownListeners(); } diff --git a/src/Terminal.ts b/src/Terminal.ts index df313ee3..b3a27be9 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -29,8 +29,8 @@ import * as Mouse from './utils/Mouse'; import { CHARSETS } from './Charsets'; import { getRawByteCoords } from './utils/Mouse'; import { translateBufferLineToString } from './utils/BufferLine'; -import { CustomKeyEventHandler, Charset } from './Types'; -import { ITerminal, IBrowser, ITerminalOptions, IInputHandlingTerminal } from './Interfaces'; +import { CustomKeyEventHandler, Charset, LinkMatcherHandler, LinkMatcherValidationCallback } from './Types'; +import { ITerminal, IBrowser, ITerminalOptions, IInputHandlingTerminal, ILinkMatcherOptions } from './Interfaces'; // Declare for RequireJS in loadAddon declare var define: any; @@ -94,7 +94,7 @@ const tangoColors: string[] = [ // Colors 0-15 + 16-255 // Much thanks to TooTallNate for writing this. -const defaultColors: string[] = (function() { +const defaultColors: string[] = (function(): string[] { let colors = tangoColors.slice(); let r = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]; let i; @@ -113,13 +113,13 @@ const defaultColors: string[] = (function() { out(c, c, c); } - function out(r, g, b) { + function out(r: number, g: number, b: number): void { colors.push('#' + hex(r) + hex(g) + hex(b)); } - function hex(c) { - c = c.toString(16); - return c.length < 2 ? '0' + c : c; + function hex(c: number): string { + let s = c.toString(16); + return s.length < 2 ? '0' + s : s; } return colors; @@ -127,8 +127,8 @@ const defaultColors: string[] = (function() { const _colors: string[] = defaultColors.slice(); -const vcolors: number[][] = (function() { - const out = []; +const vcolors: number[][] = (function(): number[][] { + const out: number[][] = []; let color; for (let i = 0; i < 256; i++) { @@ -387,8 +387,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT // Create the terminal's buffers and set the current buffer this.buffers = new BufferSet(this); this.buffer = this.buffers.active; // Convenience shortcut; - this.buffers.on('activate', function (buffer) { - this._terminal.buffer = buffer; + this.buffers.on('activate', (buffer: Buffer) => { + this.buffer = buffer; }); let i = this.rows; @@ -485,11 +485,11 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT } } - private restartCursorBlinking() { + private restartCursorBlinking(): void { this.setCursorBlinking(this.options.cursorBlink); } - private setCursorBlinking(enabled) { + private setCursorBlinking(enabled: boolean): void { this.element.classList.toggle('xterm-cursor-blink', enabled); this.clearCursorBlinkingInterval(); if (enabled) { @@ -499,7 +499,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT } } - private clearCursorBlinkingInterval() { + private clearCursorBlinkingInterval(): void { this.element.classList.remove('xterm-cursor-blink-on'); if (this.cursorBlinkInterval) { clearInterval(this.cursorBlinkInterval); @@ -510,7 +510,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT /** * Binds the desired focus behavior on a given terminal object. */ - private bindFocus() { + private bindFocus(): void { globalOn(this.textarea, 'focus', (ev) => { if (this.sendFocus) { this.send(C0.ESC + '[I'); @@ -526,14 +526,14 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT /** * Blur the terminal. Delegates blur handling to the terminal's DOM element. */ - private blur() { + private blur(): void { return this.textarea.blur(); } /** * Binds the desired blur behavior on a given terminal object. */ - private bindBlur() { + private bindBlur(): void { on(this.textarea, 'blur', (ev) => { this.refresh(this.buffer.y, this.buffer.y); if (this.sendFocus) { @@ -549,7 +549,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT /** * Initialize default behavior */ - private initGlobal() { + private initGlobal(): void { this.bindKeys(); this.bindFocus(); this.bindBlur(); @@ -598,33 +598,33 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT /** * Apply key handling to the terminal */ - private bindKeys() { + private bindKeys(): void { const self = this; - on(this.element, 'keydown', function (ev) { + on(this.element, 'keydown', function (ev: KeyboardEvent): void { if (document.activeElement !== this) { return; } self.keyDown(ev); }, true); - on(this.element, 'keypress', function (ev) { + on(this.element, 'keypress', function (ev: KeyboardEvent): void { if (document.activeElement !== this) { return; } self.keyPress(ev); }, true); - on(this.element, 'keyup', (ev) => { + on(this.element, 'keyup', (ev: KeyboardEvent) => { if (!wasMondifierKeyOnlyEvent(ev)) { this.focus(); } }, true); - on(this.textarea, 'keydown', (ev) => { + on(this.textarea, 'keydown', (ev: KeyboardEvent) => { this.keyDown(ev); }, true); - on(this.textarea, 'keypress', (ev) => { + on(this.textarea, 'keypress', (ev: KeyboardEvent) => { this.keyPress(ev); // Truncate the textarea's value, since it is not needed this.textarea.value = ''; @@ -642,7 +642,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * if no row argument is passed. Return the inserted row. * @param {HTMLElement} row (optional) The row to append to the terminal. */ - private insertRow(row?: HTMLElement) { + private insertRow(row?: HTMLElement): HTMLElement { if (typeof row !== 'object') { row = document.createElement('div'); } @@ -659,7 +659,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * @param {HTMLElement} parent The element to create the terminal within. * @param {boolean} focus Focus the terminal, after it gets instantiated in the DOM */ - private open(parent, focus) { + private open(parent: HTMLElement, focus?: boolean): void { let i = 0; let div; @@ -802,7 +802,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * @param {string} addon The name of the addon to load * @static */ - public static loadAddon(addon, callback) { + public static loadAddon(addon: string, callback?: Function): boolean | any { + // TODO: Improve return type and documentation if (typeof exports === 'object' && typeof module === 'object') { // CommonJS return require('./addons/' + addon + '/' + addon); @@ -819,7 +820,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * Updates the helper CSS class with any changes necessary after the terminal's * character width has been changed. */ - public updateCharSizeStyles() { + public updateCharSizeStyles(): void { this.charSizeStyleElement.textContent = `.xterm-wide-char{width:${this.charMeasure.width * 2}px;}` + `.xterm-normal-char{width:${this.charMeasure.width}px;}` + @@ -836,7 +837,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * Relevant functions in xterm/button.c: * BtnCode, EmitButtonCode, EditorButton, SendMousePosition */ - public bindMouse() { + public bindMouse(): void { const el = this.element; const self = this; let pressed = 32; @@ -844,7 +845,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT // mouseup, mousedown, wheel // left click: ^[[M 3<^[[M#3< // wheel up: ^[[M`3> - function sendButton(ev) { + function sendButton(ev: MouseEvent | WheelEvent): void { let button; let pos; @@ -857,7 +858,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT sendEvent(button, pos); - switch (ev.overrideType || ev.type) { + switch ((ev).overrideType || ev.type) { case 'mousedown': pressed = button; break; @@ -876,11 +877,9 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT // motion example of a left click: // ^[[M 3<^[[M@4<^[[M@5<^[[M@6<^[[M@7<^[[M#7< - function sendMove(ev) { + function sendMove(ev: MouseEvent): void { let button = pressed; - let pos; - - pos = getRawByteCoords(ev, self.rowContainer, self.charMeasure, self.cols, self.rows); + let pos = getRawByteCoords(ev, self.rowContainer, self.charMeasure, self.cols, self.rows); if (!pos) return; // buttons marked as motions @@ -892,13 +891,19 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT // encode button and // position to characters - function encode(data, ch) { + function encode(data: number[], ch: number): void { if (!self.utfMouse) { - if (ch === 255) return data.push(0); + if (ch === 255) { + data.push(0); + return; + } if (ch > 127) ch = 127; data.push(ch); } else { - if (ch === 2047) return data.push(0); + if (ch === 2047) { + data.push(0); + return; + } if (ch < 127) { data.push(ch); } else { @@ -915,7 +920,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT // sgr: ^[[ Cb ; Cx ; Cy M/m // vt300: ^[[ 24(1/3/5)~ [ Cx , Cy ] \r // locator: CSI P e ; P b ; P r ; P c ; P p & w - function sendEvent(button, pos) { + function sendEvent(button: number, pos: {x: number, y: number}): void { // self.emit('mouse', { // x: pos.x - 32, // y: pos.x - 32, @@ -957,7 +962,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT + ';' + pos.x + ';' - + (pos.page || 0) + // Not sure what page is meant to be + + (pos).page || 0 + '&w'); return; } @@ -984,7 +990,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT return; } - let data = []; + let data: number[] = []; encode(data, button); encode(data, pos.x); @@ -993,7 +999,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT self.send(C0.ESC + '[M' + String.fromCharCode.apply(String, data)); } - function getButton(ev) { + function getButton(ev: MouseEvent): number { let button; let shift; let meta; @@ -1007,7 +1013,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT // 3 = release // wheel up/down: // 1, and 2 - with 64 added - switch (ev.overrideType || ev.type) { + switch ((ev).overrideType || ev.type) { case 'mousedown': button = ev.button != null ? +ev.button @@ -1028,7 +1034,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT : 65; break; case 'wheel': - button = ev.wheelDeltaY > 0 + button = (ev).wheelDeltaY > 0 ? 64 : 65; break; @@ -1055,7 +1061,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT return button; } - on(el, 'mousedown', (ev) => { + on(el, 'mousedown', (ev: MouseEvent) => { // Prevent the focus on the textarea from getting lost // and make sure we get focused on mousedown @@ -1082,10 +1088,12 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT if (!this.x10Mouse) { const handler = (ev: MouseEvent) => { sendButton(ev); + // TODO: Seems dangerous calling this on document? if (this.normalMouse) off(this.document, 'mousemove', sendMove); off(this.document, 'mouseup', handler); return this.cancel(ev); }; + // TODO: Seems dangerous calling this on document? on(this.document, 'mouseup', handler); } @@ -1096,7 +1104,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT // on(this.document, 'mousemove', sendMove); // } - on(el, 'wheel', (ev) => { + on(el, 'wheel', (ev: WheelEvent) => { if (!this.mouseEvents) return; if (this.x10Mouse || this.vt300Mouse || this.decLocator) return; sendButton(ev); @@ -1131,8 +1139,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT super.destroy(); this.readable = false; this.writable = false; - this.handler = function() {}; - this.write = function() {}; + this.handler = () => {}; + this.write = () => {}; if (this.element && this.element.parentNode) { this.element.parentNode.removeChild(this.element); } @@ -1316,7 +1324,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT } } - private innerWrite() { + private innerWrite(): void { const writeBatch = this.writeBuffer.splice(0, WRITE_BATCH_SIZE); while (writeBatch.length > 0) { const data = writeBatch.shift(); @@ -1354,7 +1362,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * Writes text to the terminal, followed by a break line character (\n). * @param {string} data The text to write to the terminal. */ - public writeln(data): void { + public writeln(data: string): void { this.write(data + '\r\n'); } @@ -1386,9 +1394,9 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * Attaches a http(s) link handler, forcing web links to behave differently to * regular tags. This will trigger a refresh as links potentially need to be * reconstructed. Calling this with null will remove the handler. - * @param {LinkMatcherHandler} handler The handler callback function. + * @param handler The handler callback function. */ - public setHypertextLinkHandler(handler) { + public setHypertextLinkHandler(handler: LinkMatcherHandler): void { if (!this.linkifier) { throw new Error('Cannot attach a hypertext link handler before Terminal.open is called'); } @@ -1400,10 +1408,10 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT /** * Attaches a validation callback for hypertext links. This is useful to use * validation logic or to do something with the link's element and url. - * @param {LinkMatcherValidationCallback} callback The callback to use, this can + * @param callback The callback to use, this can * be cleared with null. */ - public setHypertextValidationCallback(callback) { + public setHypertextValidationCallback(callback: LinkMatcherValidationCallback): void { if (!this.linkifier) { throw new Error('Cannot attach a hypertext validation callback before Terminal.open is called'); } @@ -1413,16 +1421,16 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT } /** - * Registers a link matcher, allowing custom link patterns to be matched and - * handled. - * @param {RegExp} regex The regular expression to search for, specifically - * this searches the textContent of the rows. You will want to use \s to match - * a space ' ' character for example. - * @param {LinkMatcherHandler} handler The callback when the link is called. - * @param {LinkMatcherOptions} [options] Options for the link matcher. - * @return {number} The ID of the new matcher, this can be used to deregister. + * Registers a link matcher, allowing custom link patterns to be matched and + * handled. + * @param regex The regular expression to search for, specifically + * this searches the textContent of the rows. You will want to use \s to match + * a space ' ' character for example. + * @param handler The callback when the link is called. + * @param [options] Options for the link matcher. + * @return The ID of the new matcher, this can be used to deregister. */ - public registerLinkMatcher(regex, handler, options) { + public registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options: ILinkMatcherOptions): number { if (this.linkifier) { const matcherId = this.linkifier.registerLinkMatcher(regex, handler, options); this.refresh(0, this.rows - 1); @@ -1432,9 +1440,9 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT /** * Deregisters a link matcher if it has been registered. - * @param {number} matcherId The link matcher's ID (returned after register) + * @param matcherId The link matcher's ID (returned after register) */ - public deregisterLinkMatcher(matcherId) { + public deregisterLinkMatcher(matcherId: number): void { if (this.linkifier) { if (this.linkifier.deregisterLinkMatcher(matcherId)) { this.refresh(0, this.rows - 1); @@ -1445,7 +1453,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT /** * Gets whether the terminal has an active selection. */ - public hasSelection() { + public hasSelection(): boolean { return this.selectionManager ? this.selectionManager.hasSelection : false; } @@ -1453,14 +1461,14 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * Gets the terminal's current selection, this is useful for implementing copy * behavior outside of xterm.js. */ - public getSelection() { + public getSelection(): string { return this.selectionManager ? this.selectionManager.selectionText : ''; } /** * Clears the current terminal selection. */ - public clearSelection() { + public clearSelection(): void { if (this.selectionManager) { this.selectionManager.clearSelection(); } @@ -1469,7 +1477,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT /** * Selects all text within the terminal. */ - public selectAll() { + public selectAll(): void { if (this.selectionManager) { this.selectionManager.selectAll(); } @@ -1481,7 +1489,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent * @param {KeyboardEvent} ev The keydown event to be handled. */ - private keyDown(ev) { + private keyDown(ev: KeyboardEvent): boolean { if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) { return false; } @@ -1508,7 +1516,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT return this.cancel(ev, true); } - if (isThirdLevelShift(this, ev)) { + if (isThirdLevelShift(ev)) { return true; } @@ -1534,10 +1542,10 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * returned value is the new key code to pass to the PTY. * * Reference: http://invisible-island.net/xterm/ctlseqs/ctlseqs.html - * @param {KeyboardEvent} ev The keyboard event to be translated to key escape sequence. + * @param ev The keyboard event to be translated to key escape sequence. */ - private evaluateKeyEscapeSequence(ev) { - const result = { + private evaluateKeyEscapeSequence(ev: KeyboardEvent): {cancel: boolean, key: string, scrollDisp: number} { + const result: {cancel: boolean, key: string, scrollDisp: number} = { // Whether to cancel event propogation (NOTE: this may not be needed since the event is // canceled at the end of keyDown cancel: false, @@ -1546,7 +1554,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT // The number of characters to scroll, if this is defined it will cancel the event scrollDisp: undefined }; - const modifiers = ev.shiftKey << 0 | ev.altKey << 1 | ev.ctrlKey << 2 | ev.metaKey << 3; + const modifiers = (ev.shiftKey ? 1 : 0) | (ev.altKey ? 2 : 0) | (ev.ctrlKey ? 4 : 0) | (ev.metaKey ? 8 : 0); switch (ev.keyCode) { case 8: // backspace @@ -1843,7 +1851,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent * @param {KeyboardEvent} ev The keypress event to be handled. */ - private keyPress(ev) { + private keyPress(ev: KeyboardEvent): boolean { let key; if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) { @@ -1863,7 +1871,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT } if (!key || ( - (ev.altKey || ev.ctrlKey || ev.metaKey) && !isThirdLevelShift(this, ev) + (ev.altKey || ev.ctrlKey || ev.metaKey) && !isThirdLevelShift(ev) )) { return false; } @@ -1897,7 +1905,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * Ring the bell. * Note: We could do sweet things with webaudio here */ - public bell() { + public bell(): void { if (!this.options.visualBell) return; this.element.style.borderColor = 'white'; setTimeout(() => { @@ -2064,7 +2072,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT /** * Set the range of refreshing to the maximum value */ - public maxRange() { + public maxRange(): void { this.refreshStart = 0; this.refreshEnd = this.rows - 1; } @@ -2073,7 +2081,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * Setup the tab stops. * @param {number} i */ - public setupStops(i?: number) { + public setupStops(i?: number): void { if (i != null) { if (!this.buffer.tabs[i]) { i = this.prevStop(i); @@ -2146,7 +2154,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT /** * Clears the entire buffer, making the prompt line the new first line. */ - public clear() { + public clear(): void { if (this.buffer.ybase === 0 && this.buffer.y === 0) { // Don't clear if it's already clear return; @@ -2197,9 +2205,9 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT /** * If cur return the back color xterm feature attribute. Else return defAttr. - * @param {object} cur + * @param cur */ - public ch(cur) { + public ch(cur?: boolean): [number, string, number] { return cur ? [this.eraseAttr(), ' ', 1] : [this.defAttr, ' ', 1]; } @@ -2237,7 +2245,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * Emit the 'title' event and populate the given title. * @param {string} title The title to populate in the event. */ - private handleTitle(title: string) { + private handleTitle(title: string): void { /** * This event is emitted when the title of the terminal is changed * from inside the terminal. The parameter is the new title. @@ -2254,7 +2262,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT /** * ESC D Index (IND is 0x84). */ - public index() { + public index(): void { this.buffer.y++; if (this.buffer.y > this.buffer.scrollBottom) { this.buffer.y--; @@ -2271,7 +2279,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * * Move the cursor up one row, inserting a new blank line if necessary. */ - public reverseIndex() { + public reverseIndex(): void { if (this.buffer.y === this.buffer.scrollTop) { // possibly move the code below to term.reverseScroll(); // test: echo -ne '\e[1;1H\e[44m\eM\e[0m' @@ -2308,11 +2316,11 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT /** * ESC H Tab Set (HTS is 0x88). */ - private tabSet() { + private tabSet(): void { this.buffer.tabs[this.buffer.x] = true; } - public cancel(ev: Event, force?: boolean) { + public cancel(ev: Event, force?: boolean): boolean { if (!this.options.cancelEvents && !force) { return; } @@ -2323,7 +2331,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT // Expose to InputHandler // TODO: Revise when truecolor is introduced. - public matchColor(r1, g1, b1): any { + public matchColor(r1: number, g1: number, b1: number): any { const hash = (r1 << 16) | (g1 << 8) | b1; if (matchColorCache[hash] != null) { @@ -2375,41 +2383,25 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * Helpers */ -function globalOn(el: any, type: string, handler: (event: Event) => any, capture?: boolean) { +function globalOn(el: any, type: string, handler: (event: Event) => any, capture?: boolean): void { if (!Array.isArray(el)) { el = [el]; } - el.forEach(function (element) { + el.forEach((element: HTMLElement) => { element.addEventListener(type, handler, capture || false); }); } // TODO: Remove once everything is typed const on = globalOn; -function off(el, type, handler, capture: boolean = false) { +function off(el: any, type: string, handler: (event: Event) => any, capture: boolean = false): void { el.removeEventListener(type, handler, capture); } -function inherits(child, parent) { - function f() { - this.constructor = child; - } - f.prototype = parent.prototype; - child.prototype = new f; -} - -function indexOf(obj, el) { - let i = obj.length; - while (i--) { - if (obj[i] === el) return i; - } - return -1; -} - -function isThirdLevelShift(term, ev) { +function isThirdLevelShift(ev: KeyboardEvent): boolean { const thirdLevelKey = - (term.browser.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) || - (term.browser.isMSWindows && ev.altKey && ev.ctrlKey && !ev.metaKey); + (Browser.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) || + (Browser.isMSWindows && ev.altKey && ev.ctrlKey && !ev.metaKey); if (ev.type === 'keypress') { return thirdLevelKey; @@ -2422,36 +2414,18 @@ function isThirdLevelShift(term, ev) { const matchColorCache = {}; // http://stackoverflow.com/questions/1633828 -const matchColorDistance = function(r1, g1, b1, r2, g2, b2) { +const matchColorDistance = function(r1: number, g1: number, b1: number, r2: number, g2: number, b2: number): number { return Math.pow(30 * (r1 - r2), 2) + Math.pow(59 * (g1 - g2), 2) + Math.pow(11 * (b1 - b2), 2); }; -function each(obj, iter, con) { - if (obj.forEach) return obj.forEach(iter, con); - for (let i = 0; i < obj.length; i++) { - iter.call(con, obj[i], i, obj); - } -} - -function wasMondifierKeyOnlyEvent(ev) { +function wasMondifierKeyOnlyEvent(ev: KeyboardEvent): boolean { return ev.keyCode === 16 || // Shift ev.keyCode === 17 || // Ctrl ev.keyCode === 18; // Alt } -function keys(obj) { - if (Object.keys) return Object.keys(obj); - const keys = []; - for (let key in obj) { - if (Object.prototype.hasOwnProperty.call(obj, key)) { - keys.push(key); - } - } - return keys; -} - /** * Expose */ diff --git a/src/Viewport.ts b/src/Viewport.ts index bd667edb..4ad4ddec 100644 --- a/src/Viewport.ts +++ b/src/Viewport.ts @@ -92,7 +92,7 @@ export class Viewport implements IViewport { * terminal to scroll to it. * @param ev The scroll event. */ - private onScroll(ev: Event) { + private onScroll(ev: Event): void { const newRow = Math.round(this.viewportElement.scrollTop / this.currentRowHeight); const diff = newRow - this.terminal.buffer.ydisp; this.terminal.scrollDisp(diff, true); @@ -104,7 +104,7 @@ export class Viewport implements IViewport { * `Viewport`. * @param ev The mouse wheel event. */ - public onWheel(ev: WheelEvent) { + public onWheel(ev: WheelEvent): void { if (ev.deltaY === 0) { // Do nothing if it's not a vertical scroll event return; @@ -125,7 +125,7 @@ export class Viewport implements IViewport { * Handles the touchstart event, recording the touch occurred. * @param ev The touch event. */ - public onTouchStart(ev: TouchEvent) { + public onTouchStart(ev: TouchEvent): void { this.lastTouchY = ev.touches[0].pageY; }; @@ -133,7 +133,7 @@ export class Viewport implements IViewport { * Handles the touchmove event, scrolling the viewport if the position shifted. * @param ev The touch event. */ - public onTouchMove(ev: TouchEvent) { + public onTouchMove(ev: TouchEvent): void { let deltaY = this.lastTouchY - ev.touches[0].pageY; this.lastTouchY = ev.touches[0].pageY; if (deltaY === 0) { diff --git a/src/handlers/Clipboard.test.ts b/src/handlers/Clipboard.test.ts index a91ab87c..e5bc3581 100644 --- a/src/handlers/Clipboard.test.ts +++ b/src/handlers/Clipboard.test.ts @@ -2,8 +2,8 @@ import { assert } from 'chai'; import * as Terminal from '../xterm'; import * as Clipboard from './Clipboard'; -describe('evaluatePastedTextProcessing', function () { - it('should replace carriage return + line feed with line feed on windows', function () { +describe('evaluatePastedTextProcessing', () => { + it('should replace carriage return + line feed with line feed on windows', () => { const pastedText = 'foo\r\nbar\r\n'; const processedText = Clipboard.prepareTextForTerminal(pastedText, false); const windowsProcessedText = Clipboard.prepareTextForTerminal(pastedText, true); diff --git a/src/handlers/Clipboard.ts b/src/handlers/Clipboard.ts index aa1c1400..ac9a7b87 100644 --- a/src/handlers/Clipboard.ts +++ b/src/handlers/Clipboard.ts @@ -10,7 +10,7 @@ import { ITerminal, ISelectionManager } from '../Interfaces'; interface IWindow extends Window { clipboardData?: { getData(format: string): string; - setData(format: string, data: string); + setData(format: string, data: string): void; }; } @@ -31,7 +31,7 @@ export function prepareTextForTerminal(text: string, isMSWindows: boolean): stri * Binds copy functionality to the given terminal. * @param {ClipboardEvent} ev The original copy event to be handled */ -export function copyHandler(ev: ClipboardEvent, term: ITerminal, selectionManager: ISelectionManager) { +export function copyHandler(ev: ClipboardEvent, term: ITerminal, selectionManager: ISelectionManager): void { if (term.browser.isMSIE) { window.clipboardData.setData('Text', selectionManager.selectionText); } else { @@ -47,18 +47,17 @@ export function copyHandler(ev: ClipboardEvent, term: ITerminal, selectionManage * @param {ClipboardEvent} ev The original paste event to be handled * @param {Terminal} term The terminal on which to apply the handled paste event */ -export function pasteHandler(ev: ClipboardEvent, term: ITerminal) { +export function pasteHandler(ev: ClipboardEvent, term: ITerminal): void { ev.stopPropagation(); let text: string; - let dispatchPaste = function(text) { + let dispatchPaste = function(text: string): void { text = prepareTextForTerminal(text, term.browser.isMSWindows); term.handler(text); term.textarea.value = ''; term.emit('paste', text); - - return term.cancel(ev); + term.cancel(ev); }; if (term.browser.isMSIE) { @@ -79,7 +78,7 @@ export function pasteHandler(ev: ClipboardEvent, term: ITerminal) { * @param ev The original right click event to be handled. * @param textarea The terminal's textarea. */ -export function moveTextAreaUnderMouseCursor(ev: MouseEvent, textarea: HTMLTextAreaElement) { +export function moveTextAreaUnderMouseCursor(ev: MouseEvent, textarea: HTMLTextAreaElement): void { // Bring textarea at the cursor position textarea.style.position = 'fixed'; textarea.style.width = '20px'; @@ -91,7 +90,7 @@ export function moveTextAreaUnderMouseCursor(ev: MouseEvent, textarea: HTMLTextA textarea.focus(); // Reset the terminal textarea's styling - setTimeout(function () { + setTimeout(() => { textarea.style.position = null; textarea.style.width = null; textarea.style.height = null; @@ -107,7 +106,7 @@ export function moveTextAreaUnderMouseCursor(ev: MouseEvent, textarea: HTMLTextA * @param textarea The terminal's textarea. * @param selectionManager The terminal's selection manager. */ -export function rightClickHandler(ev: MouseEvent, textarea: HTMLTextAreaElement, selectionManager: ISelectionManager) { +export function rightClickHandler(ev: MouseEvent, textarea: HTMLTextAreaElement, selectionManager: ISelectionManager): void { moveTextAreaUnderMouseCursor(ev, textarea); // Get textarea ready to copy from the context menu diff --git a/src/utils/Generic.ts b/src/utils/Generic.ts index ce09c1be..98072326 100644 --- a/src/utils/Generic.ts +++ b/src/utils/Generic.ts @@ -9,6 +9,6 @@ * @param {Array} array The array to search for the given element. * @param {Object} el The element to look for into the array */ -export function contains(arr: any[], el: any) { +export function contains(arr: any[], el: any): boolean { return arr.indexOf(el) >= 0; }; diff --git a/tslint.json b/tslint.json index f98fd4c4..4d4d4aa5 100644 --- a/tslint.json +++ b/tslint.json @@ -9,6 +9,11 @@ true, "spaces" ], + "typedef": [ + true, + "call-signature", + "parameter" + ], "eofline": true, "no-eval": true, "no-internal-module": true, From 2878d978c4e38473ab3843e6c3085a7e24e47fae Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 5 Aug 2017 21:06:04 -0700 Subject: [PATCH 013/378] Fix lint --- src/Interfaces.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Interfaces.ts b/src/Interfaces.ts index e24fbd5b..506032a1 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -46,7 +46,7 @@ export interface ITerminal extends IEventEmitter { log(text: string): void; reset(): void; showCursor(): void; - blankLine(cur?: boolean, isWrapped?: boolean); + blankLine(cur?: boolean, isWrapped?: boolean): [number, string, number]; } /** From ae3169e6ae12605e9b966182c2b1141c216d6ed4 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 5 Aug 2017 21:11:16 -0700 Subject: [PATCH 014/378] Add CharData and LineData --- src/Buffer.ts | 9 +++++---- src/InputHandler.ts | 7 ++++--- src/Interfaces.ts | 10 +++++----- src/SelectionManager.test.ts | 9 +++++---- src/SelectionManager.ts | 5 +++-- src/Terminal.ts | 14 +++++++------- src/Types.ts | 3 +++ src/utils/TestUtils.ts | 5 +++-- 8 files changed, 35 insertions(+), 27 deletions(-) diff --git a/src/Buffer.ts b/src/Buffer.ts index 58cf8a8e..9a28438d 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -4,6 +4,7 @@ import { ITerminal, IBuffer } from './Interfaces'; import { CircularList } from './utils/CircularList'; +import { LineData, CharData } from './Types'; /** * This class represents a terminal buffer (an internal state of the terminal), where the @@ -13,7 +14,7 @@ import { CircularList } from './utils/CircularList'; * - scroll position */ export class Buffer implements IBuffer { - private _lines: CircularList<[number, string, number][]>; + private _lines: CircularList; public ydisp: number; public ybase: number; @@ -39,7 +40,7 @@ export class Buffer implements IBuffer { this.clear(); } - public get lines(): CircularList<[number, string, number][]> { + public get lines(): CircularList { return this._lines; } @@ -60,7 +61,7 @@ export class Buffer implements IBuffer { this.scrollBottom = 0; this.scrollTop = 0; this.tabs = {}; - this._lines = new CircularList<[number, string, number][]>(this._terminal.options.scrollback); + this._lines = new CircularList(this._terminal.options.scrollback); this.scrollBottom = this._terminal.rows - 1; } @@ -72,7 +73,7 @@ export class Buffer implements IBuffer { // Deal with columns increasing (we don't do anything when columns reduce) if (this._terminal.cols < newCols) { - const ch: [number, string, number] = [this._terminal.defAttr, ' ', 1]; // does xterm use the default attr? + const ch: CharData = [this._terminal.defAttr, ' ', 1]; // does xterm use the default attr? for (let i = 0; i < this._lines.length; i++) { if (this._lines.get(i) === undefined) { this._lines.set(i, this._terminal.blankLine()); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 2b4d01d0..91160134 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -5,6 +5,7 @@ import { IInputHandler, ITerminal, IInputHandlingTerminal } from './Interfaces'; import { C0 } from './EscapeSequences'; import { DEFAULT_CHARSET } from './Charsets'; +import { CharData } from './Types'; /** * The terminal's standard implementation of IInputHandler, this handles all @@ -194,7 +195,7 @@ export class InputHandler implements IInputHandler { const row = this._terminal.buffer.y + this._terminal.buffer.ybase; let j = this._terminal.buffer.x; - const ch: [number, string, number] = [this._terminal.eraseAttr(), ' ', 1]; // xterm + const ch: CharData = [this._terminal.eraseAttr(), ' ', 1]; // xterm while (param-- && j < this._terminal.cols) { this._terminal.buffer.lines.get(row).splice(j++, 0, ch); @@ -510,7 +511,7 @@ export class InputHandler implements IInputHandler { } const row = this._terminal.buffer.y + this._terminal.buffer.ybase; - const ch: [number, string, number] = [this._terminal.eraseAttr(), ' ', 1]; // xterm + const ch: CharData = [this._terminal.eraseAttr(), ' ', 1]; // xterm while (param--) { this._terminal.buffer.lines.get(row).splice(this._terminal.buffer.x, 1); @@ -558,7 +559,7 @@ export class InputHandler implements IInputHandler { const row = this._terminal.buffer.y + this._terminal.buffer.ybase; let j = this._terminal.buffer.x; - const ch: [number, string, number] = [this._terminal.eraseAttr(), ' ', 1]; // xterm + const ch: CharData = [this._terminal.eraseAttr(), ' ', 1]; // xterm while (param-- && j < this._terminal.cols) { this._terminal.buffer.lines.get(row)[j++] = ch; diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 506032a1..63058f09 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -3,7 +3,7 @@ */ import { ILinkMatcherOptions } from './Interfaces'; -import { LinkMatcherHandler, LinkMatcherValidationCallback, Charset } from './Types'; +import { LinkMatcherHandler, LinkMatcherValidationCallback, Charset, LineData } from './Types'; export interface IBrowser { isNode: boolean; @@ -46,7 +46,7 @@ export interface ITerminal extends IEventEmitter { log(text: string): void; reset(): void; showCursor(): void; - blankLine(cur?: boolean, isWrapped?: boolean): [number, string, number]; + blankLine(cur?: boolean, isWrapped?: boolean): LineData; } /** @@ -97,7 +97,7 @@ export interface IInputHandlingTerminal extends IEventEmitter { eraseRight(x: number, y: number): void; eraseLine(y: number): void; eraseLeft(x: number, y: number): void; - blankLine(cur?: boolean, isWrapped?: boolean): [number, string, number][]; + blankLine(cur?: boolean, isWrapped?: boolean): LineData; prevStop(x?: number): number; is(term: string): boolean; send(data: string): void; @@ -134,7 +134,7 @@ export interface ITerminalOptions { } export interface IBuffer { - lines: ICircularList<[number, string, number][]>; + lines: ICircularList; ydisp: number; ybase: number; y: number; @@ -166,7 +166,7 @@ export interface ISelectionManager { disable(): void; enable(): void; - setBuffer(buffer: ICircularList<[number, string, number][]>): void; + setBuffer(buffer: ICircularList): void; setSelection(row: number, col: number, length: number): void; } diff --git a/src/SelectionManager.test.ts b/src/SelectionManager.test.ts index f640d424..a39c4dda 100644 --- a/src/SelectionManager.test.ts +++ b/src/SelectionManager.test.ts @@ -10,11 +10,12 @@ import { SelectionManager } from './SelectionManager'; import { SelectionModel } from './SelectionModel'; import { BufferSet } from './BufferSet'; import { MockTerminal } from './utils/TestUtils'; +import { LineData } from './Types'; class TestSelectionManager extends SelectionManager { constructor( terminal: ITerminal, - buffer: ICircularList<[number, string, number][]>, + buffer: ICircularList, rowContainer: HTMLElement, charMeasure: CharMeasure ) { @@ -38,7 +39,7 @@ describe('SelectionManager', () => { let document: Document; let terminal: ITerminal; - let bufferLines: ICircularList<[number, string, number][]>; + let bufferLines: ICircularList; let rowContainer: HTMLElement; let selectionManager: TestSelectionManager; @@ -57,8 +58,8 @@ describe('SelectionManager', () => { selectionManager = new TestSelectionManager(terminal, bufferLines, rowContainer, null); }); - function stringToRow(text: string): [number, string, number][] { - let result: [number, string, number][] = []; + function stringToRow(text: string): LineData { + let result: LineData = []; for (let i = 0; i < text.length; i++) { result.push([0, text.charAt(i), 1]); } diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index e3770c06..781a8eda 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -10,6 +10,7 @@ import { EventEmitter } from './EventEmitter'; import { ITerminal, ICircularList, ISelectionManager } from './Interfaces'; import { SelectionModel } from './SelectionModel'; import { translateBufferLineToString } from './utils/BufferLine'; +import { LineData } from './Types'; /** * The number of pixels the mouse needs to be above or below the viewport in @@ -101,7 +102,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager constructor( private _terminal: ITerminal, - private _buffer: ICircularList<[number, string, number][]>, + private _buffer: ICircularList, private _rowContainer: HTMLElement, private _charMeasure: CharMeasure ) { @@ -150,7 +151,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager * switched in or out. * @param buffer The active buffer. */ - public setBuffer(buffer: ICircularList<[number, string, number][]>): void { + public setBuffer(buffer: ICircularList): void { this._buffer = buffer; this.clearSelection(); } diff --git a/src/Terminal.ts b/src/Terminal.ts index 3431df7f..360c2b7e 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -29,7 +29,7 @@ import * as Mouse from './utils/Mouse'; import { CHARSETS } from './Charsets'; import { getRawByteCoords } from './utils/Mouse'; import { translateBufferLineToString } from './utils/BufferLine'; -import { CustomKeyEventHandler, Charset, LinkMatcherHandler, LinkMatcherValidationCallback } from './Types'; +import { CustomKeyEventHandler, Charset, LinkMatcherHandler, LinkMatcherValidationCallback, CharData, LineData } from './Types'; import { ITerminal, IBrowser, ITerminalOptions, IInputHandlingTerminal, ILinkMatcherOptions } from './Interfaces'; // Declare for RequireJS in loadAddon @@ -2056,7 +2056,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT if (!line) { return; } - const ch: [number, string, number] = [this.eraseAttr(), ' ', 1]; // xterm + const ch: CharData = [this.eraseAttr(), ' ', 1]; // xterm for (; x < this.cols; x++) { line[x] = ch; } @@ -2073,7 +2073,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT if (!line) { return; } - const ch: [number, string, number] = [this.eraseAttr(), ' ', 1]; // xterm + const ch: CharData = [this.eraseAttr(), ' ', 1]; // xterm x++; while (x--) { line[x] = ch; @@ -2114,11 +2114,11 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * @param {boolean} cur First bunch of data for each "blank" character. * @param {boolean} isWrapped Whether the new line is wrapped from the previous line. */ - public blankLine(cur?: boolean, isWrapped?: boolean): [number, string, number][] { + public blankLine(cur?: boolean, isWrapped?: boolean): LineData { const attr = cur ? this.eraseAttr() : this.defAttr; - const ch = [attr, ' ', 1]; // width defaults to 1 halfwidth character - const line = []; + const ch: CharData = [attr, ' ', 1]; // width defaults to 1 halfwidth character + const line: LineData = []; // TODO: It is not ideal that this is a property on an array, a buffer line // class should be added that will hold this data and other useful functions. @@ -2137,7 +2137,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * If cur return the back color xterm feature attribute. Else return defAttr. * @param cur */ - public ch(cur?: boolean): [number, string, number] { + public ch(cur?: boolean): CharData { return cur ? [this.eraseAttr(), ' ', 1] : [this.defAttr, ' ', 1]; } diff --git a/src/Types.ts b/src/Types.ts index 34c6d94a..0753d80e 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -15,3 +15,6 @@ export type LinkMatcherValidationCallback = (uri: string, element: HTMLElement, export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean; export type Charset = {[key: string]: string}; + +export type CharData = [number, string, number]; +export type LineData = CharData[]; diff --git a/src/utils/TestUtils.ts b/src/utils/TestUtils.ts index 9fc929fe..51caa8fe 100644 --- a/src/utils/TestUtils.ts +++ b/src/utils/TestUtils.ts @@ -1,4 +1,5 @@ import { ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager, ITerminalOptions, IListenerType } from '../Interfaces'; +import { LineData } from '../Types'; export class MockTerminal implements ITerminal { options: ITerminalOptions = {}; @@ -47,8 +48,8 @@ export class MockTerminal implements ITerminal { showCursor(): void { throw new Error('Method not implemented.'); } - blankLine(cur?: boolean, isWrapped?: boolean): [number, string, number][] { - const line: [number, string, number][] = []; + blankLine(cur?: boolean, isWrapped?: boolean): LineData { + const line: LineData = []; for (let i = 0; i < this.cols; i++) { line.push([0, ' ', 1]); } From e0809d526ef9056b4f72f565b98b719834e3fc1a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 6 Aug 2017 00:57:54 -0700 Subject: [PATCH 015/378] Type InputHandler tests --- src/Buffer.test.ts | 1 + src/BufferSet.test.ts | 1 + src/CompositionHelper.test.ts | 4 + src/EventEmitter.test.ts | 4 + src/InputHandler.test.ts | 60 ++++++------ src/InputHandler.ts | 1 - src/Linkifier.test.ts | 1 + src/SelectionManager.test.ts | 1 + src/SelectionModel.test.ts | 1 + src/Viewport.test.ts | 4 + src/utils/CharMeasure.test.ts | 1 + src/utils/CircularList.test.ts | 4 + src/utils/CircularList.ts | 1 + src/utils/DomElementObjectPool.test.ts | 4 + src/utils/TestUtils.ts | 129 ++++++++++++++++++++++++- src/xterm.ts | 4 + 16 files changed, 189 insertions(+), 32 deletions(-) diff --git a/src/Buffer.test.ts b/src/Buffer.test.ts index 1e5e2c3b..99bd6d0f 100644 --- a/src/Buffer.test.ts +++ b/src/Buffer.test.ts @@ -1,6 +1,7 @@ /** * @license MIT */ + import { assert } from 'chai'; import { ITerminal } from './Interfaces'; import { Buffer } from './Buffer'; diff --git a/src/BufferSet.test.ts b/src/BufferSet.test.ts index 78a19351..97c07995 100644 --- a/src/BufferSet.test.ts +++ b/src/BufferSet.test.ts @@ -1,6 +1,7 @@ /** * @license MIT */ + import { assert } from 'chai'; import { ITerminal } from './Interfaces'; import { BufferSet } from './BufferSet'; diff --git a/src/CompositionHelper.test.ts b/src/CompositionHelper.test.ts index 70b23183..0adb1719 100644 --- a/src/CompositionHelper.test.ts +++ b/src/CompositionHelper.test.ts @@ -1,3 +1,7 @@ +/** + * @license MIT + */ + import { assert } from 'chai'; import { CompositionHelper } from './CompositionHelper'; diff --git a/src/EventEmitter.test.ts b/src/EventEmitter.test.ts index 7ae381c8..d61f994a 100644 --- a/src/EventEmitter.test.ts +++ b/src/EventEmitter.test.ts @@ -1,3 +1,7 @@ +/** + * @license MIT + */ + import { assert } from 'chai'; import { EventEmitter } from './EventEmitter'; diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index b6b218df..043b7919 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -1,12 +1,18 @@ +/** + * @license MIT + */ + import { assert } from 'chai'; import { InputHandler } from './InputHandler'; import { wcwidth } from './InputHandler'; +import { MockInputHandlingTerminal } from './utils/TestUtils'; describe('InputHandler', () => { describe('save and restore cursor', () => { - let terminal = { buffer: { x: 1, y: 2 } }; - // TODO: Create proper mock IInputHandlingTerminal test util object - let inputHandler = new InputHandler(terminal); + let terminal = new MockInputHandlingTerminal(); + terminal.buffer.x = 1; + terminal.buffer.y = 2; + let inputHandler = new InputHandler(terminal); // Save cursor position inputHandler.saveCursor([]); assert.equal(terminal.buffer.x, 1); @@ -21,46 +27,42 @@ describe('InputHandler', () => { }); describe('setCursorStyle', () => { it('should call Terminal.setOption with correct params', () => { - let options = {}; - let terminal = { - setOption: (option, value) => options[option] = value - }; - let inputHandler = new InputHandler(terminal); + let terminal = new MockInputHandlingTerminal(); + let inputHandler = new InputHandler(terminal); inputHandler.setCursorStyle([0]); - assert.equal(options['cursorStyle'], 'block'); - assert.equal(options['cursorBlink'], true); + assert.equal(terminal.options['cursorStyle'], 'block'); + assert.equal(terminal.options['cursorBlink'], true); - options = {}; + terminal.options = {}; inputHandler.setCursorStyle([1]); - assert.equal(options['cursorStyle'], 'block'); - assert.equal(options['cursorBlink'], true); + assert.equal(terminal.options['cursorStyle'], 'block'); + assert.equal(terminal.options['cursorBlink'], true); - options = {}; + terminal.options = {}; inputHandler.setCursorStyle([2]); - assert.equal(options['cursorStyle'], 'block'); - assert.equal(options['cursorBlink'], false); + assert.equal(terminal.options['cursorStyle'], 'block'); + assert.equal(terminal.options['cursorBlink'], false); - options = {}; + terminal.options = {}; inputHandler.setCursorStyle([3]); - assert.equal(options['cursorStyle'], 'underline'); - assert.equal(options['cursorBlink'], true); + assert.equal(terminal.options['cursorStyle'], 'underline'); + assert.equal(terminal.options['cursorBlink'], true); - options = {}; + terminal.options = {}; inputHandler.setCursorStyle([4]); - assert.equal(options['cursorStyle'], 'underline'); - assert.equal(options['cursorBlink'], false); + assert.equal(terminal.options['cursorStyle'], 'underline'); + assert.equal(terminal.options['cursorBlink'], false); - options = {}; + terminal.options = {}; inputHandler.setCursorStyle([5]); - assert.equal(options['cursorStyle'], 'bar'); - assert.equal(options['cursorBlink'], true); + assert.equal(terminal.options['cursorStyle'], 'bar'); + assert.equal(terminal.options['cursorBlink'], true); - options = {}; + terminal.options = {}; inputHandler.setCursorStyle([6]); - assert.equal(options['cursorStyle'], 'bar'); - assert.equal(options['cursorBlink'], false); - + assert.equal(terminal.options['cursorStyle'], 'bar'); + assert.equal(terminal.options['cursorBlink'], false); }); }); }); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 91160134..5362cae9 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -15,7 +15,6 @@ import { CharData } from './Types'; * each function's header comment. */ export class InputHandler implements IInputHandler { - // TODO: We want to type _terminal when it's pulled into TS constructor(private _terminal: IInputHandlingTerminal) { } public addChar(char: string, code: number): void { diff --git a/src/Linkifier.test.ts b/src/Linkifier.test.ts index 414238f9..a4ed70db 100644 --- a/src/Linkifier.test.ts +++ b/src/Linkifier.test.ts @@ -1,6 +1,7 @@ /** * @license MIT */ + import jsdom = require('jsdom'); import { assert } from 'chai'; import { ITerminal, ILinkifier } from './Interfaces'; diff --git a/src/SelectionManager.test.ts b/src/SelectionManager.test.ts index a39c4dda..698cda7e 100644 --- a/src/SelectionManager.test.ts +++ b/src/SelectionManager.test.ts @@ -1,6 +1,7 @@ /** * @license MIT */ + import jsdom = require('jsdom'); import { assert } from 'chai'; import { ITerminal, ICircularList } from './Interfaces'; diff --git a/src/SelectionModel.test.ts b/src/SelectionModel.test.ts index 696d12f0..9da84478 100644 --- a/src/SelectionModel.test.ts +++ b/src/SelectionModel.test.ts @@ -1,6 +1,7 @@ /** * @license MIT */ + import { assert } from 'chai'; import { ITerminal } from './Interfaces'; import { SelectionModel } from './SelectionModel'; diff --git a/src/Viewport.test.ts b/src/Viewport.test.ts index 7c89da29..8c7a914b 100644 --- a/src/Viewport.test.ts +++ b/src/Viewport.test.ts @@ -1,3 +1,7 @@ +/** + * @license MIT + */ + import { assert } from 'chai'; import { Viewport } from './Viewport'; import {BufferSet} from './BufferSet'; diff --git a/src/utils/CharMeasure.test.ts b/src/utils/CharMeasure.test.ts index fd4954e0..68e1b6a7 100644 --- a/src/utils/CharMeasure.test.ts +++ b/src/utils/CharMeasure.test.ts @@ -1,6 +1,7 @@ /** * @license MIT */ + import jsdom = require('jsdom'); import { assert } from 'chai'; import { ICharMeasure, ITerminal } from '../Interfaces'; diff --git a/src/utils/CircularList.test.ts b/src/utils/CircularList.test.ts index aaa4a0d9..e27e7f7a 100644 --- a/src/utils/CircularList.test.ts +++ b/src/utils/CircularList.test.ts @@ -1,3 +1,7 @@ +/** + * @license MIT + */ + import { assert } from 'chai'; import { CircularList } from './CircularList'; diff --git a/src/utils/CircularList.ts b/src/utils/CircularList.ts index 54850ab7..373d78b0 100644 --- a/src/utils/CircularList.ts +++ b/src/utils/CircularList.ts @@ -4,6 +4,7 @@ * @module xterm/utils/CircularList * @license MIT */ + import { EventEmitter } from '../EventEmitter'; import { ICircularList } from '../Interfaces'; diff --git a/src/utils/DomElementObjectPool.test.ts b/src/utils/DomElementObjectPool.test.ts index 7298f192..5a5d8a78 100644 --- a/src/utils/DomElementObjectPool.test.ts +++ b/src/utils/DomElementObjectPool.test.ts @@ -1,3 +1,7 @@ +/** + * @license MIT + */ + import { assert } from 'chai'; import { DomElementObjectPool } from './DomElementObjectPool'; diff --git a/src/utils/TestUtils.ts b/src/utils/TestUtils.ts index 51caa8fe..fcff2dcd 100644 --- a/src/utils/TestUtils.ts +++ b/src/utils/TestUtils.ts @@ -1,4 +1,8 @@ -import { ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager, ITerminalOptions, IListenerType } from '../Interfaces'; +/** + * @license MIT + */ + +import { ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager, ITerminalOptions, IListenerType, IInputHandlingTerminal, IViewport, ICircularList } from '../Interfaces'; import { LineData } from '../Types'; export class MockTerminal implements ITerminal { @@ -20,7 +24,6 @@ export class MockTerminal implements ITerminal { scrollback: number; buffers: IBufferSet; buffer: IBuffer; - handler(data: string): void { throw new Error('Method not implemented.'); } @@ -56,3 +59,125 @@ export class MockTerminal implements ITerminal { return line; } } + +export class MockInputHandlingTerminal implements IInputHandlingTerminal { + element: HTMLElement; + options: ITerminalOptions = {}; + cols: number; + rows: number; + charset: { [key: string]: string; }; + gcharset: number; + glevel: number; + charsets: { [key: string]: string; }[]; + applicationKeypad: boolean; + applicationCursor: boolean; + originMode: boolean; + insertMode: boolean; + wraparoundMode: 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 = new MockBuffer(); + viewport: IViewport; + selectionManager: ISelectionManager; + focus(): void { + throw new Error('Method not implemented.'); + } + convertEol: boolean; + updateRange(y: number): void { + throw new Error('Method not implemented.'); + } + scroll(isWrapped?: boolean): void { + throw new Error('Method not implemented.'); + } + nextStop(x?: number): number { + throw new Error('Method not implemented.'); + } + setgLevel(g: number): void { + throw new Error('Method not implemented.'); + } + eraseAttr() { + throw new Error('Method not implemented.'); + } + eraseRight(x: number, y: number): void { + throw new Error('Method not implemented.'); + } + eraseLine(y: number): void { + throw new Error('Method not implemented.'); + } + eraseLeft(x: number, y: number): void { + throw new Error('Method not implemented.'); + } + blankLine(cur?: boolean, isWrapped?: boolean): [number, string, number][] { + throw new Error('Method not implemented.'); + } + prevStop(x?: number): number { + throw new Error('Method not implemented.'); + } + is(term: string): boolean { + throw new Error('Method not implemented.'); + } + send(data: string): void { + throw new Error('Method not implemented.'); + } + setgCharset(g: number, charset: { [key: string]: string; }): void { + throw new Error('Method not implemented.'); + } + resize(x: number, y: number): void { + throw new Error('Method not implemented.'); + } + log(text: string, data?: any): void { + throw new Error('Method not implemented.'); + } + reset(): void { + throw new Error('Method not implemented.'); + } + showCursor(): void { + throw new Error('Method not implemented.'); + } + refresh(start: number, end: number): void { + throw new Error('Method not implemented.'); + } + matchColor(r1: number, g1: number, b1: number) { + throw new Error('Method not implemented.'); + } + error(text: string, data?: any): void { + throw new Error('Method not implemented.'); + } + setOption(key: string, value: any): void { + this.options[key] = value; + } + on(type: string, listener: IListenerType): void { + throw new Error('Method not implemented.'); + } + off(type: string, listener: IListenerType): void { + throw new Error('Method not implemented.'); + } + emit(type: string, data?: any): void { + throw new Error('Method not implemented.'); + } +} + +export class MockBuffer implements IBuffer { + lines: ICircularList<[number, string, number][]>; + ydisp: number; + ybase: number; + y: number; + x: number; + tabs: any; + scrollBottom: number; + scrollTop: number; + savedY: number; + savedX: number; +} diff --git a/src/xterm.ts b/src/xterm.ts index 0c2b7319..75751865 100644 --- a/src/xterm.ts +++ b/src/xterm.ts @@ -1,3 +1,7 @@ +/** + * @license MIT + */ + import { Terminal } from './Terminal'; module.exports = Terminal; From 7cd8d1ec7472a76521a9af1603f44f7cd6d7d2ab Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 6 Aug 2017 01:42:41 -0700 Subject: [PATCH 016/378] Resolve todos --- src/Interfaces.ts | 4 +- src/Terminal.ts | 33 +- src/utils/TestUtils.ts | 4 +- src/xterm.js | 2389 ---------------------------------------- 4 files changed, 19 insertions(+), 2411 deletions(-) delete mode 100644 src/xterm.js diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 63058f09..55317fbc 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -93,7 +93,7 @@ export interface IInputHandlingTerminal extends IEventEmitter { scroll(isWrapped?: boolean): void; nextStop(x?: number): number; setgLevel(g: number): void; - eraseAttr(): any; + eraseAttr(): number; eraseRight(x: number, y: number): void; eraseLine(y: number): void; eraseLeft(x: number, y: number): void; @@ -107,7 +107,7 @@ export interface IInputHandlingTerminal extends IEventEmitter { reset(): void; showCursor(): void; refresh(start: number, end: number): void; - matchColor(r1: number, g1: number, b1: number): any; + matchColor(r1: number, g1: number, b1: number): number; error(text: string, data?: any): void; setOption(key: string, value: any): void; } diff --git a/src/Terminal.ts b/src/Terminal.ts index 360c2b7e..c1fd1e7e 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -182,11 +182,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT private compositionView: HTMLElement; private charSizeStyleElement: HTMLStyleElement; - // TODO: This should be removed from the interface, opting for modules to pull - // in Browser for themselves. public browser: IBrowser = Browser; - // TODO: Options should be private, remove from interface in favor of getOption public options: ITerminalOptions; private colors: any; @@ -194,8 +191,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT public cursorState: number; public cursorHidden: boolean; public convertEol: boolean; - // TODO: This is the data queue for send, improve name and documentation - private queue: string; + + private sendDataQueue: string; private customKeyEventHandler: CustomKeyEventHandler; // The ID from a setInterval that tracks the blink animation. This animation // is done in JS due to a Chromium bug with CSS animations that thrashed the @@ -339,7 +336,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.cursorState = 0; this.cursorHidden = false; - this.queue = ''; + this.sendDataQueue = ''; this.customKeyEventHandler = null; this.cursorBlinkInterval = null; @@ -402,7 +399,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT /** * back_color_erase feature for xterm. */ - public eraseAttr(): any { + public eraseAttr(): number { // if (this.is('screen')) return this.defAttr; return (this.defAttr & ~0x1ff) | (this.curAttr & 0x1ff); } @@ -1886,14 +1883,14 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * @param {string} data */ public send(data: string): void { - if (!this.queue) { + if (!this.sendDataQueue) { setTimeout(() => { - this.handler(this.queue); - this.queue = ''; + this.handler(this.sendDataQueue); + this.sendDataQueue = ''; }, 1); } - this.queue += data; + this.sendDataQueue += data; } /** @@ -2261,7 +2258,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT // Expose to InputHandler // TODO: Revise when truecolor is introduced. - public matchColor(r1: number, g1: number, b1: number): any { + public matchColor(r1: number, g1: number, b1: number): number { const hash = (r1 << 16) | (g1 << 8) | b1; if (matchColorCache[hash] != null) { @@ -2271,11 +2268,11 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT let ldiff = Infinity; let li = -1; let i = 0; - let c; - let r2; - let g2; - let b2; - let diff; + let c: number[]; + let r2: number; + let g2: number; + let b2: number; + let diff: number; for (; i < vcolors.length; i++) { c = vcolors[i]; @@ -2341,7 +2338,7 @@ function isThirdLevelShift(ev: KeyboardEvent): boolean { return thirdLevelKey && (!ev.keyCode || ev.keyCode > 47); } -const matchColorCache = {}; +const matchColorCache: {[colorRGBHash: number]: number} = {}; // http://stackoverflow.com/questions/1633828 const matchColorDistance = function(r1: number, g1: number, b1: number, r2: number, g2: number, b2: number): number { diff --git a/src/utils/TestUtils.ts b/src/utils/TestUtils.ts index fcff2dcd..159da055 100644 --- a/src/utils/TestUtils.ts +++ b/src/utils/TestUtils.ts @@ -107,7 +107,7 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal { setgLevel(g: number): void { throw new Error('Method not implemented.'); } - eraseAttr() { + eraseAttr(): number { throw new Error('Method not implemented.'); } eraseRight(x: number, y: number): void { @@ -149,7 +149,7 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal { refresh(start: number, end: number): void { throw new Error('Method not implemented.'); } - matchColor(r1: number, g1: number, b1: number) { + matchColor(r1: number, g1: number, b1: number): number { throw new Error('Method not implemented.'); } error(text: string, data?: any): void { diff --git a/src/xterm.js b/src/xterm.js deleted file mode 100644 index e9e3465e..00000000 --- a/src/xterm.js +++ /dev/null @@ -1,2389 +0,0 @@ -/** - * xterm.js: xterm, in the browser - * Originally forked from (with the author's permission): - * Fabrice Bellard's javascript vt100 for jslinux: - * http://bellard.org/jslinux/ - * Copyright (c) 2011 Fabrice Bellard - * The original design remains. The terminal itself - * has been extended to include xterm CSI codes, among - * other features. - * @license MIT - */ - -import { BufferSet } from './BufferSet'; -import { CompositionHelper } from './CompositionHelper'; -import { EventEmitter } from './EventEmitter'; -import { Viewport } from './Viewport'; -import { rightClickHandler, moveTextAreaUnderMouseCursor, pasteHandler, copyHandler } from './handlers/Clipboard'; -import { CircularList } from './utils/CircularList'; -import { C0 } from './EscapeSequences'; -import { InputHandler } from './InputHandler'; -import { Parser } from './Parser'; -import { Renderer } from './Renderer'; -import { Linkifier } from './Linkifier'; -import { SelectionManager } from './SelectionManager'; -import { CharMeasure } from './utils/CharMeasure'; -import * as Browser from './utils/Browser'; -import * as Mouse from './utils/Mouse'; -import { CHARSETS } from './Charsets'; -import { getRawByteCoords } from './utils/Mouse'; -import { translateBufferLineToString } from './utils/BufferLine'; - -/** - * Terminal Emulation References: - * http://vt100.net/ - * http://invisible-island.net/xterm/ctlseqs/ctlseqs.txt - * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html - * http://invisible-island.net/vttest/ - * http://www.inwap.com/pdp10/ansicode.txt - * http://linux.die.net/man/4/console_codes - * http://linux.die.net/man/7/urxvt - */ - -// Let it work inside Node.js for automated testing purposes. -var document = (typeof window != 'undefined') ? window.document : null; - -/** - * The amount of write requests to queue before sending an XOFF signal to the - * pty process. This number must be small in order for ^C and similar sequences - * to be responsive. - */ -var WRITE_BUFFER_PAUSE_THRESHOLD = 5; - -/** - * The number of writes to perform in a single batch before allowing the - * renderer to catch up with a 0ms setTimeout. - */ -var WRITE_BATCH_SIZE = 300; - -/** - * The time between cursor blinks. This is driven by JS rather than a CSS - * animation due to a bug in Chromium that causes it to use excessive CPU time. - * See https://github.com/Microsoft/vscode/issues/22900 - */ -var CURSOR_BLINK_INTERVAL = 600; - -/** - * Terminal - */ - -/** - * Creates a new `Terminal` object. - * - * @param {object} options An object containing a set of options, the available options are: - * - `cursorBlink` (boolean): Whether the terminal cursor blinks - * - `cols` (number): The number of columns of the terminal (horizontal size) - * - `rows` (number): The number of rows of the terminal (vertical size) - * - * @public - * @class Xterm Xterm - * @alias module:xterm/src/xterm - */ -function Terminal(options) { - var self = this; - - if (!(this instanceof Terminal)) { - return new Terminal(arguments[0], arguments[1], arguments[2]); - } - - self.browser = Browser; - self.cancel = Terminal.cancel; - - EventEmitter.call(this); - - if (typeof options === 'number') { - options = { - cols: arguments[0], - rows: arguments[1], - handler: arguments[2] - }; - } - - options = options || {}; - - - Object.keys(Terminal.defaults).forEach(function(key) { - if (options[key] == null) { - options[key] = Terminal.options[key]; - - if (Terminal[key] !== Terminal.defaults[key]) { - options[key] = Terminal[key]; - } - } - self[key] = options[key]; - }); - - if (options.colors.length === 8) { - options.colors = options.colors.concat(Terminal._colors.slice(8)); - } else if (options.colors.length === 16) { - options.colors = options.colors.concat(Terminal._colors.slice(16)); - } else if (options.colors.length === 10) { - options.colors = options.colors.slice(0, -2).concat( - Terminal._colors.slice(8, -2), options.colors.slice(-2)); - } else if (options.colors.length === 18) { - options.colors = options.colors.concat( - Terminal._colors.slice(16, -2), options.colors.slice(-2)); - } - this.colors = options.colors; - - this.options = options; - - // this.context = options.context || window; - // this.document = options.document || document; - this.parent = options.body || options.parent || ( - document ? document.getElementsByTagName('body')[0] : null - ); - - this.cols = options.cols || options.geometry[0]; - this.rows = options.rows || options.geometry[1]; - this.geometry = [this.cols, this.rows]; - - if (options.handler) { - this.on('data', options.handler); - } - - this.cursorState = 0; - this.cursorHidden = false; - this.convertEol; - this.queue = ''; - this.customKeyEventHandler = null; - this.cursorBlinkInterval = null; - - // modes - this.applicationKeypad = false; - this.applicationCursor = false; - this.originMode = false; - this.insertMode = false; - this.wraparoundMode = true; // defaults: xterm - true, vt100 - false - - // charset - this.charset = null; - this.gcharset = null; - this.glevel = 0; - this.charsets = [null]; - - // mouse properties - this.decLocator; - this.x10Mouse; - this.vt200Mouse; - this.vt300Mouse; - this.normalMouse; - this.mouseEvents; - this.sendFocus; - this.utfMouse; - this.sgrMouse; - this.urxvtMouse; - - // misc - this.element; - this.children; - this.refreshStart; - this.refreshEnd; - this.savedX; - this.savedY; - this.savedCols; - - // stream - this.readable = true; - this.writable = true; - - this.defAttr = (0 << 18) | (257 << 9) | (256 << 0); - this.curAttr = this.defAttr; - - this.params = []; - this.currentParam = 0; - this.prefix = ''; - this.postfix = ''; - - this.inputHandler = new InputHandler(this); - this.parser = new Parser(this.inputHandler, this); - // Reuse renderer if the Terminal is being recreated via a Terminal.reset call. - this.renderer = this.renderer || null; - this.selectionManager = this.selectionManager || null; - this.linkifier = this.linkifier || new Linkifier(); - - // user input states - this.writeBuffer = []; - this.writeInProgress = false; - - /** - * Whether _xterm.js_ sent XOFF in order to catch up with the pty process. - * This is a distinct state from writeStopped so that if the user requested - * XOFF via ^S that it will not automatically resume when the writeBuffer goes - * below threshold. - */ - this.xoffSentToCatchUp = false; - - /** Whether writing has been stopped as a result of XOFF */ - this.writeStopped = false; - - // leftover surrogate high from previous write invocation - this.surrogate_high = ''; - - // Create the terminal's buffers and set the current buffer - this.buffers = new BufferSet(this); - this.buffer = this.buffers.active; // Convenience shortcut; - this.buffers.on('activate', function (buffer) { - this._terminal.buffer = buffer; - }); - - // Ensure the selection manager has the correct buffer - if (this.selectionManager) { - this.selectionManager.setBuffer(this.buffer.lines); - } - - this.setupStops(); - - // Store if user went browsing history in scrollback - this.userScrolling = false; -} - -inherits(Terminal, EventEmitter); - -/** - * back_color_erase feature for xterm. - */ -Terminal.prototype.eraseAttr = function() { - // if (this.is('screen')) return this.defAttr; - return (this.defAttr & ~0x1ff) | (this.curAttr & 0x1ff); -}; - -/** - * Colors - */ - -// Colors 0-15 -Terminal.tangoColors = [ - // dark: - '#2e3436', - '#cc0000', - '#4e9a06', - '#c4a000', - '#3465a4', - '#75507b', - '#06989a', - '#d3d7cf', - // bright: - '#555753', - '#ef2929', - '#8ae234', - '#fce94f', - '#729fcf', - '#ad7fa8', - '#34e2e2', - '#eeeeec' -]; - -// Colors 0-15 + 16-255 -// Much thanks to TooTallNate for writing this. -Terminal.colors = (function() { - var colors = Terminal.tangoColors.slice() - , r = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff] - , i; - - // 16-231 - i = 0; - for (; i < 216; i++) { - out(r[(i / 36) % 6 | 0], r[(i / 6) % 6 | 0], r[i % 6]); - } - - // 232-255 (grey) - i = 0; - for (; i < 24; i++) { - r = 8 + i * 10; - out(r, r, r); - } - - function out(r, g, b) { - colors.push('#' + hex(r) + hex(g) + hex(b)); - } - - function hex(c) { - c = c.toString(16); - return c.length < 2 ? '0' + c : c; - } - - return colors; -})(); - -Terminal._colors = Terminal.colors.slice(); - -Terminal.vcolors = (function() { - var out = [] - , colors = Terminal.colors - , i = 0 - , color; - - for (; i < 256; i++) { - color = parseInt(colors[i].substring(1), 16); - out.push([ - (color >> 16) & 0xff, - (color >> 8) & 0xff, - color & 0xff - ]); - } - - return out; -})(); - -/** - * Options - */ - -Terminal.defaults = { - colors: Terminal.colors, - theme: 'default', - convertEol: false, - termName: 'xterm', - geometry: [80, 24], - cursorBlink: false, - cursorStyle: 'block', - visualBell: false, - popOnBell: false, - scrollback: 1000, - screenKeys: false, - debug: false, - cancelEvents: false, - disableStdin: false, - useFlowControl: false, - tabStopWidth: 8 - // programFeatures: false, - // focusKeys: false, -}; - -Terminal.options = {}; - -Terminal.focus = null; - -each(keys(Terminal.defaults), function(key) { - Terminal[key] = Terminal.defaults[key]; - Terminal.options[key] = Terminal.defaults[key]; -}); - -/** - * Focus the terminal. Delegates focus handling to the terminal's DOM element. - */ -Terminal.prototype.focus = function() { - return this.textarea.focus(); -}; - -/** - * Retrieves an option's value from the terminal. - * @param {string} key The option key. - */ -Terminal.prototype.getOption = function(key) { - if (!(key in Terminal.defaults)) { - throw new Error('No option with key "' + key + '"'); - } - - if (typeof this.options[key] !== 'undefined') { - return this.options[key]; - } - - return this[key]; -}; - -/** - * Sets an option on the terminal. - * @param {string} key The option key. - * @param {string} value The option value. - */ -Terminal.prototype.setOption = function(key, value) { - if (!(key in Terminal.defaults)) { - throw new Error('No option with key "' + key + '"'); - } - switch (key) { - case 'scrollback': - if (value < this.rows) { - let msg = 'Setting the scrollback value less than the number of rows '; - - msg += `(${this.rows}) is not allowed.`; - - console.warn(msg); - return false; - } - - if (this.options[key] !== value) { - if (this.buffer.lines.length > value) { - const amountToTrim = this.buffer.lines.length - value; - const needsRefresh = (this.buffer.ydisp - amountToTrim < 0); - this.buffer.lines.trimStart(amountToTrim); - this.buffer.ybase = Math.max(this.buffer.ybase - amountToTrim, 0); - this.buffer.ydisp = Math.max(this.buffer.ydisp - amountToTrim, 0); - if (needsRefresh) { - this.refresh(0, this.rows - 1); - } - } - this.buffer.lines.maxLength = value; - this.viewport.syncScrollArea(); - } - break; - } - this[key] = value; - this.options[key] = value; - switch (key) { - case 'cursorBlink': this.setCursorBlinking(value); break; - case 'cursorStyle': - this.element.classList.toggle(`xterm-cursor-style-block`, value === 'block'); - this.element.classList.toggle(`xterm-cursor-style-underline`, value === 'underline'); - this.element.classList.toggle(`xterm-cursor-style-bar`, value === 'bar'); - break; - case 'tabStopWidth': this.setupStops(); break; - } -}; - -Terminal.prototype.restartCursorBlinking = function () { - this.setCursorBlinking(this.options.cursorBlink); -}; - -Terminal.prototype.setCursorBlinking = function (enabled) { - this.element.classList.toggle('xterm-cursor-blink', enabled); - this.clearCursorBlinkingInterval(); - if (enabled) { - var self = this; - this.cursorBlinkInterval = setInterval(function () { - self.element.classList.toggle('xterm-cursor-blink-on'); - }, CURSOR_BLINK_INTERVAL); - } -}; - -Terminal.prototype.clearCursorBlinkingInterval = function () { - this.element.classList.remove('xterm-cursor-blink-on'); - if (this.cursorBlinkInterval) { - clearInterval(this.cursorBlinkInterval); - this.cursorBlinkInterval = null; - } -}; - -/** - * Binds the desired focus behavior on a given terminal object. - * - * @static - */ -Terminal.bindFocus = function (term) { - on(term.textarea, 'focus', function (ev) { - if (term.sendFocus) { - term.send(C0.ESC + '[I'); - } - term.element.classList.add('focus'); - term.showCursor(); - term.restartCursorBlinking.apply(term); - Terminal.focus = term; - term.emit('focus', {terminal: term}); - }); -}; - -/** - * Blur the terminal. Delegates blur handling to the terminal's DOM element. - */ -Terminal.prototype.blur = function() { - return this.textarea.blur(); -}; - -/** - * Binds the desired blur behavior on a given terminal object. - * - * @static - */ -Terminal.bindBlur = function (term) { - on(term.textarea, 'blur', function (ev) { - term.refresh(term.buffer.y, term.buffer.y); - if (term.sendFocus) { - term.send(C0.ESC + '[O'); - } - term.element.classList.remove('focus'); - term.clearCursorBlinkingInterval.apply(term); - Terminal.focus = null; - term.emit('blur', {terminal: term}); - }); -}; - -/** - * Initialize default behavior - */ -Terminal.prototype.initGlobal = function() { - var term = this; - - Terminal.bindKeys(this); - Terminal.bindFocus(this); - Terminal.bindBlur(this); - - // Bind clipboard functionality - on(this.element, 'copy', event => { - // If mouse events are active it means the selection manager is disabled and - // copy should be handled by the host program. - if (!term.hasSelection()) { - return; - } - copyHandler(event, term, this.selectionManager); - }); - const pasteHandlerWrapper = event => pasteHandler(event, term); - on(this.textarea, 'paste', pasteHandlerWrapper); - on(this.element, 'paste', pasteHandlerWrapper); - - // Handle right click context menus - if (term.browser.isFirefox) { - // Firefox doesn't appear to fire the contextmenu event on right click - on(this.element, 'mousedown', event => { - if (event.button == 2) { - rightClickHandler(event, this.textarea, this.selectionManager); - } - }); - } else { - on(this.element, 'contextmenu', event => { - rightClickHandler(event, this.textarea, this.selectionManager); - }); - } - - // Move the textarea under the cursor when middle clicking on Linux to ensure - // middle click to paste selection works. This only appears to work in Chrome - // at the time is writing. - if (term.browser.isLinux) { - // Use auxclick event over mousedown the latter doesn't seem to work. Note - // that the regular click event doesn't fire for the middle mouse button. - on(this.element, 'auxclick', event => { - if (event.button === 1) { - moveTextAreaUnderMouseCursor(event, this.textarea, this.selectionManager); - } - }); - } -}; - -/** - * Apply key handling to the terminal - */ -Terminal.bindKeys = function(term) { - on(term.element, 'keydown', function(ev) { - if (document.activeElement != this) { - return; - } - term.keyDown(ev); - }, true); - - on(term.element, 'keypress', function(ev) { - if (document.activeElement != this) { - return; - } - term.keyPress(ev); - }, true); - - on(term.element, 'keyup', function(ev) { - if (!wasMondifierKeyOnlyEvent(ev)) { - term.focus(term); - } - }, true); - - on(term.textarea, 'keydown', function(ev) { - term.keyDown(ev); - }, true); - - on(term.textarea, 'keypress', function(ev) { - term.keyPress(ev); - // Truncate the textarea's value, since it is not needed - this.value = ''; - }, true); - - on(term.textarea, 'compositionstart', term.compositionHelper.compositionstart.bind(term.compositionHelper)); - on(term.textarea, 'compositionupdate', term.compositionHelper.compositionupdate.bind(term.compositionHelper)); - on(term.textarea, 'compositionend', term.compositionHelper.compositionend.bind(term.compositionHelper)); - term.on('refresh', term.compositionHelper.updateCompositionElements.bind(term.compositionHelper)); - term.on('refresh', function (data) { - term.queueLinkification(data.start, data.end) - }); -}; - - -/** - * Insert the given row to the terminal or produce a new one - * if no row argument is passed. Return the inserted row. - * @param {HTMLElement} row (optional) The row to append to the terminal. - */ -Terminal.prototype.insertRow = function (row) { - if (typeof row != 'object') { - row = document.createElement('div'); - } - - this.rowContainer.appendChild(row); - this.children.push(row); - - return row; -}; - -/** - * Opens the terminal within an element. - * - * @param {HTMLElement} parent The element to create the terminal within. - * @param {boolean} focus Focus the terminal, after it gets instantiated in the DOM - */ -Terminal.prototype.open = function(parent, focus) { - var self=this, i=0, div; - - this.parent = parent || this.parent; - - if (!this.parent) { - throw new Error('Terminal requires a parent element.'); - } - - // Grab global elements - this.context = this.parent.ownerDocument.defaultView; - this.document = this.parent.ownerDocument; - this.body = this.document.getElementsByTagName('body')[0]; - - //Create main element container - this.element = this.document.createElement('div'); - this.element.classList.add('terminal'); - this.element.classList.add('xterm'); - this.element.classList.add('xterm-theme-' + this.theme); - this.element.classList.add(`xterm-cursor-style-${this.options.cursorStyle}`); - this.setCursorBlinking(this.options.cursorBlink); - - this.element.setAttribute('tabindex', 0); - - this.viewportElement = document.createElement('div'); - this.viewportElement.classList.add('xterm-viewport'); - this.element.appendChild(this.viewportElement); - this.viewportScrollArea = document.createElement('div'); - this.viewportScrollArea.classList.add('xterm-scroll-area'); - this.viewportElement.appendChild(this.viewportScrollArea); - - // Create the selection container. - this.selectionContainer = document.createElement('div'); - this.selectionContainer.classList.add('xterm-selection'); - this.element.appendChild(this.selectionContainer); - - // Create the container that will hold the lines of the terminal and then - // produce the lines the lines. - this.rowContainer = document.createElement('div'); - this.rowContainer.classList.add('xterm-rows'); - this.element.appendChild(this.rowContainer); - this.children = []; - this.linkifier.attachToDom(document, this.children); - - // 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'); - // TODO: This should probably be inserted once it's filled to prevent an additional layout - this.element.appendChild(this.helperContainer); - this.textarea = document.createElement('textarea'); - this.textarea.classList.add('xterm-helper-textarea'); - this.textarea.setAttribute('autocorrect', 'off'); - this.textarea.setAttribute('autocapitalize', 'off'); - this.textarea.setAttribute('spellcheck', 'false'); - this.textarea.tabIndex = 0; - this.textarea.addEventListener('focus', function() { - self.emit('focus', {terminal: self}); - }); - this.textarea.addEventListener('blur', function() { - self.emit('blur', {terminal: self}); - }); - this.helperContainer.appendChild(this.textarea); - - this.compositionView = document.createElement('div'); - this.compositionView.classList.add('composition-view'); - this.compositionHelper = new CompositionHelper(this.textarea, this.compositionView, this); - this.helperContainer.appendChild(this.compositionView); - - this.charSizeStyleElement = document.createElement('style'); - this.helperContainer.appendChild(this.charSizeStyleElement); - - for (; i < this.rows; i++) { - this.insertRow(); - } - this.parent.appendChild(this.element); - - this.charMeasure = new CharMeasure(document, this.helperContainer); - this.charMeasure.on('charsizechanged', function () { - self.updateCharSizeStyles(); - }); - this.charMeasure.measure(); - - this.viewport = new Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasure); - this.renderer = new Renderer(this); - this.selectionManager = new SelectionManager( - this, this.buffer.lines, this.rowContainer, this.charMeasure - ); - this.selectionManager.on('refresh', data => { - this.renderer.refreshSelection(data.start, data.end); - }); - this.selectionManager.on('newselection', text => { - // If there's a new selection, put it into the textarea, focus and select it - // in order to register it as a selection on the OS. This event is fired - // only on Linux to enable middle click to paste selection. - this.textarea.value = text; - this.textarea.focus(); - this.textarea.select(); - }); - this.on('scroll', () => this.selectionManager.refresh()); - this.viewportElement.addEventListener('scroll', () => this.selectionManager.refresh()); - - // Setup loop that draws to screen - this.refresh(0, this.rows - 1); - - // Initialize global actions that - // need to be taken on the document. - this.initGlobal(); - - /** - * Automatic focus functionality. - * TODO: Default to `false` starting with xterm.js 3.0. - */ - if (typeof focus == 'undefined') { - let message = 'You did not pass the `focus` argument in `Terminal.prototype.open()`.\n'; - - message += 'The `focus` argument now defaults to `true` but starting with xterm.js 3.0 '; - message += 'it will default to `false`.'; - - console.warn(message); - focus = true; - } - - if (focus) { - this.focus(); - } - - // Listen for mouse events and translate - // them into terminal mouse protocols. - this.bindMouse(); - - /** - * This event is emitted when terminal has completed opening. - * - * @event open - */ - this.emit('open'); -}; - - -/** - * Attempts to load an add-on using CommonJS or RequireJS (whichever is available). - * @param {string} addon The name of the addon to load - * @static - */ -Terminal.loadAddon = function(addon, callback) { - if (typeof exports === 'object' && typeof module === 'object') { - // CommonJS - return require('./addons/' + addon + '/' + addon); - } else if (typeof define == 'function') { - // RequireJS - return require(['./addons/' + addon + '/' + addon], callback); - } else { - console.error('Cannot load a module without a CommonJS or RequireJS environment.'); - return false; - } -}; - -/** - * Updates the helper CSS class with any changes necessary after the terminal's - * character width has been changed. - */ -Terminal.prototype.updateCharSizeStyles = function() { - this.charSizeStyleElement.textContent = - `.xterm-wide-char{width:${this.charMeasure.width * 2}px;}` + - `.xterm-normal-char{width:${this.charMeasure.width}px;}` + - `.xterm-rows > div{height:${this.charMeasure.height}px;}`; -} - -/** - * XTerm mouse events - * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking - * To better understand these - * the xterm code is very helpful: - * Relevant files: - * button.c, charproc.c, misc.c - * Relevant functions in xterm/button.c: - * BtnCode, EmitButtonCode, EditorButton, SendMousePosition - */ -Terminal.prototype.bindMouse = function() { - var el = this.element, self = this, pressed = 32; - - // mouseup, mousedown, wheel - // left click: ^[[M 3<^[[M#3< - // wheel up: ^[[M`3> - function sendButton(ev) { - var button - , pos; - - // get the xterm-style button - button = getButton(ev); - - // get mouse coordinates - pos = getRawByteCoords(ev, self.rowContainer, self.charMeasure, self.cols, self.rows); - if (!pos) return; - - sendEvent(button, pos); - - switch (ev.overrideType || ev.type) { - case 'mousedown': - pressed = button; - break; - case 'mouseup': - // keep it at the left - // button, just in case. - pressed = 32; - break; - case 'wheel': - // nothing. don't - // interfere with - // `pressed`. - break; - } - } - - // motion example of a left click: - // ^[[M 3<^[[M@4<^[[M@5<^[[M@6<^[[M@7<^[[M#7< - function sendMove(ev) { - var button = pressed - , pos; - - pos = getRawByteCoords(ev, self.rowContainer, self.charMeasure, self.cols, self.rows); - if (!pos) return; - - // buttons marked as motions - // are incremented by 32 - button += 32; - - sendEvent(button, pos); - } - - // encode button and - // position to characters - function encode(data, ch) { - if (!self.utfMouse) { - if (ch === 255) return data.push(0); - if (ch > 127) ch = 127; - data.push(ch); - } else { - if (ch === 2047) return data.push(0); - if (ch < 127) { - data.push(ch); - } else { - if (ch > 2047) ch = 2047; - data.push(0xC0 | (ch >> 6)); - data.push(0x80 | (ch & 0x3F)); - } - } - } - - // send a mouse event: - // regular/utf8: ^[[M Cb Cx Cy - // urxvt: ^[[ Cb ; Cx ; Cy M - // sgr: ^[[ Cb ; Cx ; Cy M/m - // vt300: ^[[ 24(1/3/5)~ [ Cx , Cy ] \r - // locator: CSI P e ; P b ; P r ; P c ; P p & w - function sendEvent(button, pos) { - // self.emit('mouse', { - // x: pos.x - 32, - // y: pos.x - 32, - // button: button - // }); - - if (self.vt300Mouse) { - // NOTE: Unstable. - // http://www.vt100.net/docs/vt3xx-gp/chapter15.html - button &= 3; - pos.x -= 32; - pos.y -= 32; - var data = C0.ESC + '[24'; - if (button === 0) data += '1'; - else if (button === 1) data += '3'; - else if (button === 2) data += '5'; - else if (button === 3) return; - else data += '0'; - data += '~[' + pos.x + ',' + pos.y + ']\r'; - self.send(data); - return; - } - - if (self.decLocator) { - // NOTE: Unstable. - button &= 3; - pos.x -= 32; - pos.y -= 32; - if (button === 0) button = 2; - else if (button === 1) button = 4; - else if (button === 2) button = 6; - else if (button === 3) button = 3; - self.send(C0.ESC + '[' - + button - + ';' - + (button === 3 ? 4 : 0) - + ';' - + pos.y - + ';' - + pos.x - + ';' - + (pos.page || 0) - + '&w'); - return; - } - - if (self.urxvtMouse) { - pos.x -= 32; - pos.y -= 32; - pos.x++; - pos.y++; - self.send(C0.ESC + '[' + button + ';' + pos.x + ';' + pos.y + 'M'); - return; - } - - if (self.sgrMouse) { - pos.x -= 32; - pos.y -= 32; - self.send(C0.ESC + '[<' - + (((button & 3) === 3 ? button & ~3 : button) - 32) - + ';' - + pos.x - + ';' - + pos.y - + ((button & 3) === 3 ? 'm' : 'M')); - return; - } - - var data = []; - - encode(data, button); - encode(data, pos.x); - encode(data, pos.y); - - self.send(C0.ESC + '[M' + String.fromCharCode.apply(String, data)); - } - - function getButton(ev) { - var button - , shift - , meta - , ctrl - , mod; - - // two low bits: - // 0 = left - // 1 = middle - // 2 = right - // 3 = release - // wheel up/down: - // 1, and 2 - with 64 added - switch (ev.overrideType || ev.type) { - case 'mousedown': - button = ev.button != null - ? +ev.button - : ev.which != null - ? ev.which - 1 - : null; - - if (self.browser.isMSIE) { - button = button === 1 ? 0 : button === 4 ? 1 : button; - } - break; - case 'mouseup': - button = 3; - break; - case 'DOMMouseScroll': - button = ev.detail < 0 - ? 64 - : 65; - break; - case 'wheel': - button = ev.wheelDeltaY > 0 - ? 64 - : 65; - break; - } - - // next three bits are the modifiers: - // 4 = shift, 8 = meta, 16 = control - shift = ev.shiftKey ? 4 : 0; - meta = ev.metaKey ? 8 : 0; - ctrl = ev.ctrlKey ? 16 : 0; - mod = shift | meta | ctrl; - - // no mods - if (self.vt200Mouse) { - // ctrl only - mod &= ctrl; - } else if (!self.normalMouse) { - mod = 0; - } - - // increment to SP - button = (32 + (mod << 2)) + button; - - return button; - } - - on(el, 'mousedown', function(ev) { - - // Prevent the focus on the textarea from getting lost - // and make sure we get focused on mousedown - ev.preventDefault(); - self.focus(); - - if (!self.mouseEvents) return; - - // send the button - sendButton(ev); - - // fix for odd bug - //if (self.vt200Mouse && !self.normalMouse) { - if (self.vt200Mouse) { - ev.overrideType = 'mouseup'; - sendButton(ev); - return self.cancel(ev); - } - - // bind events - if (self.normalMouse) on(self.document, 'mousemove', sendMove); - - // x10 compatibility mode can't send button releases - if (!self.x10Mouse) { - on(self.document, 'mouseup', function up(ev) { - sendButton(ev); - if (self.normalMouse) off(self.document, 'mousemove', sendMove); - off(self.document, 'mouseup', up); - return self.cancel(ev); - }); - } - - return self.cancel(ev); - }); - - //if (self.normalMouse) { - // on(self.document, 'mousemove', sendMove); - //} - - on(el, 'wheel', function(ev) { - if (!self.mouseEvents) return; - if (self.x10Mouse - || self.vt300Mouse - || self.decLocator) return; - sendButton(ev); - return self.cancel(ev); - }); - - // allow wheel scrolling in - // the shell for example - on(el, 'wheel', function(ev) { - if (self.mouseEvents) return; - self.viewport.onWheel(ev); - return self.cancel(ev); - }); - - on(el, 'touchstart', function(ev) { - if (self.mouseEvents) return; - self.viewport.onTouchStart(ev); - return self.cancel(ev); - }); - - on(el, 'touchmove', function(ev) { - if (self.mouseEvents) return; - self.viewport.onTouchMove(ev); - return self.cancel(ev); - }); -}; - -/** - * Destroys the terminal. - */ -Terminal.prototype.destroy = function() { - this.readable = false; - this.writable = false; - this._events = {}; - this.handler = function() {}; - this.write = function() {}; - if (this.element && this.element.parentNode) { - this.element.parentNode.removeChild(this.element); - } - //this.emit('close'); -}; - -/** - * Tells the renderer to refresh terminal content between two rows (inclusive) at the next - * opportunity. - * @param {number} start The row to start from (between 0 and this.rows - 1). - * @param {number} end The row to end at (between start and this.rows - 1). - */ -Terminal.prototype.refresh = function(start, end) { - if (this.renderer) { - this.renderer.queueRefresh(start, end); - } -}; - -/** - * Queues linkification for the specified rows. - * @param {number} start The row to start from (between 0 and this.rows - 1). - * @param {number} end The row to end at (between start and this.rows - 1). - */ -Terminal.prototype.queueLinkification = function(start, end) { - if (this.linkifier) { - for (let i = start; i <= end; i++) { - this.linkifier.linkifyRow(i); - } - } -}; - -/** - * Display the cursor element - */ -Terminal.prototype.showCursor = function() { - if (!this.cursorState) { - this.cursorState = 1; - this.refresh(this.buffer.y, this.buffer.y); - } -}; - -/** - * Scroll the terminal down 1 row, creating a blank line. - * @param {boolean} isWrapped Whether the new line is wrapped from the previous - * line. - */ -Terminal.prototype.scroll = function(isWrapped) { - var row; - - // Make room for the new row in lines - if (this.buffer.lines.length === this.buffer.lines.maxLength) { - this.buffer.lines.trimStart(1); - this.buffer.ybase--; - if (this.buffer.ydisp !== 0) { - this.buffer.ydisp--; - } - } - - this.buffer.ybase++; - - // TODO: Why is this done twice? - if (!this.userScrolling) { - this.buffer.ydisp = this.buffer.ybase; - } - - // last line - row = this.buffer.ybase + this.rows - 1; - - // subtract the bottom scroll region - row -= this.rows - 1 - this.buffer.scrollBottom; - - if (row === this.buffer.lines.length) { - // Optimization: pushing is faster than splicing when they amount to the same behavior - this.buffer.lines.push(this.blankLine(undefined, isWrapped)); - } else { - // add our new line - this.buffer.lines.splice(row, 0, this.blankLine(undefined, isWrapped)); - } - - if (this.buffer.scrollTop !== 0) { - if (this.buffer.ybase !== 0) { - this.buffer.ybase--; - if (!this.userScrolling) { - this.buffer.ydisp = this.buffer.ybase; - } - } - this.buffer.lines.splice(this.buffer.ybase + this.buffer.scrollTop, 1); - } - - // this.maxRange(); - this.updateRange(this.buffer.scrollTop); - this.updateRange(this.buffer.scrollBottom); - - /** - * This event is emitted whenever the terminal is scrolled. - * The one parameter passed is the new y display position. - * - * @event scroll - */ - this.emit('scroll', this.buffer.ydisp); -}; - -/** - * Scroll the display of the terminal - * @param {number} disp The number of lines to scroll down (negatives scroll up). - * @param {boolean} suppressScrollEvent Don't emit the scroll event as scrollDisp. This is used - * to avoid unwanted events being handled by the veiwport when the event was triggered from the - * viewport originally. - */ -Terminal.prototype.scrollDisp = function(disp, suppressScrollEvent) { - if (disp < 0) { - if (this.buffer.ydisp === 0) { - return; - } - this.userScrolling = true; - } else if (disp + this.buffer.ydisp >= this.buffer.ybase) { - this.userScrolling = false; - } - - const oldYdisp = this.buffer.ydisp; - this.buffer.ydisp = Math.max(Math.min(this.buffer.ydisp + disp, this.buffer.ybase), 0); - - // No change occurred, don't trigger scroll/refresh - if (oldYdisp === this.buffer.ydisp) { - return; - } - - if (!suppressScrollEvent) { - this.emit('scroll', this.buffer.ydisp); - } - - this.refresh(0, this.rows - 1); -}; - -/** - * Scroll the display of the terminal by a number of pages. - * @param {number} pageCount The number of pages to scroll (negative scrolls up). - */ -Terminal.prototype.scrollPages = function(pageCount) { - this.scrollDisp(pageCount * (this.rows - 1)); -}; - -/** - * Scrolls the display of the terminal to the top. - */ -Terminal.prototype.scrollToTop = function() { - this.scrollDisp(-this.buffer.ydisp); -}; - -/** - * Scrolls the display of the terminal to the bottom. - */ -Terminal.prototype.scrollToBottom = function() { - this.scrollDisp(this.buffer.ybase - this.buffer.ydisp); -}; - -/** - * Writes text to the terminal. - * @param {string} data The text to write to the terminal. - */ -Terminal.prototype.write = function(data) { - this.writeBuffer.push(data); - - // Send XOFF to pause the pty process if the write buffer becomes too large so - // xterm.js can catch up before more data is sent. This is necessary in order - // to keep signals such as ^C responsive. - if (this.options.useFlowControl && !this.xoffSentToCatchUp && this.writeBuffer.length >= WRITE_BUFFER_PAUSE_THRESHOLD) { - // XOFF - stop pty pipe - // XON will be triggered by emulator before processing data chunk - this.send(C0.DC3); - this.xoffSentToCatchUp = true; - } - - if (!this.writeInProgress && this.writeBuffer.length > 0) { - // Kick off a write which will write all data in sequence recursively - this.writeInProgress = true; - // Kick off an async innerWrite so more writes can come in while processing data - var self = this; - setTimeout(function () { - self.innerWrite(); - }); - } -}; - -Terminal.prototype.innerWrite = function() { - var writeBatch = this.writeBuffer.splice(0, WRITE_BATCH_SIZE); - while (writeBatch.length > 0) { - var data = writeBatch.shift(); - var l = data.length, i = 0, j, cs, ch, code, low, ch_width, row; - - // If XOFF was sent in order to catch up with the pty process, resume it if - // the writeBuffer is empty to allow more data to come in. - if (this.xoffSentToCatchUp && writeBatch.length === 0 && this.writeBuffer.length === 0) { - this.send(C0.DC1); - this.xoffSentToCatchUp = false; - } - - this.refreshStart = this.buffer.y; - this.refreshEnd = this.buffer.y; - - // HACK: Set the parser state based on it's state at the time of return. - // This works around the bug #662 which saw the parser state reset in the - // middle of parsing escape sequence in two chunks. For some reason the - // state of the parser resets to 0 after exiting parser.parse. This change - // just sets the state back based on the correct return statement. - var state = this.parser.parse(data); - this.parser.setState(state); - - this.updateRange(this.buffer.y); - this.refresh(this.refreshStart, this.refreshEnd); - } - if (this.writeBuffer.length > 0) { - // Allow renderer to catch up before processing the next batch - var self = this; - setTimeout(function () { - self.innerWrite(); - }, 0); - } else { - this.writeInProgress = false; - } -}; - -/** - * Writes text to the terminal, followed by a break line character (\n). - * @param {string} data The text to write to the terminal. - */ -Terminal.prototype.writeln = function(data) { - this.write(data + '\r\n'); -}; - -/** - * DEPRECATED: only for backward compatibility. Please use attachCustomKeyEventHandler() instead. - * @param {function} customKeydownHandler The custom KeyboardEvent handler to attach. This is a - * function that takes a KeyboardEvent, allowing consumers to stop propogation and/or prevent - * the default action. The function returns whether the event should be processed by xterm.js. - */ -Terminal.prototype.attachCustomKeydownHandler = function(customKeydownHandler) { - let message = 'attachCustomKeydownHandler() is DEPRECATED and will be removed soon. Please use attachCustomKeyEventHandler() instead.'; - console.warn(message); - this.attachCustomKeyEventHandler(customKeydownHandler); -}; - -/** - * Attaches a custom key event handler which is run before keys are processed, giving consumers of - * xterm.js ultimate control as to what keys should be processed by the terminal and what keys - * should not. - * @param {function} customKeyEventHandler The custom KeyboardEvent handler to attach. This is a - * function that takes a KeyboardEvent, allowing consumers to stop propogation and/or prevent - * the default action. The function returns whether the event should be processed by xterm.js. - */ -Terminal.prototype.attachCustomKeyEventHandler = function(customKeyEventHandler) { - this.customKeyEventHandler = customKeyEventHandler; -}; - -/** - * Attaches a http(s) link handler, forcing web links to behave differently to - * regular tags. This will trigger a refresh as links potentially need to be - * reconstructed. Calling this with null will remove the handler. - * @param {LinkMatcherHandler} handler The handler callback function. - */ -Terminal.prototype.setHypertextLinkHandler = function(handler) { - if (!this.linkifier) { - throw new Error('Cannot attach a hypertext link handler before Terminal.open is called'); - } - this.linkifier.setHypertextLinkHandler(handler); - // Refresh to force links to refresh - this.refresh(0, this.rows - 1); -}; - -/** - * Attaches a validation callback for hypertext links. This is useful to use - * validation logic or to do something with the link's element and url. - * @param {LinkMatcherValidationCallback} callback The callback to use, this can - * be cleared with null. - */ -Terminal.prototype.setHypertextValidationCallback = function(callback) { - if (!this.linkifier) { - throw new Error('Cannot attach a hypertext validation callback before Terminal.open is called'); - } - this.linkifier.setHypertextValidationCallback(callback); - // Refresh to force links to refresh - this.refresh(0, this.rows - 1); -}; - -/** - * Registers a link matcher, allowing custom link patterns to be matched and - * handled. - * @param {RegExp} regex The regular expression to search for, specifically - * this searches the textContent of the rows. You will want to use \s to match - * a space ' ' character for example. - * @param {LinkMatcherHandler} handler The callback when the link is called. - * @param {LinkMatcherOptions} [options] Options for the link matcher. - * @return {number} The ID of the new matcher, this can be used to deregister. - */ -Terminal.prototype.registerLinkMatcher = function(regex, handler, options) { - if (this.linkifier) { - var matcherId = this.linkifier.registerLinkMatcher(regex, handler, options); - this.refresh(0, this.rows - 1); - return matcherId; - } -}; - -/** - * Deregisters a link matcher if it has been registered. - * @param {number} matcherId The link matcher's ID (returned after register) - */ -Terminal.prototype.deregisterLinkMatcher = function(matcherId) { - if (this.linkifier) { - if (this.linkifier.deregisterLinkMatcher(matcherId)) { - this.refresh(0, this.rows - 1); - } - } -}; - -/** - * Gets whether the terminal has an active selection. - */ -Terminal.prototype.hasSelection = function() { - return this.selectionManager ? this.selectionManager.hasSelection : false; -}; - -/** - * Gets the terminal's current selection, this is useful for implementing copy - * behavior outside of xterm.js. - */ -Terminal.prototype.getSelection = function() { - return this.selectionManager ? this.selectionManager.selectionText : ''; -}; - -/** - * Clears the current terminal selection. - */ -Terminal.prototype.clearSelection = function() { - if (this.selectionManager) { - this.selectionManager.clearSelection(); - } -}; - -/** - * Selects all text within the terminal. - */ -Terminal.prototype.selectAll = function() { - if (this.selectionManager) { - this.selectionManager.selectAll(); - } -}; - -/** - * Handle a keydown event - * Key Resources: - * - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent - * @param {KeyboardEvent} ev The keydown event to be handled. - */ -Terminal.prototype.keyDown = function(ev) { - if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) { - return false; - } - - this.restartCursorBlinking(); - - if (!this.compositionHelper.keydown.bind(this.compositionHelper)(ev)) { - if (this.buffer.ybase !== this.buffer.ydisp) { - this.scrollToBottom(); - } - return false; - } - - var self = this; - var result = this.evaluateKeyEscapeSequence(ev); - - if (result.key === C0.DC3) { // XOFF - this.writeStopped = true; - } else if (result.key === C0.DC1) { // XON - this.writeStopped = false; - } - - if (result.scrollDisp) { - this.scrollDisp(result.scrollDisp); - return this.cancel(ev, true); - } - - if (isThirdLevelShift(this, ev)) { - return true; - } - - if (result.cancel) { - // The event is canceled at the end already, is this necessary? - this.cancel(ev, true); - } - - if (!result.key) { - return true; - } - - this.emit('keydown', ev); - this.emit('key', result.key, ev); - this.showCursor(); - this.handler(result.key); - - return this.cancel(ev, true); -}; - -/** - * Returns an object that determines how a KeyboardEvent should be handled. The key of the - * returned value is the new key code to pass to the PTY. - * - * Reference: http://invisible-island.net/xterm/ctlseqs/ctlseqs.html - * @param {KeyboardEvent} ev The keyboard event to be translated to key escape sequence. - */ -Terminal.prototype.evaluateKeyEscapeSequence = function(ev) { - var result = { - // Whether to cancel event propogation (NOTE: this may not be needed since the event is - // canceled at the end of keyDown - cancel: false, - // The new key even to emit - key: undefined, - // The number of characters to scroll, if this is defined it will cancel the event - scrollDisp: undefined - }; - var modifiers = ev.shiftKey << 0 | ev.altKey << 1 | ev.ctrlKey << 2 | ev.metaKey << 3; - switch (ev.keyCode) { - case 8: - // backspace - if (ev.shiftKey) { - result.key = C0.BS; // ^H - break; - } - result.key = C0.DEL; // ^? - break; - case 9: - // tab - if (ev.shiftKey) { - result.key = C0.ESC + '[Z'; - break; - } - result.key = C0.HT; - result.cancel = true; - break; - case 13: - // return/enter - result.key = C0.CR; - result.cancel = true; - break; - case 27: - // escape - result.key = C0.ESC; - result.cancel = true; - break; - case 37: - // left-arrow - if (modifiers) { - result.key = C0.ESC + '[1;' + (modifiers + 1) + 'D'; - // HACK: Make Alt + left-arrow behave like Ctrl + left-arrow: move one word backwards - // http://unix.stackexchange.com/a/108106 - // macOS uses different escape sequences than linux - if (result.key == C0.ESC + '[1;3D') { - result.key = (this.browser.isMac) ? C0.ESC + 'b' : C0.ESC + '[1;5D'; - } - } else if (this.applicationCursor) { - result.key = C0.ESC + 'OD'; - } else { - result.key = C0.ESC + '[D'; - } - break; - case 39: - // right-arrow - if (modifiers) { - result.key = C0.ESC + '[1;' + (modifiers + 1) + 'C'; - // HACK: Make Alt + right-arrow behave like Ctrl + right-arrow: move one word forward - // http://unix.stackexchange.com/a/108106 - // macOS uses different escape sequences than linux - if (result.key == C0.ESC + '[1;3C') { - result.key = (this.browser.isMac) ? C0.ESC + 'f' : C0.ESC + '[1;5C'; - } - } else if (this.applicationCursor) { - result.key = C0.ESC + 'OC'; - } else { - result.key = C0.ESC + '[C'; - } - break; - case 38: - // up-arrow - if (modifiers) { - result.key = C0.ESC + '[1;' + (modifiers + 1) + 'A'; - // HACK: Make Alt + up-arrow behave like Ctrl + up-arrow - // http://unix.stackexchange.com/a/108106 - if (result.key == C0.ESC + '[1;3A') { - result.key = C0.ESC + '[1;5A'; - } - } else if (this.applicationCursor) { - result.key = C0.ESC + 'OA'; - } else { - result.key = C0.ESC + '[A'; - } - break; - case 40: - // down-arrow - if (modifiers) { - result.key = C0.ESC + '[1;' + (modifiers + 1) + 'B'; - // HACK: Make Alt + down-arrow behave like Ctrl + down-arrow - // http://unix.stackexchange.com/a/108106 - if (result.key == C0.ESC + '[1;3B') { - result.key = C0.ESC + '[1;5B'; - } - } else if (this.applicationCursor) { - result.key = C0.ESC + 'OB'; - } else { - result.key = C0.ESC + '[B'; - } - break; - case 45: - // insert - if (!ev.shiftKey && !ev.ctrlKey) { - // or + are used to - // copy-paste on some systems. - result.key = C0.ESC + '[2~'; - } - break; - case 46: - // delete - if (modifiers) { - result.key = C0.ESC + '[3;' + (modifiers + 1) + '~'; - } else { - result.key = C0.ESC + '[3~'; - } - break; - case 36: - // home - if (modifiers) - result.key = C0.ESC + '[1;' + (modifiers + 1) + 'H'; - else if (this.applicationCursor) - result.key = C0.ESC + 'OH'; - else - result.key = C0.ESC + '[H'; - break; - case 35: - // end - if (modifiers) - result.key = C0.ESC + '[1;' + (modifiers + 1) + 'F'; - else if (this.applicationCursor) - result.key = C0.ESC + 'OF'; - else - result.key = C0.ESC + '[F'; - break; - case 33: - // page up - if (ev.shiftKey) { - result.scrollDisp = -(this.rows - 1); - } else { - result.key = C0.ESC + '[5~'; - } - break; - case 34: - // page down - if (ev.shiftKey) { - result.scrollDisp = this.rows - 1; - } else { - result.key = C0.ESC + '[6~'; - } - break; - case 112: - // F1-F12 - if (modifiers) { - result.key = C0.ESC + '[1;' + (modifiers + 1) + 'P'; - } else { - result.key = C0.ESC + 'OP'; - } - break; - case 113: - if (modifiers) { - result.key = C0.ESC + '[1;' + (modifiers + 1) + 'Q'; - } else { - result.key = C0.ESC + 'OQ'; - } - break; - case 114: - if (modifiers) { - result.key = C0.ESC + '[1;' + (modifiers + 1) + 'R'; - } else { - result.key = C0.ESC + 'OR'; - } - break; - case 115: - if (modifiers) { - result.key = C0.ESC + '[1;' + (modifiers + 1) + 'S'; - } else { - result.key = C0.ESC + 'OS'; - } - break; - case 116: - if (modifiers) { - result.key = C0.ESC + '[15;' + (modifiers + 1) + '~'; - } else { - result.key = C0.ESC + '[15~'; - } - break; - case 117: - if (modifiers) { - result.key = C0.ESC + '[17;' + (modifiers + 1) + '~'; - } else { - result.key = C0.ESC + '[17~'; - } - break; - case 118: - if (modifiers) { - result.key = C0.ESC + '[18;' + (modifiers + 1) + '~'; - } else { - result.key = C0.ESC + '[18~'; - } - break; - case 119: - if (modifiers) { - result.key = C0.ESC + '[19;' + (modifiers + 1) + '~'; - } else { - result.key = C0.ESC + '[19~'; - } - break; - case 120: - if (modifiers) { - result.key = C0.ESC + '[20;' + (modifiers + 1) + '~'; - } else { - result.key = C0.ESC + '[20~'; - } - break; - case 121: - if (modifiers) { - result.key = C0.ESC + '[21;' + (modifiers + 1) + '~'; - } else { - result.key = C0.ESC + '[21~'; - } - break; - case 122: - if (modifiers) { - result.key = C0.ESC + '[23;' + (modifiers + 1) + '~'; - } else { - result.key = C0.ESC + '[23~'; - } - break; - case 123: - if (modifiers) { - result.key = C0.ESC + '[24;' + (modifiers + 1) + '~'; - } else { - result.key = C0.ESC + '[24~'; - } - break; - default: - // a-z and space - if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) { - if (ev.keyCode >= 65 && ev.keyCode <= 90) { - result.key = String.fromCharCode(ev.keyCode - 64); - } else if (ev.keyCode === 32) { - // NUL - result.key = String.fromCharCode(0); - } else if (ev.keyCode >= 51 && ev.keyCode <= 55) { - // escape, file sep, group sep, record sep, unit sep - result.key = String.fromCharCode(ev.keyCode - 51 + 27); - } else if (ev.keyCode === 56) { - // delete - result.key = String.fromCharCode(127); - } else if (ev.keyCode === 219) { - // ^[ - Control Sequence Introducer (CSI) - result.key = String.fromCharCode(27); - } else if (ev.keyCode === 220) { - // ^\ - String Terminator (ST) - result.key = String.fromCharCode(28); - } else if (ev.keyCode === 221) { - // ^] - Operating System Command (OSC) - result.key = String.fromCharCode(29); - } - } else if (!this.browser.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) { - // On Mac this is a third level shift. 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 + '`'; - } else if (ev.keyCode >= 48 && ev.keyCode <= 57) { - result.key = C0.ESC + (ev.keyCode - 48); - } - } else if (this.browser.isMac && !ev.altKey && !ev.ctrlKey && ev.metaKey) { - if (ev.keyCode === 65) { // cmd + a - this.selectAll(); - } - } - break; - } - - return result; -}; - -/** - * Set the G level of the terminal - * @param g - */ -Terminal.prototype.setgLevel = function(g) { - this.glevel = g; - this.charset = this.charsets[g]; -}; - -/** - * Set the charset for the given G level of the terminal - * @param g - * @param charset - */ -Terminal.prototype.setgCharset = function(g, charset) { - this.charsets[g] = charset; - if (this.glevel === g) { - this.charset = charset; - } -}; - -/** - * Handle a keypress event. - * Key Resources: - * - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent - * @param {KeyboardEvent} ev The keypress event to be handled. - */ -Terminal.prototype.keyPress = function(ev) { - var key; - - if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) { - return false; - } - - this.cancel(ev); - - if (ev.charCode) { - key = ev.charCode; - } else if (ev.which == null) { - key = ev.keyCode; - } else if (ev.which !== 0 && ev.charCode !== 0) { - key = ev.which; - } else { - return false; - } - - if (!key || ( - (ev.altKey || ev.ctrlKey || ev.metaKey) && !isThirdLevelShift(this, ev) - )) { - return false; - } - - key = String.fromCharCode(key); - - this.emit('keypress', key, ev); - this.emit('key', key, ev); - this.showCursor(); - this.handler(key); - - return true; -}; - -/** - * Send data for handling to the terminal - * @param {string} data - */ -Terminal.prototype.send = function(data) { - var self = this; - - if (!this.queue) { - setTimeout(function() { - self.handler(self.queue); - self.queue = ''; - }, 1); - } - - this.queue += data; -}; - -/** - * Ring the bell. - * Note: We could do sweet things with webaudio here - */ -Terminal.prototype.bell = function() { - if (!this.visualBell) return; - var self = this; - this.element.style.borderColor = 'white'; - setTimeout(function() { - self.element.style.borderColor = ''; - }, 10); - if (this.popOnBell) this.focus(); -}; - -/** - * Log the current state to the console. - */ -Terminal.prototype.log = function() { - if (!this.debug) return; - if (!this.context.console || !this.context.console.log) return; - var args = Array.prototype.slice.call(arguments); - this.context.console.log.apply(this.context.console, args); -}; - -/** - * Log the current state as error to the console. - */ -Terminal.prototype.error = function() { - if (!this.debug) return; - if (!this.context.console || !this.context.console.error) return; - var args = Array.prototype.slice.call(arguments); - this.context.console.error.apply(this.context.console, args); -}; - -/** - * Resizes the terminal. - * - * @param {number} x The number of columns to resize to. - * @param {number} y The number of rows to resize to. - */ -Terminal.prototype.resize = function(x, y) { - if (isNaN(x) || isNaN(y)) { - return; - } - - if (y > this.getOption('scrollback')) { - this.setOption('scrollback', y) - } - - var line - , el - , i - , j - , ch - , addToY; - - if (x === this.cols && y === this.rows) { - // Check if we still need to measure the char size (fixes #785). - if (!this.charMeasure.width || !this.charMeasure.height) { - this.charMeasure.measure(); - } - return; - } - - if (x < 1) x = 1; - if (y < 1) y = 1; - - this.buffers.resize(x, y); - - // Adjust rows in the DOM to accurately reflect the new dimensions - while (this.children.length < y) { - this.insertRow(); - } - while (this.children.length > y) { - el = this.children.shift(); - if (!el) continue; - el.parentNode.removeChild(el); - } - - this.cols = x; - this.rows = y; - this.setupStops(this.cols); - - this.charMeasure.measure(); - - this.refresh(0, this.rows - 1); - - this.geometry = [this.cols, this.rows]; - this.emit('resize', {terminal: this, cols: x, rows: y}); -}; - -/** - * Updates the range of rows to refresh - * @param {number} y The number of rows to refresh next. - */ -Terminal.prototype.updateRange = function(y) { - if (y < this.refreshStart) this.refreshStart = y; - if (y > this.refreshEnd) this.refreshEnd = y; - // if (y > this.refreshEnd) { - // this.refreshEnd = y; - // if (y > this.rows - 1) { - // this.refreshEnd = this.rows - 1; - // } - // } -}; - -/** - * Set the range of refreshing to the maximum value - */ -Terminal.prototype.maxRange = function() { - this.refreshStart = 0; - this.refreshEnd = this.rows - 1; -}; - - - -/** - * Setup the tab stops. - * @param {number} i - */ -Terminal.prototype.setupStops = function(i) { - if (i != null) { - if (!this.buffer.tabs[i]) { - i = this.prevStop(i); - } - } else { - this.buffer.tabs = {}; - i = 0; - } - - for (; i < this.cols; i += this.getOption('tabStopWidth')) { - this.buffer.tabs[i] = true; - } -}; - - -/** - * Move the cursor to the previous tab stop from the given position (default is current). - * @param {number} x The position to move the cursor to the previous tab stop. - */ -Terminal.prototype.prevStop = function(x) { - if (x == null) x = this.buffer.x; - while (!this.buffer.tabs[--x] && x > 0); - return x >= this.cols - ? this.cols - 1 - : x < 0 ? 0 : x; -}; - - -/** - * Move the cursor one tab stop forward from the given position (default is current). - * @param {number} x The position to move the cursor one tab stop forward. - */ -Terminal.prototype.nextStop = function(x) { - if (x == null) x = this.buffer.x; - while (!this.buffer.tabs[++x] && x < this.cols); - return x >= this.cols - ? this.cols - 1 - : x < 0 ? 0 : x; -}; - - -/** - * Erase in the identified line everything from "x" to the end of the line (right). - * @param {number} x The column from which to start erasing to the end of the line. - * @param {number} y The line in which to operate. - */ -Terminal.prototype.eraseRight = function(x, y) { - var line = this.buffer.lines.get(this.buffer.ybase + y); - if (!line) { - return; - } - var ch = [this.eraseAttr(), ' ', 1]; // xterm - for (; x < this.cols; x++) { - line[x] = ch; - } - this.updateRange(y); -}; - - - -/** - * Erase in the identified line everything from "x" to the start of the line (left). - * @param {number} x The column from which to start erasing to the start of the line. - * @param {number} y The line in which to operate. - */ -Terminal.prototype.eraseLeft = function(x, y) { - var line = this.buffer.lines.get(this.buffer.ybase + y); - if (!line) { - return; - } - var ch = [this.eraseAttr(), ' ', 1]; // xterm - x++; - while (x--) { - line[x] = ch; - } - this.updateRange(y); -}; - -/** - * Clears the entire buffer, making the prompt line the new first line. - */ -Terminal.prototype.clear = function() { - if (this.buffer.ybase === 0 && this.buffer.y === 0) { - // Don't clear if it's already clear - return; - } - this.buffer.lines.set(0, this.buffer.lines.get(this.buffer.ybase + this.buffer.y)); - this.buffer.lines.length = 1; - this.buffer.ydisp = 0; - this.buffer.ybase = 0; - this.buffer.y = 0; - for (var i = 1; i < this.rows; i++) { - this.buffer.lines.push(this.blankLine()); - } - this.refresh(0, this.rows - 1); - this.emit('scroll', this.buffer.ydisp); -}; - -/** - * Erase all content in the given line - * @param {number} y The line to erase all of its contents. - */ -Terminal.prototype.eraseLine = function(y) { - this.eraseRight(0, y); -}; - - -/** - * Return the data array of a blank line - * @param {number} cur First bunch of data for each "blank" character. - * @param {boolean} isWrapped Whether the new line is wrapped from the previous line. - */ -Terminal.prototype.blankLine = function(cur, isWrapped) { - var attr = cur - ? this.eraseAttr() - : this.defAttr; - - var ch = [attr, ' ', 1] // width defaults to 1 halfwidth character - , line = [] - , i = 0; - - // TODO: It is not ideal that this is a property on an array, a buffer line - // class should be added that will hold this data and other useful functions. - if (isWrapped) { - line.isWrapped = isWrapped; - } - - for (; i < this.cols; i++) { - line[i] = ch; - } - - return line; -}; - - -/** - * If cur return the back color xterm feature attribute. Else return defAttr. - * @param {object} cur - */ -Terminal.prototype.ch = function(cur) { - return cur - ? [this.eraseAttr(), ' ', 1] - : [this.defAttr, ' ', 1]; -}; - - -/** - * Evaluate if the current terminal is the given argument. - * @param {object} term The terminal to evaluate - */ -Terminal.prototype.is = function(term) { - var name = this.termName; - return (name + '').indexOf(term) === 0; -}; - - -/** - * Emit the 'data' event and populate the given data. - * @param {string} data The data to populate in the event. - */ -Terminal.prototype.handler = function(data) { - // Prevents all events to pty process if stdin is disabled - if (this.options.disableStdin) { - return; - } - - // Clear the selection if the selection manager is available and has an active selection - if (this.selectionManager && this.selectionManager.hasSelection) { - this.selectionManager.clearSelection(); - } - - // Input is being sent to the terminal, the terminal should focus the prompt. - if (this.buffer.ybase !== this.buffer.ydisp) { - this.scrollToBottom(); - } - this.emit('data', data); -}; - - -/** - * Emit the 'title' event and populate the given title. - * @param {string} title The title to populate in the event. - */ -Terminal.prototype.handleTitle = function(title) { - /** - * This event is emitted when the title of the terminal is changed - * from inside the terminal. The parameter is the new title. - * - * @event title - */ - this.emit('title', title); -}; - - -/** - * ESC - */ - -/** - * ESC D Index (IND is 0x84). - */ -Terminal.prototype.index = function() { - this.buffer.y++; - if (this.buffer.y > this.buffer.scrollBottom) { - this.buffer.y--; - this.scroll(); - } - // If the end of the line is hit, prevent this action from wrapping around to the next line. - if (this.buffer.x >= this.cols) { - this.buffer.x--; - } -}; - - -/** - * ESC M Reverse Index (RI is 0x8d). - * - * Move the cursor up one row, inserting a new blank line if necessary. - */ -Terminal.prototype.reverseIndex = function() { - var j; - if (this.buffer.y === this.buffer.scrollTop) { - // possibly move the code below to term.reverseScroll(); - // test: echo -ne '\e[1;1H\e[44m\eM\e[0m' - // blankLine(true) is xterm/linux behavior - this.buffer.lines.shiftElements(this.buffer.y + this.buffer.ybase, this.rows - 1, 1); - this.buffer.lines.set(this.buffer.y + this.buffer.ybase, this.blankLine(true)); - this.updateRange(this.buffer.scrollTop); - this.updateRange(this.buffer.scrollBottom); - } else { - this.buffer.y--; - } -}; - - -/** - * ESC c Full Reset (RIS). - */ -Terminal.prototype.reset = function() { - this.options.rows = this.rows; - this.options.cols = this.cols; - var customKeyEventHandler = this.customKeyEventHandler; - var cursorBlinkInterval = this.cursorBlinkInterval; - var inputHandler = this.inputHandler; - Terminal.call(this, this.options); - this.customKeyEventHandler = customKeyEventHandler; - this.cursorBlinkInterval = cursorBlinkInterval; - this.inputHandler = inputHandler; - this.refresh(0, this.rows - 1); - this.viewport.syncScrollArea(); -}; - - -/** - * ESC H Tab Set (HTS is 0x88). - */ -Terminal.prototype.tabSet = function() { - this.buffer.tabs[this.buffer.x] = true; -}; - -/** - * Helpers - */ - -function on(el, type, handler, capture) { - if (!Array.isArray(el)) { - el = [el]; - } - el.forEach(function (element) { - element.addEventListener(type, handler, capture || false); - }); -} - -function off(el, type, handler, capture) { - el.removeEventListener(type, handler, capture || false); -} - -function cancel(ev, force) { - if (!this.cancelEvents && !force) { - return; - } - ev.preventDefault(); - ev.stopPropagation(); - return false; -} - -function inherits(child, parent) { - function f() { - this.constructor = child; - } - f.prototype = parent.prototype; - child.prototype = new f; -} - -function indexOf(obj, el) { - var i = obj.length; - while (i--) { - if (obj[i] === el) return i; - } - return -1; -} - -function isThirdLevelShift(term, ev) { - var thirdLevelKey = - (term.browser.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) || - (term.browser.isMSWindows && ev.altKey && ev.ctrlKey && !ev.metaKey); - - if (ev.type == 'keypress') { - return thirdLevelKey; - } - - // Don't invoke for arrows, pageDown, home, backspace, etc. (on non-keypress events) - return thirdLevelKey && (!ev.keyCode || ev.keyCode > 47); -} - -// Expose to InputHandler (temporary) -Terminal.prototype.matchColor = matchColor; - -function matchColor(r1, g1, b1) { - var hash = (r1 << 16) | (g1 << 8) | b1; - - if (matchColor._cache[hash] != null) { - return matchColor._cache[hash]; - } - - var ldiff = Infinity - , li = -1 - , i = 0 - , c - , r2 - , g2 - , b2 - , diff; - - for (; i < Terminal.vcolors.length; i++) { - c = Terminal.vcolors[i]; - r2 = c[0]; - g2 = c[1]; - b2 = c[2]; - - diff = matchColor.distance(r1, g1, b1, r2, g2, b2); - - if (diff === 0) { - li = i; - break; - } - - if (diff < ldiff) { - ldiff = diff; - li = i; - } - } - - return matchColor._cache[hash] = li; -} - -matchColor._cache = {}; - -// http://stackoverflow.com/questions/1633828 -matchColor.distance = function(r1, g1, b1, r2, g2, b2) { - return Math.pow(30 * (r1 - r2), 2) - + Math.pow(59 * (g1 - g2), 2) - + Math.pow(11 * (b1 - b2), 2); -}; - -function each(obj, iter, con) { - if (obj.forEach) return obj.forEach(iter, con); - for (var i = 0; i < obj.length; i++) { - iter.call(con, obj[i], i, obj); - } -} - -function wasMondifierKeyOnlyEvent(ev) { - return ev.keyCode === 16 || // Shift - ev.keyCode === 17 || // Ctrl - ev.keyCode === 18; // Alt -} - -function keys(obj) { - if (Object.keys) return Object.keys(obj); - var key, keys = []; - for (key in obj) { - if (Object.prototype.hasOwnProperty.call(obj, key)) { - keys.push(key); - } - } - return keys; -} - -/** - * Expose - */ - -Terminal.translateBufferLineToString = translateBufferLineToString; -Terminal.EventEmitter = EventEmitter; -Terminal.inherits = inherits; - -/** - * Adds an event listener to the terminal. - * - * @param {string} event The name of the event. TODO: Document all event types - * @param {function} callback The function to call when the event is triggered. - */ -Terminal.on = on; -Terminal.off = off; -Terminal.cancel = cancel; - -module.exports = Terminal; From 33cc78c987e429896473e2970cb0cc587e80fc83 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 6 Aug 2017 01:42:57 -0700 Subject: [PATCH 017/378] Add explicit types on set/getOption --- src/Terminal.ts | 19 +++++++++++++++---- src/Types.ts | 23 +++++++++++++++++++++++ 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index c1fd1e7e..06a1a316 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -29,7 +29,7 @@ import * as Mouse from './utils/Mouse'; import { CHARSETS } from './Charsets'; import { getRawByteCoords } from './utils/Mouse'; import { translateBufferLineToString } from './utils/BufferLine'; -import { CustomKeyEventHandler, Charset, LinkMatcherHandler, LinkMatcherValidationCallback, CharData, LineData } from './Types'; +import { CustomKeyEventHandler, Charset, LinkMatcherHandler, LinkMatcherValidationCallback, CharData, LineData, Option, StringOption, BooleanOption, StringArrayOption, NumberOption, GeometryOption, HandlerOption } from './Types'; import { ITerminal, IBrowser, ITerminalOptions, IInputHandlingTerminal, ILinkMatcherOptions } from './Interfaces'; // Declare for RequireJS in loadAddon @@ -415,7 +415,13 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * Retrieves an option's value from the terminal. * @param {string} key The option key. */ - public getOption(key: string): any { + public getOption(key: StringOption): string; + public getOption(key: BooleanOption): boolean; + public getOption(key: StringArrayOption): number[]; + public getOption(key: NumberOption): number; + public getOption(key: GeometryOption): [number, number]; + public getOption(key: HandlerOption): (data: string) => void; + public getOption(key: Option): any { if (!(key in DEFAULT_OPTIONS)) { throw new Error('No option with key "' + key + '"'); } @@ -432,8 +438,13 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * @param {string} key The option key. * @param {any} value The option value. */ - public setOption(key: string, value: any): void { - // TODO: Give value a better type (boolean | string, ...) + public setOption(key: StringOption, value: string): void; + public setOption(key: BooleanOption, value: boolean): void; + public setOption(key: StringArrayOption, value: number[]): void; + public setOption(key: NumberOption, value: number): void; + public setOption(key: GeometryOption, value: [number, number]): void; + public setOption(key: HandlerOption, value: (data: string) => void): void; + public setOption(key: Option, value: any): void { if (!(key in DEFAULT_OPTIONS)) { throw new Error('No option with key "' + key + '"'); } diff --git a/src/Types.ts b/src/Types.ts index 0753d80e..0de78d52 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -18,3 +18,26 @@ export type Charset = {[key: string]: string}; export type CharData = [number, string, number]; export type LineData = CharData[]; + +export type Option = BooleanOption | StringOption | StringArrayOption | NumberOption | GeometryOption | HandlerOption; +export type BooleanOption = + 'cancelEvents' | + 'convertEol' | + 'cursorBlink' | + 'debug' | + 'disableStdin' | + 'popOnBell' | + 'screenKeys' | + 'useFlowControl' | + 'visualBell'; +export type StringOption = + 'cursorStyle' | + 'termName'; +export type StringArrayOption = 'colors'; +export type NumberOption = + 'cols' | + 'rows' | + 'tabStopWidth' | + 'scrollback'; +export type GeometryOption = 'geometry'; +export type HandlerOption = 'handler'; From 6cf2e8753e908485192e6e7ed417df880b4aec4e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 6 Aug 2017 01:44:27 -0700 Subject: [PATCH 018/378] Remove comments --- src/Terminal.ts | 39 --------------------------------------- src/xterm.ts | 11 +++++++++++ 2 files changed, 11 insertions(+), 39 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 06a1a316..cd44e7fb 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -35,17 +35,6 @@ import { ITerminal, IBrowser, ITerminalOptions, IInputHandlingTerminal, ILinkMat // Declare for RequireJS in loadAddon declare var define: any; -/** - * Terminal Emulation References: - * http://vt100.net/ - * http://invisible-island.net/xterm/ctlseqs/ctlseqs.txt - * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html - * http://invisible-island.net/vttest/ - * http://www.inwap.com/pdp10/ansicode.txt - * http://linux.die.net/man/4/console_codes - * http://linux.die.net/man/7/urxvt - */ - // Let it work inside Node.js for automated testing purposes. const document = (typeof window !== 'undefined') ? window.document : null; @@ -2308,15 +2297,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT } } - - -// TODO: Make sure things work after this is removed -// each(keys(Terminal.defaults), function(key) { -// Terminal[key] = Terminal.defaults[key]; -// Terminal.options[key] = Terminal.defaults[key]; -// }); - - /** * Helpers */ @@ -2363,22 +2343,3 @@ function wasMondifierKeyOnlyEvent(ev: KeyboardEvent): boolean { ev.keyCode === 17 || // Ctrl ev.keyCode === 18; // Alt } - -/** - * Expose - */ - -// Terminal.translateBufferLineToString = translateBufferLineToString; -// Terminal.EventEmitter = EventEmitter; -// Terminal.inherits = inherits; - -/** - * Adds an event listener to the terminal. - * - * @param {string} event The name of the event. TODO: Document all event types - * @param {function} callback The function to call when the event is triggered. - */ -// Terminal.on = on; -// Terminal.off = off; - -// module.exports = Terminal; diff --git a/src/xterm.ts b/src/xterm.ts index 75751865..49e44151 100644 --- a/src/xterm.ts +++ b/src/xterm.ts @@ -4,4 +4,15 @@ import { Terminal } from './Terminal'; +/** + * Terminal Emulation References: + * http://vt100.net/ + * http://invisible-island.net/xterm/ctlseqs/ctlseqs.txt + * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html + * http://invisible-island.net/vttest/ + * http://www.inwap.com/pdp10/ansicode.txt + * http://linux.die.net/man/4/console_codes + * http://linux.die.net/man/7/urxvt + */ + module.exports = Terminal; From 6cdd5a64050fd8a1ff76cdf392dc17fd8981b85e Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 3 Aug 2017 14:43:51 -0500 Subject: [PATCH 019/378] Prevent webpack error --- src/addons/search/search.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/addons/search/search.ts b/src/addons/search/search.ts index 5a227a8a..1a9ac337 100644 --- a/src/addons/search/search.ts +++ b/src/addons/search/search.ts @@ -20,8 +20,7 @@ declare var window: any; /** * CommonJS environment */ - const xterm = '../../xterm'; - module.exports = addon(require(xterm)); + module.exports = addon(require('../../xterm')); } else if (typeof define == 'function') { /** * Require.js is available From 24932b16d79e733d23fb65a5ceca1f035def75bc Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 6 Aug 2017 10:25:25 -0700 Subject: [PATCH 020/378] jsdoc --- src/Buffer.ts | 11 +++++++++++ src/BufferSet.ts | 5 +++++ 2 files changed, 16 insertions(+) diff --git a/src/Buffer.ts b/src/Buffer.ts index 9a28438d..eb346d5b 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -44,6 +44,9 @@ export class Buffer implements IBuffer { return this._lines; } + /** + * Fills the buffer's viewport with blank lines. + */ public fillViewportRows(): void { if (this._lines.length === 0) { let i = this._terminal.rows; @@ -53,6 +56,9 @@ export class Buffer implements IBuffer { } } + /** + * Clears the buffer to it's initial state, discarding all previous data. + */ public clear(): void { this.ydisp = 0; this.ybase = 0; @@ -65,6 +71,11 @@ export class Buffer implements IBuffer { this.scrollBottom = this._terminal.rows - 1; } + /** + * Resizes the buffer, adjusting its data accordingly. + * @param newCols The new number of columns. + * @param newRows The new number of rows. + */ public resize(newCols: number, newRows: number): void { // Don't resize the buffer if it's empty and hasn't been used yet. if (this._lines.length === 0) { diff --git a/src/BufferSet.ts b/src/BufferSet.ts index 345191cf..24d6c314 100644 --- a/src/BufferSet.ts +++ b/src/BufferSet.ts @@ -76,6 +76,11 @@ export class BufferSet extends EventEmitter implements IBufferSet { this.emit('activate', this._alt); } + /** + * Resizes both normal and alt buffers, adjusting their data accordingly. + * @param newCols The new number of columns. + * @param newRows The new number of rows. + */ public resize(newCols: number, newRows: number): void { this._normal.resize(newCols, newRows); this._alt.resize(newCols, newRows); From 9358bf24db5c7b4d4134813c1054fbf938843a85 Mon Sep 17 00:00:00 2001 From: Paris Kasidiaris Date: Sun, 6 Aug 2017 17:50:53 +0000 Subject: [PATCH 021/378] Bump version to 2.9.1 Signed-off-by: Paris Kasidiaris --- bower.json | 2 +- dist/xterm.js | 193 +++++++++++++++++++++++++--------------------- dist/xterm.js.map | 2 +- package.json | 2 +- 4 files changed, 106 insertions(+), 93 deletions(-) diff --git a/bower.json b/bower.json index 2f64c187..512324cc 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "xterm.js", - "version": "2.9.0", + "version": "2.9.1", "ignore": ["demo", "test", ".gitignore"], "main": [ "dist/xterm.js", diff --git a/dist/xterm.js b/dist/xterm.js index 5abdbc3f..8380c762 100644 --- a/dist/xterm.js +++ b/dist/xterm.js @@ -3,25 +3,93 @@ Object.defineProperty(exports, "__esModule", { value: true }); var CircularList_1 = require("./utils/CircularList"); var Buffer = (function () { - function Buffer(terminal, ydisp, ybase, y, x, scrollBottom, scrollTop, tabs) { - if (ydisp === void 0) { ydisp = 0; } - if (ybase === void 0) { ybase = 0; } - if (y === void 0) { y = 0; } - if (x === void 0) { x = 0; } - if (scrollBottom === void 0) { scrollBottom = 0; } - if (scrollTop === void 0) { scrollTop = 0; } - if (tabs === void 0) { tabs = {}; } - this.terminal = terminal; - this.ydisp = ydisp; - this.ybase = ybase; - this.y = y; - this.x = x; - this.scrollBottom = scrollBottom; - this.scrollTop = scrollTop; - this.tabs = tabs; - this.lines = new CircularList_1.CircularList(this.terminal.scrollback); - this.scrollBottom = this.terminal.rows - 1; + function Buffer(_terminal) { + this._terminal = _terminal; + this.clear(); } + Object.defineProperty(Buffer.prototype, "lines", { + get: function () { + return this._lines; + }, + enumerable: true, + configurable: true + }); + Buffer.prototype.fillViewportRows = function () { + if (this._lines.length === 0) { + var i = this._terminal.rows; + while (i--) { + this.lines.push(this._terminal.blankLine()); + } + } + }; + Buffer.prototype.clear = function () { + this.ydisp = 0; + this.ybase = 0; + this.y = 0; + this.x = 0; + this.scrollBottom = 0; + this.scrollTop = 0; + this.tabs = {}; + this._lines = new CircularList_1.CircularList(this._terminal.scrollback); + this.scrollBottom = this._terminal.rows - 1; + }; + Buffer.prototype.resize = function (newCols, newRows) { + if (this._lines.length === 0) { + return; + } + if (this._terminal.cols < newCols) { + var ch = [this._terminal.defAttr, ' ', 1]; + for (var i = 0; i < this._lines.length; i++) { + if (this._lines.get(i) === undefined) { + this._lines.set(i, this._terminal.blankLine()); + } + while (this._lines.get(i).length < newCols) { + this._lines.get(i).push(ch); + } + } + } + var addToY = 0; + if (this._terminal.rows < newRows) { + for (var y = this._terminal.rows; y < newRows; y++) { + if (this._lines.length < newRows + this.ybase) { + if (this.ybase > 0 && this._lines.length <= this.ybase + this.y + addToY + 1) { + this.ybase--; + addToY++; + if (this.ydisp > 0) { + this.ydisp--; + } + } + else { + this._lines.push(this._terminal.blankLine()); + } + } + } + } + else { + for (var y = this._terminal.rows; y > newRows; y--) { + if (this._lines.length > newRows + this.ybase) { + if (this._lines.length > this.ybase + this.y + 1) { + this._lines.pop(); + } + else { + this.ybase++; + this.ydisp++; + } + } + } + } + if (this.y >= newRows) { + this.y = newRows - 1; + } + if (addToY) { + this.y += addToY; + } + if (this.x >= newCols) { + this.x = newCols - 1; + } + this.scrollTop = 0; + this.scrollBottom = newRows - 1; + }; return Buffer; }()); exports.Buffer = Buffer; @@ -49,6 +117,7 @@ var BufferSet = (function (_super) { var _this = _super.call(this) || this; _this._terminal = _terminal; _this._normal = new Buffer_1.Buffer(_this._terminal); + _this._normal.fillViewportRows(); _this._alt = new Buffer_1.Buffer(_this._terminal); _this._activeBuffer = _this._normal; return _this; @@ -75,13 +144,19 @@ var BufferSet = (function (_super) { configurable: true }); BufferSet.prototype.activateNormalBuffer = function () { + this._alt.clear(); this._activeBuffer = this._normal; this.emit('activate', this._normal); }; BufferSet.prototype.activateAltBuffer = function () { + this._alt.fillViewportRows(); this._activeBuffer = this._alt; this.emit('activate', this._alt); }; + BufferSet.prototype.resize = function (newCols, newRows) { + this._normal.resize(newCols, newRows); + this._alt.resize(newCols, newRows); + }; return BufferSet; }(EventEmitter_1.EventEmitter)); exports.BufferSet = BufferSet; @@ -1024,7 +1099,6 @@ var InputHandler = (function () { case 47: case 1047: this._terminal.buffers.activateAltBuffer(); - this._terminal.reset(); this._terminal.viewport.syncScrollArea(); this._terminal.showCursor(); break; @@ -3596,10 +3670,6 @@ function Terminal(options) { this.buffers.on('activate', function (buffer) { this._terminal.buffer = buffer; }); - var i = this.rows; - while (i--) { - this.buffer.lines.push(this.blankLine()); - } if (this.selectionManager) { this.selectionManager.setBuffer(this.buffer.lines); } @@ -4775,74 +4845,19 @@ Terminal.prototype.resize = function (x, y) { x = 1; if (y < 1) y = 1; - j = this.cols; - if (j < x) { - ch = [this.defAttr, ' ', 1]; - i = this.buffer.lines.length; - while (i--) { - if (this.buffer.lines.get(i) === undefined) { - this.buffer.lines.set(i, this.blankLine()); - } - while (this.buffer.lines.get(i).length < x) { - this.buffer.lines.get(i).push(ch); - } - } + this.buffers.resize(x, y); + while (this.children.length < y) { + this.insertRow(); + } + while (this.children.length > y) { + el = this.children.shift(); + if (!el) + continue; + el.parentNode.removeChild(el); } this.cols = x; - this.setupStops(this.cols); - j = this.rows; - addToY = 0; - if (j < y) { - el = this.element; - while (j++ < y) { - if (this.buffer.lines.length < y + this.buffer.ybase) { - if (this.buffer.ybase > 0 && this.buffer.lines.length <= this.buffer.ybase + this.buffer.y + addToY + 1) { - this.buffer.ybase--; - addToY++; - if (this.buffer.ydisp > 0) { - this.buffer.ydisp--; - } - } - else { - this.buffer.lines.push(this.blankLine()); - } - } - if (this.children.length < y) { - this.insertRow(); - } - } - } - else { - while (j-- > y) { - if (this.buffer.lines.length > y + this.buffer.ybase) { - if (this.buffer.lines.length > this.buffer.ybase + this.buffer.y + 1) { - this.buffer.lines.pop(); - } - else { - this.buffer.ybase++; - this.buffer.ydisp++; - } - } - if (this.children.length > y) { - el = this.children.shift(); - if (!el) - continue; - el.parentNode.removeChild(el); - } - } - } this.rows = y; - if (this.buffer.y >= y) { - this.buffer.y = y - 1; - } - if (addToY) { - this.buffer.y += addToY; - } - if (this.buffer.x >= x) { - this.buffer.x = x - 1; - } - this.buffer.scrollTop = 0; - this.buffer.scrollBottom = y - 1; + this.setupStops(this.cols); this.charMeasure.measure(); this.refresh(0, this.rows - 1); this.geometry = [this.cols, this.rows]; @@ -4996,12 +5011,10 @@ Terminal.prototype.reset = function () { var customKeyEventHandler = this.customKeyEventHandler; var cursorBlinkInterval = this.cursorBlinkInterval; var inputHandler = this.inputHandler; - var buffers = this.buffers; Terminal.call(this, this.options); this.customKeyEventHandler = customKeyEventHandler; this.cursorBlinkInterval = cursorBlinkInterval; this.inputHandler = inputHandler; - this.buffers = buffers; this.refresh(0, this.rows - 1); this.viewport.syncScrollArea(); }; diff --git a/dist/xterm.js.map b/dist/xterm.js.map index 9611ff19..64e1b0e6 100644 --- a/dist/xterm.js.map +++ b/dist/xterm.js.map @@ -1 +1 @@ -{"version":3,"file":"xterm.js","sources":["../src/xterm.js","../src/utils/Mouse.ts","../src/utils/Generic.ts","../src/utils/DomElementObjectPool.ts","../src/utils/CircularList.ts","../src/utils/CharMeasure.ts","../src/utils/BufferLine.ts","../src/utils/Browser.ts","../src/handlers/Clipboard.ts","../src/Viewport.ts","../src/SelectionModel.ts","../src/SelectionManager.ts","../src/Renderer.ts","../src/Parser.ts","../src/Linkifier.ts","../src/InputHandler.ts","../src/EventEmitter.ts","../src/EscapeSequences.ts","../src/CompositionHelper.ts","../src/Charsets.ts","../src/BufferSet.ts","../src/Buffer.ts","../node_modules/browserify/node_modules/browser-pack/_prelude.js"],"sourcesContent":["/**\n * xterm.js: xterm, in the browser\n * Originally forked from (with the author's permission):\n * Fabrice Bellard's javascript vt100 for jslinux:\n * http://bellard.org/jslinux/\n * Copyright (c) 2011 Fabrice Bellard\n * The original design remains. The terminal itself\n * has been extended to include xterm CSI codes, among\n * other features.\n * @license MIT\n */\n\nimport { BufferSet } from './BufferSet';\nimport { CompositionHelper } from './CompositionHelper';\nimport { EventEmitter } from './EventEmitter';\nimport { Viewport } from './Viewport';\nimport { rightClickHandler, moveTextAreaUnderMouseCursor, pasteHandler, copyHandler } from './handlers/Clipboard';\nimport { CircularList } from './utils/CircularList';\nimport { C0 } from './EscapeSequences';\nimport { InputHandler } from './InputHandler';\nimport { Parser } from './Parser';\nimport { Renderer } from './Renderer';\nimport { Linkifier } from './Linkifier';\nimport { SelectionManager } from './SelectionManager';\nimport { CharMeasure } from './utils/CharMeasure';\nimport * as Browser from './utils/Browser';\nimport * as Mouse from './utils/Mouse';\nimport { CHARSETS } from './Charsets';\nimport { getRawByteCoords } from './utils/Mouse';\nimport { translateBufferLineToString } from './utils/BufferLine';\n\n/**\n * Terminal Emulation References:\n * http://vt100.net/\n * http://invisible-island.net/xterm/ctlseqs/ctlseqs.txt\n * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html\n * http://invisible-island.net/vttest/\n * http://www.inwap.com/pdp10/ansicode.txt\n * http://linux.die.net/man/4/console_codes\n * http://linux.die.net/man/7/urxvt\n */\n\n// Let it work inside Node.js for automated testing purposes.\nvar document = (typeof window != 'undefined') ? window.document : null;\n\n/**\n * The amount of write requests to queue before sending an XOFF signal to the\n * pty process. This number must be small in order for ^C and similar sequences\n * to be responsive.\n */\nvar WRITE_BUFFER_PAUSE_THRESHOLD = 5;\n\n/**\n * The number of writes to perform in a single batch before allowing the\n * renderer to catch up with a 0ms setTimeout.\n */\nvar WRITE_BATCH_SIZE = 300;\n\n/**\n * The time between cursor blinks. This is driven by JS rather than a CSS\n * animation due to a bug in Chromium that causes it to use excessive CPU time.\n * See https://github.com/Microsoft/vscode/issues/22900\n */\nvar CURSOR_BLINK_INTERVAL = 600;\n\n/**\n * Terminal\n */\n\n/**\n * Creates a new `Terminal` object.\n *\n * @param {object} options An object containing a set of options, the available options are:\n * - `cursorBlink` (boolean): Whether the terminal cursor blinks\n * - `cols` (number): The number of columns of the terminal (horizontal size)\n * - `rows` (number): The number of rows of the terminal (vertical size)\n *\n * @public\n * @class Xterm Xterm\n * @alias module:xterm/src/xterm\n */\nfunction Terminal(options) {\n var self = this;\n\n if (!(this instanceof Terminal)) {\n return new Terminal(arguments[0], arguments[1], arguments[2]);\n }\n\n self.browser = Browser;\n self.cancel = Terminal.cancel;\n\n EventEmitter.call(this);\n\n if (typeof options === 'number') {\n options = {\n cols: arguments[0],\n rows: arguments[1],\n handler: arguments[2]\n };\n }\n\n options = options || {};\n\n\n Object.keys(Terminal.defaults).forEach(function(key) {\n if (options[key] == null) {\n options[key] = Terminal.options[key];\n\n if (Terminal[key] !== Terminal.defaults[key]) {\n options[key] = Terminal[key];\n }\n }\n self[key] = options[key];\n });\n\n if (options.colors.length === 8) {\n options.colors = options.colors.concat(Terminal._colors.slice(8));\n } else if (options.colors.length === 16) {\n options.colors = options.colors.concat(Terminal._colors.slice(16));\n } else if (options.colors.length === 10) {\n options.colors = options.colors.slice(0, -2).concat(\n Terminal._colors.slice(8, -2), options.colors.slice(-2));\n } else if (options.colors.length === 18) {\n options.colors = options.colors.concat(\n Terminal._colors.slice(16, -2), options.colors.slice(-2));\n }\n this.colors = options.colors;\n\n this.options = options;\n\n // this.context = options.context || window;\n // this.document = options.document || document;\n this.parent = options.body || options.parent || (\n document ? document.getElementsByTagName('body')[0] : null\n );\n\n this.cols = options.cols || options.geometry[0];\n this.rows = options.rows || options.geometry[1];\n this.geometry = [this.cols, this.rows];\n\n if (options.handler) {\n this.on('data', options.handler);\n }\n\n this.cursorState = 0;\n this.cursorHidden = false;\n this.convertEol;\n this.queue = '';\n this.customKeyEventHandler = null;\n this.cursorBlinkInterval = null;\n\n // modes\n this.applicationKeypad = false;\n this.applicationCursor = false;\n this.originMode = false;\n this.insertMode = false;\n this.wraparoundMode = true; // defaults: xterm - true, vt100 - false\n\n // charset\n this.charset = null;\n this.gcharset = null;\n this.glevel = 0;\n this.charsets = [null];\n\n // mouse properties\n this.decLocator;\n this.x10Mouse;\n this.vt200Mouse;\n this.vt300Mouse;\n this.normalMouse;\n this.mouseEvents;\n this.sendFocus;\n this.utfMouse;\n this.sgrMouse;\n this.urxvtMouse;\n\n // misc\n this.element;\n this.children;\n this.refreshStart;\n this.refreshEnd;\n this.savedX;\n this.savedY;\n this.savedCols;\n\n // stream\n this.readable = true;\n this.writable = true;\n\n this.defAttr = (0 << 18) | (257 << 9) | (256 << 0);\n this.curAttr = this.defAttr;\n\n this.params = [];\n this.currentParam = 0;\n this.prefix = '';\n this.postfix = '';\n\n this.inputHandler = new InputHandler(this);\n this.parser = new Parser(this.inputHandler, this);\n // Reuse renderer if the Terminal is being recreated via a Terminal.reset call.\n this.renderer = this.renderer || null;\n this.selectionManager = this.selectionManager || null;\n this.linkifier = this.linkifier || new Linkifier();\n\n // user input states\n this.writeBuffer = [];\n this.writeInProgress = false;\n\n /**\n * Whether _xterm.js_ sent XOFF in order to catch up with the pty process.\n * This is a distinct state from writeStopped so that if the user requested\n * XOFF via ^S that it will not automatically resume when the writeBuffer goes\n * below threshold.\n */\n this.xoffSentToCatchUp = false;\n\n /** Whether writing has been stopped as a result of XOFF */\n this.writeStopped = false;\n\n // leftover surrogate high from previous write invocation\n this.surrogate_high = '';\n\n // Create the terminal's buffers and set the current buffer\n this.buffers = new BufferSet(this);\n this.buffer = this.buffers.active; // Convenience shortcut;\n this.buffers.on('activate', function (buffer) {\n this._terminal.buffer = buffer;\n });\n\n var i = this.rows;\n\n while (i--) {\n this.buffer.lines.push(this.blankLine());\n }\n // Ensure the selection manager has the correct buffer\n if (this.selectionManager) {\n this.selectionManager.setBuffer(this.buffer.lines);\n }\n\n this.setupStops();\n\n // Store if user went browsing history in scrollback\n this.userScrolling = false;\n}\n\ninherits(Terminal, EventEmitter);\n\n/**\n * back_color_erase feature for xterm.\n */\nTerminal.prototype.eraseAttr = function() {\n // if (this.is('screen')) return this.defAttr;\n return (this.defAttr & ~0x1ff) | (this.curAttr & 0x1ff);\n};\n\n/**\n * Colors\n */\n\n// Colors 0-15\nTerminal.tangoColors = [\n // dark:\n '#2e3436',\n '#cc0000',\n '#4e9a06',\n '#c4a000',\n '#3465a4',\n '#75507b',\n '#06989a',\n '#d3d7cf',\n // bright:\n '#555753',\n '#ef2929',\n '#8ae234',\n '#fce94f',\n '#729fcf',\n '#ad7fa8',\n '#34e2e2',\n '#eeeeec'\n];\n\n// Colors 0-15 + 16-255\n// Much thanks to TooTallNate for writing this.\nTerminal.colors = (function() {\n var colors = Terminal.tangoColors.slice()\n , r = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]\n , i;\n\n // 16-231\n i = 0;\n for (; i < 216; i++) {\n out(r[(i / 36) % 6 | 0], r[(i / 6) % 6 | 0], r[i % 6]);\n }\n\n // 232-255 (grey)\n i = 0;\n for (; i < 24; i++) {\n r = 8 + i * 10;\n out(r, r, r);\n }\n\n function out(r, g, b) {\n colors.push('#' + hex(r) + hex(g) + hex(b));\n }\n\n function hex(c) {\n c = c.toString(16);\n return c.length < 2 ? '0' + c : c;\n }\n\n return colors;\n})();\n\nTerminal._colors = Terminal.colors.slice();\n\nTerminal.vcolors = (function() {\n var out = []\n , colors = Terminal.colors\n , i = 0\n , color;\n\n for (; i < 256; i++) {\n color = parseInt(colors[i].substring(1), 16);\n out.push([\n (color >> 16) & 0xff,\n (color >> 8) & 0xff,\n color & 0xff\n ]);\n }\n\n return out;\n})();\n\n/**\n * Options\n */\n\nTerminal.defaults = {\n colors: Terminal.colors,\n theme: 'default',\n convertEol: false,\n termName: 'xterm',\n geometry: [80, 24],\n cursorBlink: false,\n cursorStyle: 'block',\n visualBell: false,\n popOnBell: false,\n scrollback: 1000,\n screenKeys: false,\n debug: false,\n cancelEvents: false,\n disableStdin: false,\n useFlowControl: false,\n tabStopWidth: 8\n // programFeatures: false,\n // focusKeys: false,\n};\n\nTerminal.options = {};\n\nTerminal.focus = null;\n\neach(keys(Terminal.defaults), function(key) {\n Terminal[key] = Terminal.defaults[key];\n Terminal.options[key] = Terminal.defaults[key];\n});\n\n/**\n * Focus the terminal. Delegates focus handling to the terminal's DOM element.\n */\nTerminal.prototype.focus = function() {\n return this.textarea.focus();\n};\n\n/**\n * Retrieves an option's value from the terminal.\n * @param {string} key The option key.\n */\nTerminal.prototype.getOption = function(key) {\n if (!(key in Terminal.defaults)) {\n throw new Error('No option with key \"' + key + '\"');\n }\n\n if (typeof this.options[key] !== 'undefined') {\n return this.options[key];\n }\n\n return this[key];\n};\n\n/**\n * Sets an option on the terminal.\n * @param {string} key The option key.\n * @param {string} value The option value.\n */\nTerminal.prototype.setOption = function(key, value) {\n if (!(key in Terminal.defaults)) {\n throw new Error('No option with key \"' + key + '\"');\n }\n switch (key) {\n case 'scrollback':\n if (value < this.rows) {\n let msg = 'Setting the scrollback value less than the number of rows ';\n\n msg += `(${this.rows}) is not allowed.`;\n\n console.warn(msg);\n return false;\n }\n\n if (this.options[key] !== value) {\n if (this.buffer.lines.length > value) {\n const amountToTrim = this.buffer.lines.length - value;\n const needsRefresh = (this.buffer.ydisp - amountToTrim < 0);\n this.buffer.lines.trimStart(amountToTrim);\n this.buffer.ybase = Math.max(this.buffer.ybase - amountToTrim, 0);\n this.buffer.ydisp = Math.max(this.buffer.ydisp - amountToTrim, 0);\n if (needsRefresh) {\n this.refresh(0, this.rows - 1);\n }\n }\n this.buffer.lines.maxLength = value;\n this.viewport.syncScrollArea();\n }\n break;\n }\n this[key] = value;\n this.options[key] = value;\n switch (key) {\n case 'cursorBlink': this.setCursorBlinking(value); break;\n case 'cursorStyle':\n this.element.classList.toggle(`xterm-cursor-style-block`, value === 'block');\n this.element.classList.toggle(`xterm-cursor-style-underline`, value === 'underline');\n this.element.classList.toggle(`xterm-cursor-style-bar`, value === 'bar');\n break;\n case 'tabStopWidth': this.setupStops(); break;\n }\n};\n\nTerminal.prototype.restartCursorBlinking = function () {\n this.setCursorBlinking(this.options.cursorBlink);\n};\n\nTerminal.prototype.setCursorBlinking = function (enabled) {\n this.element.classList.toggle('xterm-cursor-blink', enabled);\n this.clearCursorBlinkingInterval();\n if (enabled) {\n var self = this;\n this.cursorBlinkInterval = setInterval(function () {\n self.element.classList.toggle('xterm-cursor-blink-on');\n }, CURSOR_BLINK_INTERVAL);\n }\n};\n\nTerminal.prototype.clearCursorBlinkingInterval = function () {\n this.element.classList.remove('xterm-cursor-blink-on');\n if (this.cursorBlinkInterval) {\n clearInterval(this.cursorBlinkInterval);\n this.cursorBlinkInterval = null;\n }\n};\n\n/**\n * Binds the desired focus behavior on a given terminal object.\n *\n * @static\n */\nTerminal.bindFocus = function (term) {\n on(term.textarea, 'focus', function (ev) {\n if (term.sendFocus) {\n term.send(C0.ESC + '[I');\n }\n term.element.classList.add('focus');\n term.showCursor();\n term.restartCursorBlinking.apply(term);\n Terminal.focus = term;\n term.emit('focus', {terminal: term});\n });\n};\n\n/**\n * Blur the terminal. Delegates blur handling to the terminal's DOM element.\n */\nTerminal.prototype.blur = function() {\n return this.textarea.blur();\n};\n\n/**\n * Binds the desired blur behavior on a given terminal object.\n *\n * @static\n */\nTerminal.bindBlur = function (term) {\n on(term.textarea, 'blur', function (ev) {\n term.refresh(term.buffer.y, term.buffer.y);\n if (term.sendFocus) {\n term.send(C0.ESC + '[O');\n }\n term.element.classList.remove('focus');\n term.clearCursorBlinkingInterval.apply(term);\n Terminal.focus = null;\n term.emit('blur', {terminal: term});\n });\n};\n\n/**\n * Initialize default behavior\n */\nTerminal.prototype.initGlobal = function() {\n var term = this;\n\n Terminal.bindKeys(this);\n Terminal.bindFocus(this);\n Terminal.bindBlur(this);\n\n // Bind clipboard functionality\n on(this.element, 'copy', event => {\n // If mouse events are active it means the selection manager is disabled and\n // copy should be handled by the host program.\n if (!term.hasSelection()) {\n return;\n }\n copyHandler(event, term, this.selectionManager);\n });\n const pasteHandlerWrapper = event => pasteHandler(event, term);\n on(this.textarea, 'paste', pasteHandlerWrapper);\n on(this.element, 'paste', pasteHandlerWrapper);\n\n // Handle right click context menus\n if (term.browser.isFirefox) {\n // Firefox doesn't appear to fire the contextmenu event on right click\n on(this.element, 'mousedown', event => {\n if (event.button == 2) {\n rightClickHandler(event, this.textarea, this.selectionManager);\n }\n });\n } else {\n on(this.element, 'contextmenu', event => {\n rightClickHandler(event, this.textarea, this.selectionManager);\n });\n }\n\n // Move the textarea under the cursor when middle clicking on Linux to ensure\n // middle click to paste selection works. This only appears to work in Chrome\n // at the time is writing.\n if (term.browser.isLinux) {\n // Use auxclick event over mousedown the latter doesn't seem to work. Note\n // that the regular click event doesn't fire for the middle mouse button.\n on(this.element, 'auxclick', event => {\n if (event.button === 1) {\n moveTextAreaUnderMouseCursor(event, this.textarea, this.selectionManager);\n }\n });\n }\n};\n\n/**\n * Apply key handling to the terminal\n */\nTerminal.bindKeys = function(term) {\n on(term.element, 'keydown', function(ev) {\n if (document.activeElement != this) {\n return;\n }\n term.keyDown(ev);\n }, true);\n\n on(term.element, 'keypress', function(ev) {\n if (document.activeElement != this) {\n return;\n }\n term.keyPress(ev);\n }, true);\n\n on(term.element, 'keyup', function(ev) {\n if (!wasMondifierKeyOnlyEvent(ev)) {\n term.focus(term);\n }\n }, true);\n\n on(term.textarea, 'keydown', function(ev) {\n term.keyDown(ev);\n }, true);\n\n on(term.textarea, 'keypress', function(ev) {\n term.keyPress(ev);\n // Truncate the textarea's value, since it is not needed\n this.value = '';\n }, true);\n\n on(term.textarea, 'compositionstart', term.compositionHelper.compositionstart.bind(term.compositionHelper));\n on(term.textarea, 'compositionupdate', term.compositionHelper.compositionupdate.bind(term.compositionHelper));\n on(term.textarea, 'compositionend', term.compositionHelper.compositionend.bind(term.compositionHelper));\n term.on('refresh', term.compositionHelper.updateCompositionElements.bind(term.compositionHelper));\n term.on('refresh', function (data) {\n term.queueLinkification(data.start, data.end)\n });\n};\n\n\n/**\n * Insert the given row to the terminal or produce a new one\n * if no row argument is passed. Return the inserted row.\n * @param {HTMLElement} row (optional) The row to append to the terminal.\n */\nTerminal.prototype.insertRow = function (row) {\n if (typeof row != 'object') {\n row = document.createElement('div');\n }\n\n this.rowContainer.appendChild(row);\n this.children.push(row);\n\n return row;\n};\n\n/**\n * Opens the terminal within an element.\n *\n * @param {HTMLElement} parent The element to create the terminal within.\n * @param {boolean} focus Focus the terminal, after it gets instantiated in the DOM\n */\nTerminal.prototype.open = function(parent, focus) {\n var self=this, i=0, div;\n\n this.parent = parent || this.parent;\n\n if (!this.parent) {\n throw new Error('Terminal requires a parent element.');\n }\n\n // Grab global elements\n this.context = this.parent.ownerDocument.defaultView;\n this.document = this.parent.ownerDocument;\n this.body = this.document.getElementsByTagName('body')[0];\n\n //Create main element container\n this.element = this.document.createElement('div');\n this.element.classList.add('terminal');\n this.element.classList.add('xterm');\n this.element.classList.add('xterm-theme-' + this.theme);\n this.element.classList.add(`xterm-cursor-style-${this.options.cursorStyle}`);\n this.setCursorBlinking(this.options.cursorBlink);\n\n this.element.setAttribute('tabindex', 0);\n\n this.viewportElement = document.createElement('div');\n this.viewportElement.classList.add('xterm-viewport');\n this.element.appendChild(this.viewportElement);\n this.viewportScrollArea = document.createElement('div');\n this.viewportScrollArea.classList.add('xterm-scroll-area');\n this.viewportElement.appendChild(this.viewportScrollArea);\n\n // Create the selection container.\n this.selectionContainer = document.createElement('div');\n this.selectionContainer.classList.add('xterm-selection');\n this.element.appendChild(this.selectionContainer);\n\n // Create the container that will hold the lines of the terminal and then\n // produce the lines the lines.\n this.rowContainer = document.createElement('div');\n this.rowContainer.classList.add('xterm-rows');\n this.element.appendChild(this.rowContainer);\n this.children = [];\n this.linkifier.attachToDom(document, this.children);\n\n // Create the container that will hold helpers like the textarea for\n // capturing DOM Events. Then produce the helpers.\n this.helperContainer = document.createElement('div');\n this.helperContainer.classList.add('xterm-helpers');\n // TODO: This should probably be inserted once it's filled to prevent an additional layout\n this.element.appendChild(this.helperContainer);\n this.textarea = document.createElement('textarea');\n this.textarea.classList.add('xterm-helper-textarea');\n this.textarea.setAttribute('autocorrect', 'off');\n this.textarea.setAttribute('autocapitalize', 'off');\n this.textarea.setAttribute('spellcheck', 'false');\n this.textarea.tabIndex = 0;\n this.textarea.addEventListener('focus', function() {\n self.emit('focus', {terminal: self});\n });\n this.textarea.addEventListener('blur', function() {\n self.emit('blur', {terminal: self});\n });\n this.helperContainer.appendChild(this.textarea);\n\n this.compositionView = document.createElement('div');\n this.compositionView.classList.add('composition-view');\n this.compositionHelper = new CompositionHelper(this.textarea, this.compositionView, this);\n this.helperContainer.appendChild(this.compositionView);\n\n this.charSizeStyleElement = document.createElement('style');\n this.helperContainer.appendChild(this.charSizeStyleElement);\n\n for (; i < this.rows; i++) {\n this.insertRow();\n }\n this.parent.appendChild(this.element);\n\n this.charMeasure = new CharMeasure(document, this.helperContainer);\n this.charMeasure.on('charsizechanged', function () {\n self.updateCharSizeStyles();\n });\n this.charMeasure.measure();\n\n this.viewport = new Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasure);\n this.renderer = new Renderer(this);\n this.selectionManager = new SelectionManager(\n this, this.buffer.lines, this.rowContainer, this.charMeasure\n );\n this.selectionManager.on('refresh', data => {\n this.renderer.refreshSelection(data.start, data.end);\n });\n this.selectionManager.on('newselection', text => {\n // If there's a new selection, put it into the textarea, focus and select it\n // in order to register it as a selection on the OS. This event is fired\n // only on Linux to enable middle click to paste selection.\n this.textarea.value = text;\n this.textarea.focus();\n this.textarea.select();\n });\n this.on('scroll', () => this.selectionManager.refresh());\n this.viewportElement.addEventListener('scroll', () => this.selectionManager.refresh());\n\n // Setup loop that draws to screen\n this.refresh(0, this.rows - 1);\n\n // Initialize global actions that\n // need to be taken on the document.\n this.initGlobal();\n\n /**\n * Automatic focus functionality.\n * TODO: Default to `false` starting with xterm.js 3.0.\n */\n if (typeof focus == 'undefined') {\n let message = 'You did not pass the `focus` argument in `Terminal.prototype.open()`.\\n';\n\n message += 'The `focus` argument now defaults to `true` but starting with xterm.js 3.0 ';\n message += 'it will default to `false`.';\n\n console.warn(message);\n focus = true;\n }\n\n if (focus) {\n this.focus();\n }\n\n // Listen for mouse events and translate\n // them into terminal mouse protocols.\n this.bindMouse();\n\n /**\n * This event is emitted when terminal has completed opening.\n *\n * @event open\n */\n this.emit('open');\n};\n\n\n/**\n * Attempts to load an add-on using CommonJS or RequireJS (whichever is available).\n * @param {string} addon The name of the addon to load\n * @static\n */\nTerminal.loadAddon = function(addon, callback) {\n if (typeof exports === 'object' && typeof module === 'object') {\n // CommonJS\n return require('./addons/' + addon + '/' + addon);\n } else if (typeof define == 'function') {\n // RequireJS\n return require(['./addons/' + addon + '/' + addon], callback);\n } else {\n console.error('Cannot load a module without a CommonJS or RequireJS environment.');\n return false;\n }\n};\n\n/**\n * Updates the helper CSS class with any changes necessary after the terminal's\n * character width has been changed.\n */\nTerminal.prototype.updateCharSizeStyles = function() {\n this.charSizeStyleElement.textContent =\n `.xterm-wide-char{width:${this.charMeasure.width * 2}px;}` +\n `.xterm-normal-char{width:${this.charMeasure.width}px;}` +\n `.xterm-rows > div{height:${this.charMeasure.height}px;}`;\n}\n\n/**\n * XTerm mouse events\n * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking\n * To better understand these\n * the xterm code is very helpful:\n * Relevant files:\n * button.c, charproc.c, misc.c\n * Relevant functions in xterm/button.c:\n * BtnCode, EmitButtonCode, EditorButton, SendMousePosition\n */\nTerminal.prototype.bindMouse = function() {\n var el = this.element, self = this, pressed = 32;\n\n // mouseup, mousedown, wheel\n // left click: ^[[M 3<^[[M#3<\n // wheel up: ^[[M`3>\n function sendButton(ev) {\n var button\n , pos;\n\n // get the xterm-style button\n button = getButton(ev);\n\n // get mouse coordinates\n pos = getRawByteCoords(ev, self.rowContainer, self.charMeasure, self.cols, self.rows);\n if (!pos) return;\n\n sendEvent(button, pos);\n\n switch (ev.overrideType || ev.type) {\n case 'mousedown':\n pressed = button;\n break;\n case 'mouseup':\n // keep it at the left\n // button, just in case.\n pressed = 32;\n break;\n case 'wheel':\n // nothing. don't\n // interfere with\n // `pressed`.\n break;\n }\n }\n\n // motion example of a left click:\n // ^[[M 3<^[[M@4<^[[M@5<^[[M@6<^[[M@7<^[[M#7<\n function sendMove(ev) {\n var button = pressed\n , pos;\n\n pos = getRawByteCoords(ev, self.rowContainer, self.charMeasure, self.cols, self.rows);\n if (!pos) return;\n\n // buttons marked as motions\n // are incremented by 32\n button += 32;\n\n sendEvent(button, pos);\n }\n\n // encode button and\n // position to characters\n function encode(data, ch) {\n if (!self.utfMouse) {\n if (ch === 255) return data.push(0);\n if (ch > 127) ch = 127;\n data.push(ch);\n } else {\n if (ch === 2047) return data.push(0);\n if (ch < 127) {\n data.push(ch);\n } else {\n if (ch > 2047) ch = 2047;\n data.push(0xC0 | (ch >> 6));\n data.push(0x80 | (ch & 0x3F));\n }\n }\n }\n\n // send a mouse event:\n // regular/utf8: ^[[M Cb Cx Cy\n // urxvt: ^[[ Cb ; Cx ; Cy M\n // sgr: ^[[ Cb ; Cx ; Cy M/m\n // vt300: ^[[ 24(1/3/5)~ [ Cx , Cy ] \\r\n // locator: CSI P e ; P b ; P r ; P c ; P p & w\n function sendEvent(button, pos) {\n // self.emit('mouse', {\n // x: pos.x - 32,\n // y: pos.x - 32,\n // button: button\n // });\n\n if (self.vt300Mouse) {\n // NOTE: Unstable.\n // http://www.vt100.net/docs/vt3xx-gp/chapter15.html\n button &= 3;\n pos.x -= 32;\n pos.y -= 32;\n var data = C0.ESC + '[24';\n if (button === 0) data += '1';\n else if (button === 1) data += '3';\n else if (button === 2) data += '5';\n else if (button === 3) return;\n else data += '0';\n data += '~[' + pos.x + ',' + pos.y + ']\\r';\n self.send(data);\n return;\n }\n\n if (self.decLocator) {\n // NOTE: Unstable.\n button &= 3;\n pos.x -= 32;\n pos.y -= 32;\n if (button === 0) button = 2;\n else if (button === 1) button = 4;\n else if (button === 2) button = 6;\n else if (button === 3) button = 3;\n self.send(C0.ESC + '['\n + button\n + ';'\n + (button === 3 ? 4 : 0)\n + ';'\n + pos.y\n + ';'\n + pos.x\n + ';'\n + (pos.page || 0)\n + '&w');\n return;\n }\n\n if (self.urxvtMouse) {\n pos.x -= 32;\n pos.y -= 32;\n pos.x++;\n pos.y++;\n self.send(C0.ESC + '[' + button + ';' + pos.x + ';' + pos.y + 'M');\n return;\n }\n\n if (self.sgrMouse) {\n pos.x -= 32;\n pos.y -= 32;\n self.send(C0.ESC + '[<'\n + (((button & 3) === 3 ? button & ~3 : button) - 32)\n + ';'\n + pos.x\n + ';'\n + pos.y\n + ((button & 3) === 3 ? 'm' : 'M'));\n return;\n }\n\n var data = [];\n\n encode(data, button);\n encode(data, pos.x);\n encode(data, pos.y);\n\n self.send(C0.ESC + '[M' + String.fromCharCode.apply(String, data));\n }\n\n function getButton(ev) {\n var button\n , shift\n , meta\n , ctrl\n , mod;\n\n // two low bits:\n // 0 = left\n // 1 = middle\n // 2 = right\n // 3 = release\n // wheel up/down:\n // 1, and 2 - with 64 added\n switch (ev.overrideType || ev.type) {\n case 'mousedown':\n button = ev.button != null\n ? +ev.button\n : ev.which != null\n ? ev.which - 1\n : null;\n\n if (self.browser.isMSIE) {\n button = button === 1 ? 0 : button === 4 ? 1 : button;\n }\n break;\n case 'mouseup':\n button = 3;\n break;\n case 'DOMMouseScroll':\n button = ev.detail < 0\n ? 64\n : 65;\n break;\n case 'wheel':\n button = ev.wheelDeltaY > 0\n ? 64\n : 65;\n break;\n }\n\n // next three bits are the modifiers:\n // 4 = shift, 8 = meta, 16 = control\n shift = ev.shiftKey ? 4 : 0;\n meta = ev.metaKey ? 8 : 0;\n ctrl = ev.ctrlKey ? 16 : 0;\n mod = shift | meta | ctrl;\n\n // no mods\n if (self.vt200Mouse) {\n // ctrl only\n mod &= ctrl;\n } else if (!self.normalMouse) {\n mod = 0;\n }\n\n // increment to SP\n button = (32 + (mod << 2)) + button;\n\n return button;\n }\n\n on(el, 'mousedown', function(ev) {\n\n // Prevent the focus on the textarea from getting lost\n // and make sure we get focused on mousedown\n ev.preventDefault();\n self.focus();\n\n if (!self.mouseEvents) return;\n\n // send the button\n sendButton(ev);\n\n // fix for odd bug\n //if (self.vt200Mouse && !self.normalMouse) {\n if (self.vt200Mouse) {\n ev.overrideType = 'mouseup';\n sendButton(ev);\n return self.cancel(ev);\n }\n\n // bind events\n if (self.normalMouse) on(self.document, 'mousemove', sendMove);\n\n // x10 compatibility mode can't send button releases\n if (!self.x10Mouse) {\n on(self.document, 'mouseup', function up(ev) {\n sendButton(ev);\n if (self.normalMouse) off(self.document, 'mousemove', sendMove);\n off(self.document, 'mouseup', up);\n return self.cancel(ev);\n });\n }\n\n return self.cancel(ev);\n });\n\n //if (self.normalMouse) {\n // on(self.document, 'mousemove', sendMove);\n //}\n\n on(el, 'wheel', function(ev) {\n if (!self.mouseEvents) return;\n if (self.x10Mouse\n || self.vt300Mouse\n || self.decLocator) return;\n sendButton(ev);\n return self.cancel(ev);\n });\n\n // allow wheel scrolling in\n // the shell for example\n on(el, 'wheel', function(ev) {\n if (self.mouseEvents) return;\n self.viewport.onWheel(ev);\n return self.cancel(ev);\n });\n\n on(el, 'touchstart', function(ev) {\n if (self.mouseEvents) return;\n self.viewport.onTouchStart(ev);\n return self.cancel(ev);\n });\n\n on(el, 'touchmove', function(ev) {\n if (self.mouseEvents) return;\n self.viewport.onTouchMove(ev);\n return self.cancel(ev);\n });\n};\n\n/**\n * Destroys the terminal.\n */\nTerminal.prototype.destroy = function() {\n this.readable = false;\n this.writable = false;\n this._events = {};\n this.handler = function() {};\n this.write = function() {};\n if (this.element && this.element.parentNode) {\n this.element.parentNode.removeChild(this.element);\n }\n //this.emit('close');\n};\n\n/**\n * Tells the renderer to refresh terminal content between two rows (inclusive) at the next\n * opportunity.\n * @param {number} start The row to start from (between 0 and this.rows - 1).\n * @param {number} end The row to end at (between start and this.rows - 1).\n */\nTerminal.prototype.refresh = function(start, end) {\n if (this.renderer) {\n this.renderer.queueRefresh(start, end);\n }\n};\n\n/**\n * Queues linkification for the specified rows.\n * @param {number} start The row to start from (between 0 and this.rows - 1).\n * @param {number} end The row to end at (between start and this.rows - 1).\n */\nTerminal.prototype.queueLinkification = function(start, end) {\n if (this.linkifier) {\n for (let i = start; i <= end; i++) {\n this.linkifier.linkifyRow(i);\n }\n }\n};\n\n/**\n * Display the cursor element\n */\nTerminal.prototype.showCursor = function() {\n if (!this.cursorState) {\n this.cursorState = 1;\n this.refresh(this.buffer.y, this.buffer.y);\n }\n};\n\n/**\n * Scroll the terminal down 1 row, creating a blank line.\n * @param {boolean} isWrapped Whether the new line is wrapped from the previous\n * line.\n */\nTerminal.prototype.scroll = function(isWrapped) {\n var row;\n\n // Make room for the new row in lines\n if (this.buffer.lines.length === this.buffer.lines.maxLength) {\n this.buffer.lines.trimStart(1);\n this.buffer.ybase--;\n if (this.buffer.ydisp !== 0) {\n this.buffer.ydisp--;\n }\n }\n\n this.buffer.ybase++;\n\n // TODO: Why is this done twice?\n if (!this.userScrolling) {\n this.buffer.ydisp = this.buffer.ybase;\n }\n\n // last line\n row = this.buffer.ybase + this.rows - 1;\n\n // subtract the bottom scroll region\n row -= this.rows - 1 - this.buffer.scrollBottom;\n\n if (row === this.buffer.lines.length) {\n // Optimization: pushing is faster than splicing when they amount to the same behavior\n this.buffer.lines.push(this.blankLine(undefined, isWrapped));\n } else {\n // add our new line\n this.buffer.lines.splice(row, 0, this.blankLine(undefined, isWrapped));\n }\n\n if (this.buffer.scrollTop !== 0) {\n if (this.buffer.ybase !== 0) {\n this.buffer.ybase--;\n if (!this.userScrolling) {\n this.buffer.ydisp = this.buffer.ybase;\n }\n }\n this.buffer.lines.splice(this.buffer.ybase + this.buffer.scrollTop, 1);\n }\n\n // this.maxRange();\n this.updateRange(this.buffer.scrollTop);\n this.updateRange(this.buffer.scrollBottom);\n\n /**\n * This event is emitted whenever the terminal is scrolled.\n * The one parameter passed is the new y display position.\n *\n * @event scroll\n */\n this.emit('scroll', this.buffer.ydisp);\n};\n\n/**\n * Scroll the display of the terminal\n * @param {number} disp The number of lines to scroll down (negatives scroll up).\n * @param {boolean} suppressScrollEvent Don't emit the scroll event as scrollDisp. This is used\n * to avoid unwanted events being handled by the veiwport when the event was triggered from the\n * viewport originally.\n */\nTerminal.prototype.scrollDisp = function(disp, suppressScrollEvent) {\n if (disp < 0) {\n if (this.buffer.ydisp === 0) {\n return;\n }\n this.userScrolling = true;\n } else if (disp + this.buffer.ydisp >= this.buffer.ybase) {\n this.userScrolling = false;\n }\n\n const oldYdisp = this.buffer.ydisp;\n this.buffer.ydisp = Math.max(Math.min(this.buffer.ydisp + disp, this.buffer.ybase), 0);\n\n // No change occurred, don't trigger scroll/refresh\n if (oldYdisp === this.buffer.ydisp) {\n return;\n }\n\n if (!suppressScrollEvent) {\n this.emit('scroll', this.buffer.ydisp);\n }\n\n this.refresh(0, this.rows - 1);\n};\n\n/**\n * Scroll the display of the terminal by a number of pages.\n * @param {number} pageCount The number of pages to scroll (negative scrolls up).\n */\nTerminal.prototype.scrollPages = function(pageCount) {\n this.scrollDisp(pageCount * (this.rows - 1));\n};\n\n/**\n * Scrolls the display of the terminal to the top.\n */\nTerminal.prototype.scrollToTop = function() {\n this.scrollDisp(-this.buffer.ydisp);\n};\n\n/**\n * Scrolls the display of the terminal to the bottom.\n */\nTerminal.prototype.scrollToBottom = function() {\n this.scrollDisp(this.buffer.ybase - this.buffer.ydisp);\n};\n\n/**\n * Writes text to the terminal.\n * @param {string} data The text to write to the terminal.\n */\nTerminal.prototype.write = function(data) {\n this.writeBuffer.push(data);\n\n // Send XOFF to pause the pty process if the write buffer becomes too large so\n // xterm.js can catch up before more data is sent. This is necessary in order\n // to keep signals such as ^C responsive.\n if (this.options.useFlowControl && !this.xoffSentToCatchUp && this.writeBuffer.length >= WRITE_BUFFER_PAUSE_THRESHOLD) {\n // XOFF - stop pty pipe\n // XON will be triggered by emulator before processing data chunk\n this.send(C0.DC3);\n this.xoffSentToCatchUp = true;\n }\n\n if (!this.writeInProgress && this.writeBuffer.length > 0) {\n // Kick off a write which will write all data in sequence recursively\n this.writeInProgress = true;\n // Kick off an async innerWrite so more writes can come in while processing data\n var self = this;\n setTimeout(function () {\n self.innerWrite();\n });\n }\n};\n\nTerminal.prototype.innerWrite = function() {\n var writeBatch = this.writeBuffer.splice(0, WRITE_BATCH_SIZE);\n while (writeBatch.length > 0) {\n var data = writeBatch.shift();\n var l = data.length, i = 0, j, cs, ch, code, low, ch_width, row;\n\n // If XOFF was sent in order to catch up with the pty process, resume it if\n // the writeBuffer is empty to allow more data to come in.\n if (this.xoffSentToCatchUp && writeBatch.length === 0 && this.writeBuffer.length === 0) {\n this.send(C0.DC1);\n this.xoffSentToCatchUp = false;\n }\n\n this.refreshStart = this.buffer.y;\n this.refreshEnd = this.buffer.y;\n\n // HACK: Set the parser state based on it's state at the time of return.\n // This works around the bug #662 which saw the parser state reset in the\n // middle of parsing escape sequence in two chunks. For some reason the\n // state of the parser resets to 0 after exiting parser.parse. This change\n // just sets the state back based on the correct return statement.\n var state = this.parser.parse(data);\n this.parser.setState(state);\n\n this.updateRange(this.buffer.y);\n this.refresh(this.refreshStart, this.refreshEnd);\n }\n if (this.writeBuffer.length > 0) {\n // Allow renderer to catch up before processing the next batch\n var self = this;\n setTimeout(function () {\n self.innerWrite();\n }, 0);\n } else {\n this.writeInProgress = false;\n }\n};\n\n/**\n * Writes text to the terminal, followed by a break line character (\\n).\n * @param {string} data The text to write to the terminal.\n */\nTerminal.prototype.writeln = function(data) {\n this.write(data + '\\r\\n');\n};\n\n/**\n * DEPRECATED: only for backward compatibility. Please use attachCustomKeyEventHandler() instead.\n * @param {function} customKeydownHandler The custom KeyboardEvent handler to attach. This is a\n * function that takes a KeyboardEvent, allowing consumers to stop propogation and/or prevent\n * the default action. The function returns whether the event should be processed by xterm.js.\n */\nTerminal.prototype.attachCustomKeydownHandler = function(customKeydownHandler) {\n let message = 'attachCustomKeydownHandler() is DEPRECATED and will be removed soon. Please use attachCustomKeyEventHandler() instead.';\n console.warn(message);\n this.attachCustomKeyEventHandler(customKeydownHandler);\n};\n\n/**\n * Attaches a custom key event handler which is run before keys are processed, giving consumers of\n * xterm.js ultimate control as to what keys should be processed by the terminal and what keys\n * should not.\n * @param {function} customKeyEventHandler The custom KeyboardEvent handler to attach. This is a\n * function that takes a KeyboardEvent, allowing consumers to stop propogation and/or prevent\n * the default action. The function returns whether the event should be processed by xterm.js.\n */\nTerminal.prototype.attachCustomKeyEventHandler = function(customKeyEventHandler) {\n this.customKeyEventHandler = customKeyEventHandler;\n};\n\n/**\n * Attaches a http(s) link handler, forcing web links to behave differently to\n * regular tags. This will trigger a refresh as links potentially need to be\n * reconstructed. Calling this with null will remove the handler.\n * @param {LinkMatcherHandler} handler The handler callback function.\n */\nTerminal.prototype.setHypertextLinkHandler = function(handler) {\n if (!this.linkifier) {\n throw new Error('Cannot attach a hypertext link handler before Terminal.open is called');\n }\n this.linkifier.setHypertextLinkHandler(handler);\n // Refresh to force links to refresh\n this.refresh(0, this.rows - 1);\n};\n\n/**\n * Attaches a validation callback for hypertext links. This is useful to use\n * validation logic or to do something with the link's element and url.\n * @param {LinkMatcherValidationCallback} callback The callback to use, this can\n * be cleared with null.\n */\nTerminal.prototype.setHypertextValidationCallback = function(callback) {\n if (!this.linkifier) {\n throw new Error('Cannot attach a hypertext validation callback before Terminal.open is called');\n }\n this.linkifier.setHypertextValidationCallback(callback);\n // Refresh to force links to refresh\n this.refresh(0, this.rows - 1);\n};\n\n/**\n * Registers a link matcher, allowing custom link patterns to be matched and\n * handled.\n * @param {RegExp} regex The regular expression to search for, specifically\n * this searches the textContent of the rows. You will want to use \\s to match\n * a space ' ' character for example.\n * @param {LinkMatcherHandler} handler The callback when the link is called.\n * @param {LinkMatcherOptions} [options] Options for the link matcher.\n * @return {number} The ID of the new matcher, this can be used to deregister.\n */\nTerminal.prototype.registerLinkMatcher = function(regex, handler, options) {\n if (this.linkifier) {\n var matcherId = this.linkifier.registerLinkMatcher(regex, handler, options);\n this.refresh(0, this.rows - 1);\n return matcherId;\n }\n};\n\n/**\n * Deregisters a link matcher if it has been registered.\n * @param {number} matcherId The link matcher's ID (returned after register)\n */\nTerminal.prototype.deregisterLinkMatcher = function(matcherId) {\n if (this.linkifier) {\n if (this.linkifier.deregisterLinkMatcher(matcherId)) {\n this.refresh(0, this.rows - 1);\n }\n }\n};\n\n/**\n * Gets whether the terminal has an active selection.\n */\nTerminal.prototype.hasSelection = function() {\n return this.selectionManager ? this.selectionManager.hasSelection : false;\n};\n\n/**\n * Gets the terminal's current selection, this is useful for implementing copy\n * behavior outside of xterm.js.\n */\nTerminal.prototype.getSelection = function() {\n return this.selectionManager ? this.selectionManager.selectionText : '';\n};\n\n/**\n * Clears the current terminal selection.\n */\nTerminal.prototype.clearSelection = function() {\n if (this.selectionManager) {\n this.selectionManager.clearSelection();\n }\n};\n\n/**\n * Selects all text within the terminal.\n */\nTerminal.prototype.selectAll = function() {\n if (this.selectionManager) {\n this.selectionManager.selectAll();\n }\n};\n\n/**\n * Handle a keydown event\n * Key Resources:\n * - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent\n * @param {KeyboardEvent} ev The keydown event to be handled.\n */\nTerminal.prototype.keyDown = function(ev) {\n if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) {\n return false;\n }\n\n this.restartCursorBlinking();\n\n if (!this.compositionHelper.keydown.bind(this.compositionHelper)(ev)) {\n if (this.buffer.ybase !== this.buffer.ydisp) {\n this.scrollToBottom();\n }\n return false;\n }\n\n var self = this;\n var result = this.evaluateKeyEscapeSequence(ev);\n\n if (result.key === C0.DC3) { // XOFF\n this.writeStopped = true;\n } else if (result.key === C0.DC1) { // XON\n this.writeStopped = false;\n }\n\n if (result.scrollDisp) {\n this.scrollDisp(result.scrollDisp);\n return this.cancel(ev, true);\n }\n\n if (isThirdLevelShift(this, ev)) {\n return true;\n }\n\n if (result.cancel) {\n // The event is canceled at the end already, is this necessary?\n this.cancel(ev, true);\n }\n\n if (!result.key) {\n return true;\n }\n\n this.emit('keydown', ev);\n this.emit('key', result.key, ev);\n this.showCursor();\n this.handler(result.key);\n\n return this.cancel(ev, true);\n};\n\n/**\n * Returns an object that determines how a KeyboardEvent should be handled. The key of the\n * returned value is the new key code to pass to the PTY.\n *\n * Reference: http://invisible-island.net/xterm/ctlseqs/ctlseqs.html\n * @param {KeyboardEvent} ev The keyboard event to be translated to key escape sequence.\n */\nTerminal.prototype.evaluateKeyEscapeSequence = function(ev) {\n var result = {\n // Whether to cancel event propogation (NOTE: this may not be needed since the event is\n // canceled at the end of keyDown\n cancel: false,\n // The new key even to emit\n key: undefined,\n // The number of characters to scroll, if this is defined it will cancel the event\n scrollDisp: undefined\n };\n var modifiers = ev.shiftKey << 0 | ev.altKey << 1 | ev.ctrlKey << 2 | ev.metaKey << 3;\n switch (ev.keyCode) {\n case 8:\n // backspace\n if (ev.shiftKey) {\n result.key = C0.BS; // ^H\n break;\n }\n result.key = C0.DEL; // ^?\n break;\n case 9:\n // tab\n if (ev.shiftKey) {\n result.key = C0.ESC + '[Z';\n break;\n }\n result.key = C0.HT;\n result.cancel = true;\n break;\n case 13:\n // return/enter\n result.key = C0.CR;\n result.cancel = true;\n break;\n case 27:\n // escape\n result.key = C0.ESC;\n result.cancel = true;\n break;\n case 37:\n // left-arrow\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'D';\n // HACK: Make Alt + left-arrow behave like Ctrl + left-arrow: move one word backwards\n // http://unix.stackexchange.com/a/108106\n // macOS uses different escape sequences than linux\n if (result.key == C0.ESC + '[1;3D') {\n result.key = (this.browser.isMac) ? C0.ESC + 'b' : C0.ESC + '[1;5D';\n }\n } else if (this.applicationCursor) {\n result.key = C0.ESC + 'OD';\n } else {\n result.key = C0.ESC + '[D';\n }\n break;\n case 39:\n // right-arrow\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'C';\n // HACK: Make Alt + right-arrow behave like Ctrl + right-arrow: move one word forward\n // http://unix.stackexchange.com/a/108106\n // macOS uses different escape sequences than linux\n if (result.key == C0.ESC + '[1;3C') {\n result.key = (this.browser.isMac) ? C0.ESC + 'f' : C0.ESC + '[1;5C';\n }\n } else if (this.applicationCursor) {\n result.key = C0.ESC + 'OC';\n } else {\n result.key = C0.ESC + '[C';\n }\n break;\n case 38:\n // up-arrow\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'A';\n // HACK: Make Alt + up-arrow behave like Ctrl + up-arrow\n // http://unix.stackexchange.com/a/108106\n if (result.key == C0.ESC + '[1;3A') {\n result.key = C0.ESC + '[1;5A';\n }\n } else if (this.applicationCursor) {\n result.key = C0.ESC + 'OA';\n } else {\n result.key = C0.ESC + '[A';\n }\n break;\n case 40:\n // down-arrow\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'B';\n // HACK: Make Alt + down-arrow behave like Ctrl + down-arrow\n // http://unix.stackexchange.com/a/108106\n if (result.key == C0.ESC + '[1;3B') {\n result.key = C0.ESC + '[1;5B';\n }\n } else if (this.applicationCursor) {\n result.key = C0.ESC + 'OB';\n } else {\n result.key = C0.ESC + '[B';\n }\n break;\n case 45:\n // insert\n if (!ev.shiftKey && !ev.ctrlKey) {\n // or + are used to\n // copy-paste on some systems.\n result.key = C0.ESC + '[2~';\n }\n break;\n case 46:\n // delete\n if (modifiers) {\n result.key = C0.ESC + '[3;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[3~';\n }\n break;\n case 36:\n // home\n if (modifiers)\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'H';\n else if (this.applicationCursor)\n result.key = C0.ESC + 'OH';\n else\n result.key = C0.ESC + '[H';\n break;\n case 35:\n // end\n if (modifiers)\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'F';\n else if (this.applicationCursor)\n result.key = C0.ESC + 'OF';\n else\n result.key = C0.ESC + '[F';\n break;\n case 33:\n // page up\n if (ev.shiftKey) {\n result.scrollDisp = -(this.rows - 1);\n } else {\n result.key = C0.ESC + '[5~';\n }\n break;\n case 34:\n // page down\n if (ev.shiftKey) {\n result.scrollDisp = this.rows - 1;\n } else {\n result.key = C0.ESC + '[6~';\n }\n break;\n case 112:\n // F1-F12\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'P';\n } else {\n result.key = C0.ESC + 'OP';\n }\n break;\n case 113:\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'Q';\n } else {\n result.key = C0.ESC + 'OQ';\n }\n break;\n case 114:\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'R';\n } else {\n result.key = C0.ESC + 'OR';\n }\n break;\n case 115:\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'S';\n } else {\n result.key = C0.ESC + 'OS';\n }\n break;\n case 116:\n if (modifiers) {\n result.key = C0.ESC + '[15;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[15~';\n }\n break;\n case 117:\n if (modifiers) {\n result.key = C0.ESC + '[17;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[17~';\n }\n break;\n case 118:\n if (modifiers) {\n result.key = C0.ESC + '[18;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[18~';\n }\n break;\n case 119:\n if (modifiers) {\n result.key = C0.ESC + '[19;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[19~';\n }\n break;\n case 120:\n if (modifiers) {\n result.key = C0.ESC + '[20;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[20~';\n }\n break;\n case 121:\n if (modifiers) {\n result.key = C0.ESC + '[21;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[21~';\n }\n break;\n case 122:\n if (modifiers) {\n result.key = C0.ESC + '[23;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[23~';\n }\n break;\n case 123:\n if (modifiers) {\n result.key = C0.ESC + '[24;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[24~';\n }\n break;\n default:\n // a-z and space\n if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) {\n if (ev.keyCode >= 65 && ev.keyCode <= 90) {\n result.key = String.fromCharCode(ev.keyCode - 64);\n } else if (ev.keyCode === 32) {\n // NUL\n result.key = String.fromCharCode(0);\n } else if (ev.keyCode >= 51 && ev.keyCode <= 55) {\n // escape, file sep, group sep, record sep, unit sep\n result.key = String.fromCharCode(ev.keyCode - 51 + 27);\n } else if (ev.keyCode === 56) {\n // delete\n result.key = String.fromCharCode(127);\n } else if (ev.keyCode === 219) {\n // ^[ - Control Sequence Introducer (CSI)\n result.key = String.fromCharCode(27);\n } else if (ev.keyCode === 220) {\n // ^\\ - String Terminator (ST)\n result.key = String.fromCharCode(28);\n } else if (ev.keyCode === 221) {\n // ^] - Operating System Command (OSC)\n result.key = String.fromCharCode(29);\n }\n } else if (!this.browser.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) {\n // On Mac this is a third level shift. Use instead.\n if (ev.keyCode >= 65 && ev.keyCode <= 90) {\n result.key = C0.ESC + String.fromCharCode(ev.keyCode + 32);\n } else if (ev.keyCode === 192) {\n result.key = C0.ESC + '`';\n } else if (ev.keyCode >= 48 && ev.keyCode <= 57) {\n result.key = C0.ESC + (ev.keyCode - 48);\n }\n } else if (this.browser.isMac && !ev.altKey && !ev.ctrlKey && ev.metaKey) {\n if (ev.keyCode === 65) { // cmd + a\n this.selectAll();\n }\n }\n break;\n }\n\n return result;\n};\n\n/**\n * Set the G level of the terminal\n * @param g\n */\nTerminal.prototype.setgLevel = function(g) {\n this.glevel = g;\n this.charset = this.charsets[g];\n};\n\n/**\n * Set the charset for the given G level of the terminal\n * @param g\n * @param charset\n */\nTerminal.prototype.setgCharset = function(g, charset) {\n this.charsets[g] = charset;\n if (this.glevel === g) {\n this.charset = charset;\n }\n};\n\n/**\n * Handle a keypress event.\n * Key Resources:\n * - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent\n * @param {KeyboardEvent} ev The keypress event to be handled.\n */\nTerminal.prototype.keyPress = function(ev) {\n var key;\n\n if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) {\n return false;\n }\n\n this.cancel(ev);\n\n if (ev.charCode) {\n key = ev.charCode;\n } else if (ev.which == null) {\n key = ev.keyCode;\n } else if (ev.which !== 0 && ev.charCode !== 0) {\n key = ev.which;\n } else {\n return false;\n }\n\n if (!key || (\n (ev.altKey || ev.ctrlKey || ev.metaKey) && !isThirdLevelShift(this, ev)\n )) {\n return false;\n }\n\n key = String.fromCharCode(key);\n\n this.emit('keypress', key, ev);\n this.emit('key', key, ev);\n this.showCursor();\n this.handler(key);\n\n return true;\n};\n\n/**\n * Send data for handling to the terminal\n * @param {string} data\n */\nTerminal.prototype.send = function(data) {\n var self = this;\n\n if (!this.queue) {\n setTimeout(function() {\n self.handler(self.queue);\n self.queue = '';\n }, 1);\n }\n\n this.queue += data;\n};\n\n/**\n * Ring the bell.\n * Note: We could do sweet things with webaudio here\n */\nTerminal.prototype.bell = function() {\n if (!this.visualBell) return;\n var self = this;\n this.element.style.borderColor = 'white';\n setTimeout(function() {\n self.element.style.borderColor = '';\n }, 10);\n if (this.popOnBell) this.focus();\n};\n\n/**\n * Log the current state to the console.\n */\nTerminal.prototype.log = function() {\n if (!this.debug) return;\n if (!this.context.console || !this.context.console.log) return;\n var args = Array.prototype.slice.call(arguments);\n this.context.console.log.apply(this.context.console, args);\n};\n\n/**\n * Log the current state as error to the console.\n */\nTerminal.prototype.error = function() {\n if (!this.debug) return;\n if (!this.context.console || !this.context.console.error) return;\n var args = Array.prototype.slice.call(arguments);\n this.context.console.error.apply(this.context.console, args);\n};\n\n/**\n * Resizes the terminal.\n *\n * @param {number} x The number of columns to resize to.\n * @param {number} y The number of rows to resize to.\n */\nTerminal.prototype.resize = function(x, y) {\n if (isNaN(x) || isNaN(y)) {\n return;\n }\n\n if (y > this.getOption('scrollback')) {\n this.setOption('scrollback', y)\n }\n\n var line\n , el\n , i\n , j\n , ch\n , addToY;\n\n if (x === this.cols && y === this.rows) {\n // Check if we still need to measure the char size (fixes #785).\n if (!this.charMeasure.width || !this.charMeasure.height) {\n this.charMeasure.measure();\n }\n return;\n }\n\n if (x < 1) x = 1;\n if (y < 1) y = 1;\n\n // resize cols\n j = this.cols;\n if (j < x) {\n ch = [this.defAttr, ' ', 1]; // does xterm use the default attr?\n i = this.buffer.lines.length;\n while (i--) {\n if (this.buffer.lines.get(i) === undefined) {\n this.buffer.lines.set(i, this.blankLine());\n }\n while (this.buffer.lines.get(i).length < x) {\n this.buffer.lines.get(i).push(ch);\n }\n }\n }\n\n this.cols = x;\n this.setupStops(this.cols);\n\n // resize rows\n j = this.rows;\n addToY = 0;\n if (j < y) {\n el = this.element;\n while (j++ < y) {\n // y is rows, not this.buffer.y\n if (this.buffer.lines.length < y + this.buffer.ybase) {\n if (this.buffer.ybase > 0 && this.buffer.lines.length <= this.buffer.ybase + this.buffer.y + addToY + 1) {\n // There is room above the buffer and there are no empty elements below the line,\n // scroll up\n this.buffer.ybase--;\n addToY++;\n if (this.buffer.ydisp > 0) {\n // Viewport is at the top of the buffer, must increase downwards\n this.buffer.ydisp--;\n }\n } else {\n // Add a blank line if there is no buffer left at the top to scroll to, or if there\n // are blank lines after the cursor\n this.buffer.lines.push(this.blankLine());\n }\n }\n if (this.children.length < y) {\n this.insertRow();\n }\n }\n } else { // (j > y)\n while (j-- > y) {\n if (this.buffer.lines.length > y + this.buffer.ybase) {\n if (this.buffer.lines.length > this.buffer.ybase + this.buffer.y + 1) {\n // The line is a blank line below the cursor, remove it\n this.buffer.lines.pop();\n } else {\n // The line is the cursor, scroll down\n this.buffer.ybase++;\n this.buffer.ydisp++;\n }\n }\n if (this.children.length > y) {\n el = this.children.shift();\n if (!el) continue;\n el.parentNode.removeChild(el);\n }\n }\n }\n this.rows = y;\n\n // Make sure that the cursor stays on screen\n if (this.buffer.y >= y) {\n this.buffer.y = y - 1;\n }\n if (addToY) {\n this.buffer.y += addToY;\n }\n\n if (this.buffer.x >= x) {\n this.buffer.x = x - 1;\n }\n\n this.buffer.scrollTop = 0;\n this.buffer.scrollBottom = y - 1;\n\n this.charMeasure.measure();\n\n this.refresh(0, this.rows - 1);\n\n this.geometry = [this.cols, this.rows];\n this.emit('resize', {terminal: this, cols: x, rows: y});\n};\n\n/**\n * Updates the range of rows to refresh\n * @param {number} y The number of rows to refresh next.\n */\nTerminal.prototype.updateRange = function(y) {\n if (y < this.refreshStart) this.refreshStart = y;\n if (y > this.refreshEnd) this.refreshEnd = y;\n // if (y > this.refreshEnd) {\n // this.refreshEnd = y;\n // if (y > this.rows - 1) {\n // this.refreshEnd = this.rows - 1;\n // }\n // }\n};\n\n/**\n * Set the range of refreshing to the maximum value\n */\nTerminal.prototype.maxRange = function() {\n this.refreshStart = 0;\n this.refreshEnd = this.rows - 1;\n};\n\n\n\n/**\n * Setup the tab stops.\n * @param {number} i\n */\nTerminal.prototype.setupStops = function(i) {\n if (i != null) {\n if (!this.buffer.tabs[i]) {\n i = this.prevStop(i);\n }\n } else {\n this.buffer.tabs = {};\n i = 0;\n }\n\n for (; i < this.cols; i += this.getOption('tabStopWidth')) {\n this.buffer.tabs[i] = true;\n }\n};\n\n\n/**\n * Move the cursor to the previous tab stop from the given position (default is current).\n * @param {number} x The position to move the cursor to the previous tab stop.\n */\nTerminal.prototype.prevStop = function(x) {\n if (x == null) x = this.buffer.x;\n while (!this.buffer.tabs[--x] && x > 0);\n return x >= this.cols\n ? this.cols - 1\n : x < 0 ? 0 : x;\n};\n\n\n/**\n * Move the cursor one tab stop forward from the given position (default is current).\n * @param {number} x The position to move the cursor one tab stop forward.\n */\nTerminal.prototype.nextStop = function(x) {\n if (x == null) x = this.buffer.x;\n while (!this.buffer.tabs[++x] && x < this.cols);\n return x >= this.cols\n ? this.cols - 1\n : x < 0 ? 0 : x;\n};\n\n\n/**\n * Erase in the identified line everything from \"x\" to the end of the line (right).\n * @param {number} x The column from which to start erasing to the end of the line.\n * @param {number} y The line in which to operate.\n */\nTerminal.prototype.eraseRight = function(x, y) {\n var line = this.buffer.lines.get(this.buffer.ybase + y);\n if (!line) {\n return;\n }\n var ch = [this.eraseAttr(), ' ', 1]; // xterm\n for (; x < this.cols; x++) {\n line[x] = ch;\n }\n this.updateRange(y);\n};\n\n\n\n/**\n * Erase in the identified line everything from \"x\" to the start of the line (left).\n * @param {number} x The column from which to start erasing to the start of the line.\n * @param {number} y The line in which to operate.\n */\nTerminal.prototype.eraseLeft = function(x, y) {\n var line = this.buffer.lines.get(this.buffer.ybase + y);\n if (!line) {\n return;\n }\n var ch = [this.eraseAttr(), ' ', 1]; // xterm\n x++;\n while (x--) {\n line[x] = ch;\n }\n this.updateRange(y);\n};\n\n/**\n * Clears the entire buffer, making the prompt line the new first line.\n */\nTerminal.prototype.clear = function() {\n if (this.buffer.ybase === 0 && this.buffer.y === 0) {\n // Don't clear if it's already clear\n return;\n }\n this.buffer.lines.set(0, this.buffer.lines.get(this.buffer.ybase + this.buffer.y));\n this.buffer.lines.length = 1;\n this.buffer.ydisp = 0;\n this.buffer.ybase = 0;\n this.buffer.y = 0;\n for (var i = 1; i < this.rows; i++) {\n this.buffer.lines.push(this.blankLine());\n }\n this.refresh(0, this.rows - 1);\n this.emit('scroll', this.buffer.ydisp);\n};\n\n/**\n * Erase all content in the given line\n * @param {number} y The line to erase all of its contents.\n */\nTerminal.prototype.eraseLine = function(y) {\n this.eraseRight(0, y);\n};\n\n\n/**\n * Return the data array of a blank line\n * @param {number} cur First bunch of data for each \"blank\" character.\n * @param {boolean} isWrapped Whether the new line is wrapped from the previous line.\n */\nTerminal.prototype.blankLine = function(cur, isWrapped) {\n var attr = cur\n ? this.eraseAttr()\n : this.defAttr;\n\n var ch = [attr, ' ', 1] // width defaults to 1 halfwidth character\n , line = []\n , i = 0;\n\n // TODO: It is not ideal that this is a property on an array, a buffer line\n // class should be added that will hold this data and other useful functions.\n if (isWrapped) {\n line.isWrapped = isWrapped;\n }\n\n for (; i < this.cols; i++) {\n line[i] = ch;\n }\n\n return line;\n};\n\n\n/**\n * If cur return the back color xterm feature attribute. Else return defAttr.\n * @param {object} cur\n */\nTerminal.prototype.ch = function(cur) {\n return cur\n ? [this.eraseAttr(), ' ', 1]\n : [this.defAttr, ' ', 1];\n};\n\n\n/**\n * Evaluate if the current terminal is the given argument.\n * @param {object} term The terminal to evaluate\n */\nTerminal.prototype.is = function(term) {\n var name = this.termName;\n return (name + '').indexOf(term) === 0;\n};\n\n\n/**\n * Emit the 'data' event and populate the given data.\n * @param {string} data The data to populate in the event.\n */\nTerminal.prototype.handler = function(data) {\n // Prevents all events to pty process if stdin is disabled\n if (this.options.disableStdin) {\n return;\n }\n\n // Clear the selection if the selection manager is available and has an active selection\n if (this.selectionManager && this.selectionManager.hasSelection) {\n this.selectionManager.clearSelection();\n }\n\n // Input is being sent to the terminal, the terminal should focus the prompt.\n if (this.buffer.ybase !== this.buffer.ydisp) {\n this.scrollToBottom();\n }\n this.emit('data', data);\n};\n\n\n/**\n * Emit the 'title' event and populate the given title.\n * @param {string} title The title to populate in the event.\n */\nTerminal.prototype.handleTitle = function(title) {\n /**\n * This event is emitted when the title of the terminal is changed\n * from inside the terminal. The parameter is the new title.\n *\n * @event title\n */\n this.emit('title', title);\n};\n\n\n/**\n * ESC\n */\n\n/**\n * ESC D Index (IND is 0x84).\n */\nTerminal.prototype.index = function() {\n this.buffer.y++;\n if (this.buffer.y > this.buffer.scrollBottom) {\n this.buffer.y--;\n this.scroll();\n }\n // If the end of the line is hit, prevent this action from wrapping around to the next line.\n if (this.buffer.x >= this.cols) {\n this.buffer.x--;\n }\n};\n\n\n/**\n * ESC M Reverse Index (RI is 0x8d).\n *\n * Move the cursor up one row, inserting a new blank line if necessary.\n */\nTerminal.prototype.reverseIndex = function() {\n var j;\n if (this.buffer.y === this.buffer.scrollTop) {\n // possibly move the code below to term.reverseScroll();\n // test: echo -ne '\\e[1;1H\\e[44m\\eM\\e[0m'\n // blankLine(true) is xterm/linux behavior\n this.buffer.lines.shiftElements(this.buffer.y + this.buffer.ybase, this.rows - 1, 1);\n this.buffer.lines.set(this.buffer.y + this.buffer.ybase, this.blankLine(true));\n this.updateRange(this.buffer.scrollTop);\n this.updateRange(this.buffer.scrollBottom);\n } else {\n this.buffer.y--;\n }\n};\n\n\n/**\n * ESC c Full Reset (RIS).\n */\nTerminal.prototype.reset = function() {\n this.options.rows = this.rows;\n this.options.cols = this.cols;\n var customKeyEventHandler = this.customKeyEventHandler;\n var cursorBlinkInterval = this.cursorBlinkInterval;\n var inputHandler = this.inputHandler;\n var buffers = this.buffers;\n Terminal.call(this, this.options);\n this.customKeyEventHandler = customKeyEventHandler;\n this.cursorBlinkInterval = cursorBlinkInterval;\n this.inputHandler = inputHandler;\n this.buffers = buffers;\n this.refresh(0, this.rows - 1);\n this.viewport.syncScrollArea();\n};\n\n\n/**\n * ESC H Tab Set (HTS is 0x88).\n */\nTerminal.prototype.tabSet = function() {\n this.buffer.tabs[this.buffer.x] = true;\n};\n\n/**\n * Helpers\n */\n\nfunction on(el, type, handler, capture) {\n if (!Array.isArray(el)) {\n el = [el];\n }\n el.forEach(function (element) {\n element.addEventListener(type, handler, capture || false);\n });\n}\n\nfunction off(el, type, handler, capture) {\n el.removeEventListener(type, handler, capture || false);\n}\n\nfunction cancel(ev, force) {\n if (!this.cancelEvents && !force) {\n return;\n }\n ev.preventDefault();\n ev.stopPropagation();\n return false;\n}\n\nfunction inherits(child, parent) {\n function f() {\n this.constructor = child;\n }\n f.prototype = parent.prototype;\n child.prototype = new f;\n}\n\nfunction indexOf(obj, el) {\n var i = obj.length;\n while (i--) {\n if (obj[i] === el) return i;\n }\n return -1;\n}\n\nfunction isThirdLevelShift(term, ev) {\n var thirdLevelKey =\n (term.browser.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) ||\n (term.browser.isMSWindows && ev.altKey && ev.ctrlKey && !ev.metaKey);\n\n if (ev.type == 'keypress') {\n return thirdLevelKey;\n }\n\n // Don't invoke for arrows, pageDown, home, backspace, etc. (on non-keypress events)\n return thirdLevelKey && (!ev.keyCode || ev.keyCode > 47);\n}\n\n// Expose to InputHandler (temporary)\nTerminal.prototype.matchColor = matchColor;\n\nfunction matchColor(r1, g1, b1) {\n var hash = (r1 << 16) | (g1 << 8) | b1;\n\n if (matchColor._cache[hash] != null) {\n return matchColor._cache[hash];\n }\n\n var ldiff = Infinity\n , li = -1\n , i = 0\n , c\n , r2\n , g2\n , b2\n , diff;\n\n for (; i < Terminal.vcolors.length; i++) {\n c = Terminal.vcolors[i];\n r2 = c[0];\n g2 = c[1];\n b2 = c[2];\n\n diff = matchColor.distance(r1, g1, b1, r2, g2, b2);\n\n if (diff === 0) {\n li = i;\n break;\n }\n\n if (diff < ldiff) {\n ldiff = diff;\n li = i;\n }\n }\n\n return matchColor._cache[hash] = li;\n}\n\nmatchColor._cache = {};\n\n// http://stackoverflow.com/questions/1633828\nmatchColor.distance = function(r1, g1, b1, r2, g2, b2) {\n return Math.pow(30 * (r1 - r2), 2)\n + Math.pow(59 * (g1 - g2), 2)\n + Math.pow(11 * (b1 - b2), 2);\n};\n\nfunction each(obj, iter, con) {\n if (obj.forEach) return obj.forEach(iter, con);\n for (var i = 0; i < obj.length; i++) {\n iter.call(con, obj[i], i, obj);\n }\n}\n\nfunction wasMondifierKeyOnlyEvent(ev) {\n return ev.keyCode === 16 || // Shift\n ev.keyCode === 17 || // Ctrl\n ev.keyCode === 18; // Alt\n}\n\nfunction keys(obj) {\n if (Object.keys) return Object.keys(obj);\n var key, keys = [];\n for (key in obj) {\n if (Object.prototype.hasOwnProperty.call(obj, key)) {\n keys.push(key);\n }\n }\n return keys;\n}\n\n/**\n * Expose\n */\n\nTerminal.translateBufferLineToString = translateBufferLineToString;\nTerminal.EventEmitter = EventEmitter;\nTerminal.inherits = inherits;\n\n/**\n * Adds an event listener to the terminal.\n *\n * @param {string} event The name of the event. TODO: Document all event types\n * @param {function} callback The function to call when the event is triggered.\n */\nTerminal.on = on;\nTerminal.off = off;\nTerminal.cancel = cancel;\n\nmodule.exports = Terminal;\n","/**\n * @license MIT\n */\n\nimport { CharMeasure } from './CharMeasure';\n\nexport function getCoordsRelativeToElement(event: MouseEvent, element: HTMLElement): [number, number] {\n // Ignore browsers that don't support MouseEvent.pageX\n if (event.pageX == null) {\n return null;\n }\n\n let x = event.pageX;\n let y = event.pageY;\n\n // Converts the coordinates from being relative to the document to being\n // relative to the terminal.\n while (element && element !== self.document.documentElement) {\n x -= element.offsetLeft;\n y -= element.offsetTop;\n element = 'offsetParent' in element ? element.offsetParent : element.parentElement;\n }\n return [x, y];\n}\n\n/**\n * Gets coordinates within the terminal for a particular mouse event. The result\n * is returned as an array in the form [x, y] instead of an object as it's a\n * little faster and this function is used in some low level code.\n * @param event The mouse event.\n * @param rowContainer The terminal's row container.\n * @param charMeasure The char measure object used to determine character sizes.\n * @param colCount The number of columns in the terminal.\n * @param rowCount The number of rows n the terminal.\n * @param isSelection Whether the request is for the selection or not. This will\n * apply an offset to the x value such that the left half of the cell will\n * select that cell and the right half will select the next cell.\n */\nexport function getCoords(event: MouseEvent, rowContainer: HTMLElement, charMeasure: CharMeasure, colCount: number, rowCount: number, isSelection?: boolean): [number, number] {\n // Coordinates cannot be measured if charMeasure has not been initialized\n if (!charMeasure.width || !charMeasure.height) {\n return null;\n }\n\n const coords = getCoordsRelativeToElement(event, rowContainer);\n if (!coords) {\n return null;\n }\n\n // Convert to cols/rows.\n coords[0] = Math.ceil((coords[0] + (isSelection ? charMeasure.width / 2 : 0)) / charMeasure.width);\n coords[1] = Math.ceil(coords[1] / charMeasure.height);\n\n // Ensure coordinates are within the terminal viewport.\n coords[0] = Math.min(Math.max(coords[0], 1), colCount + 1);\n coords[1] = Math.min(Math.max(coords[1], 1), rowCount + 1);\n\n return coords;\n}\n\n/**\n * Gets coordinates within the terminal for a particular mouse event, wrapping\n * them to the bounds of the terminal and adding 32 to both the x and y values\n * as expected by xterm.\n * @param event The mouse event.\n * @param rowContainer The terminal's row container.\n * @param charMeasure The char measure object used to determine character sizes.\n * @param colCount The number of columns in the terminal.\n * @param rowCount The number of rows in the terminal.\n */\nexport function getRawByteCoords(event: MouseEvent, rowContainer: HTMLElement, charMeasure: CharMeasure, colCount: number, rowCount: number): { x: number, y: number } {\n const coords = getCoords(event, rowContainer, charMeasure, colCount, rowCount);\n let x = coords[0];\n let y = coords[1];\n\n // xterm sends raw bytes and starts at 32 (SP) for each.\n x += 32;\n y += 32;\n\n return { x, y };\n}\n","/**\n * Generic utilities module with methods that can be helpful at different parts of the code base.\n * @module xterm/utils/Generic\n * @license MIT\n */\n\n/**\n * Return if the given array contains the given element\n * @param {Array} array The array to search for the given element.\n * @param {Object} el The element to look for into the array\n */\nexport function contains(arr: any[], el: any) {\n return arr.indexOf(el) >= 0;\n};\n","/**\n * @module xterm/utils/DomElementObjectPool\n * @license MIT\n */\n\n/**\n * An object pool that manages acquisition and releasing of DOM elements for\n * when reuse is desirable.\n */\nexport class DomElementObjectPool {\n private static readonly OBJECT_ID_ATTRIBUTE = 'data-obj-id';\n\n private static _objectCount = 0;\n\n private _type: string;\n private _pool: HTMLElement[];\n private _inUse: {[key: string]: HTMLElement};\n\n /**\n * @param type The DOM element type (div, span, etc.).\n */\n constructor(private type: string) {\n this._type = type;\n this._pool = [];\n this._inUse = {};\n }\n\n /**\n * Acquire an element from the pool, creating it if the pool is empty.\n */\n public acquire(): HTMLElement {\n let element: HTMLElement;\n if (this._pool.length === 0) {\n element = this._createNew();\n } else {\n element = this._pool.pop();\n }\n this._inUse[element.getAttribute(DomElementObjectPool.OBJECT_ID_ATTRIBUTE)] = element;\n return element;\n }\n\n /**\n * Release an element back into the pool. It's up to the caller of this\n * function to ensure that all external references to the element have been\n * removed.\n * @param element The element being released.\n */\n public release(element: HTMLElement): void {\n if (!this._inUse[element.getAttribute(DomElementObjectPool.OBJECT_ID_ATTRIBUTE)]) {\n throw new Error('Could not release an element not yet acquired');\n }\n delete this._inUse[element.getAttribute(DomElementObjectPool.OBJECT_ID_ATTRIBUTE)];\n this._cleanElement(element);\n this._pool.push(element);\n }\n\n /**\n * Creates a new element for the pool.\n */\n private _createNew(): HTMLElement {\n const element = document.createElement(this._type);\n const id = DomElementObjectPool._objectCount++;\n element.setAttribute(DomElementObjectPool.OBJECT_ID_ATTRIBUTE, id.toString(10));\n return element;\n }\n\n /**\n * Resets an element back to a \"clean state\".\n * @param element The element to be cleaned.\n */\n private _cleanElement(element: HTMLElement): void {\n element.className = '';\n element.innerHTML = '';\n }\n}\n","/**\n * Represents a circular list; a list with a maximum size that wraps around when push is called,\n * overriding values at the start of the list.\n * @module xterm/utils/CircularList\n * @license MIT\n */\nimport { EventEmitter } from '../EventEmitter';\n\nexport class CircularList extends EventEmitter {\n private _array: T[];\n private _startIndex: number;\n private _length: number;\n\n constructor(maxLength: number) {\n super();\n this._array = new Array(maxLength);\n this._startIndex = 0;\n this._length = 0;\n }\n\n public get maxLength(): number {\n return this._array.length;\n }\n\n public set maxLength(newMaxLength: number) {\n // Reconstruct array, starting at index 0. Only transfer values from the\n // indexes 0 to length.\n let newArray = new Array(newMaxLength);\n for (let i = 0; i < Math.min(newMaxLength, this.length); i++) {\n newArray[i] = this._array[this._getCyclicIndex(i)];\n }\n this._array = newArray;\n this._startIndex = 0;\n }\n\n public get length(): number {\n return this._length;\n }\n\n public set length(newLength: number) {\n if (newLength > this._length) {\n for (let i = this._length; i < newLength; i++) {\n this._array[i] = undefined;\n }\n }\n this._length = newLength;\n }\n\n public get forEach(): (callbackfn: (value: T, index: number) => void) => void {\n return (callbackfn: (value: T, index: number) => void) => {\n let i = 0;\n let length = this.length;\n for (let i = 0; i < length; i++) {\n callbackfn(this.get(i), i);\n }\n };\n }\n\n /**\n * Gets the value at an index.\n *\n * Note that for performance reasons there is no bounds checking here, the index reference is\n * circular so this should always return a value and never throw.\n * @param index The index of the value to get.\n * @return The value corresponding to the index.\n */\n public get(index: number): T {\n return this._array[this._getCyclicIndex(index)];\n }\n\n /**\n * Sets the value at an index.\n *\n * Note that for performance reasons there is no bounds checking here, the index reference is\n * circular so this should always return a value and never throw.\n * @param index The index to set.\n * @param value The value to set.\n */\n public set(index: number, value: T): void {\n this._array[this._getCyclicIndex(index)] = value;\n }\n\n /**\n * Pushes a new value onto the list, wrapping around to the start of the array, overriding index 0\n * if the maximum length is reached.\n * @param value The value to push onto the list.\n */\n public push(value: T): void {\n this._array[this._getCyclicIndex(this._length)] = value;\n if (this._length === this.maxLength) {\n this._startIndex++;\n if (this._startIndex === this.maxLength) {\n this._startIndex = 0;\n }\n this.emit('trim', 1);\n } else {\n this._length++;\n }\n }\n\n /**\n * Removes and returns the last value on the list.\n * @return The popped value.\n */\n public pop(): T {\n return this._array[this._getCyclicIndex(this._length-- - 1)];\n }\n\n /**\n * Deletes and/or inserts items at a particular index (in that order). Unlike\n * Array.prototype.splice, this operation does not return the deleted items as a new array in\n * order to save creating a new array. Note that this operation may shift all values in the list\n * in the worst case.\n * @param start The index to delete and/or insert.\n * @param deleteCount The number of elements to delete.\n * @param items The items to insert.\n */\n public splice(start: number, deleteCount: number, ...items: T[]): void {\n // Delete items\n if (deleteCount) {\n for (let i = start; i < this._length - deleteCount; i++) {\n this._array[this._getCyclicIndex(i)] = this._array[this._getCyclicIndex(i + deleteCount)];\n }\n this._length -= deleteCount;\n }\n\n if (items && items.length) {\n // Add items\n for (let i = this._length - 1; i >= start; i--) {\n this._array[this._getCyclicIndex(i + items.length)] = this._array[this._getCyclicIndex(i)];\n }\n for (let i = 0; i < items.length; i++) {\n this._array[this._getCyclicIndex(start + i)] = items[i];\n }\n\n // Adjust length as needed\n if (this._length + items.length > this.maxLength) {\n const countToTrim = (this._length + items.length) - this.maxLength;\n this._startIndex += countToTrim;\n this._length = this.maxLength;\n this.emit('trim', countToTrim);\n } else {\n this._length += items.length;\n }\n }\n }\n\n /**\n * Trims a number of items from the start of the list.\n * @param count The number of items to remove.\n */\n public trimStart(count: number): void {\n if (count > this._length) {\n count = this._length;\n }\n this._startIndex += count;\n this._length -= count;\n this.emit('trim', count);\n }\n\n public shiftElements(start: number, count: number, offset: number): void {\n if (count <= 0) {\n return;\n }\n if (start < 0 || start >= this._length) {\n throw new Error('start argument out of range');\n }\n if (start + offset < 0) {\n throw new Error('Cannot shift elements in list beyond index 0');\n }\n\n if (offset > 0) {\n for (let i = count - 1; i >= 0; i--) {\n this.set(start + i + offset, this.get(start + i));\n }\n const expandListBy = (start + count + offset) - this._length;\n if (expandListBy > 0) {\n this._length += expandListBy;\n while (this._length > this.maxLength) {\n this._length--;\n this._startIndex++;\n this.emit('trim', 1);\n }\n }\n } else {\n for (let i = 0; i < count; i++) {\n this.set(start + i + offset, this.get(start + i));\n }\n }\n }\n\n /**\n * Gets the cyclic index for the specified regular index. The cyclic index can then be used on the\n * backing array to get the element associated with the regular index.\n * @param index The regular index.\n * @returns The cyclic index.\n */\n private _getCyclicIndex(index: number): number {\n return (this._startIndex + index) % this.maxLength;\n }\n}\n","/**\n * @module xterm/utils/CharMeasure\n * @license MIT\n */\n\nimport { EventEmitter } from '../EventEmitter.js';\n\n/**\n * Utility class that measures the size of a character.\n */\nexport class CharMeasure extends EventEmitter {\n private _document: Document;\n private _parentElement: HTMLElement;\n private _measureElement: HTMLElement;\n private _width: number;\n private _height: number;\n\n constructor(document: Document, parentElement: HTMLElement) {\n super();\n this._document = document;\n this._parentElement = parentElement;\n }\n\n public get width(): number {\n return this._width;\n }\n\n public get height(): number {\n return this._height;\n }\n\n public measure(): void {\n if (!this._measureElement) {\n this._measureElement = this._document.createElement('span');\n this._measureElement.style.position = 'absolute';\n this._measureElement.style.top = '0';\n this._measureElement.style.left = '-9999em';\n this._measureElement.textContent = 'W';\n this._measureElement.setAttribute('aria-hidden', 'true');\n this._parentElement.appendChild(this._measureElement);\n // Perform _doMeasure async if the element was just attached as sometimes\n // getBoundingClientRect does not return accurate values without this.\n setTimeout(() => this._doMeasure(), 0);\n } else {\n this._doMeasure();\n }\n }\n\n private _doMeasure(): void {\n const geometry = this._measureElement.getBoundingClientRect();\n // The element is likely currently display:none, we should retain the\n // previous value.\n if (geometry.width === 0 || geometry.height === 0) {\n return;\n }\n if (this._width !== geometry.width || this._height !== geometry.height) {\n this._width = geometry.width;\n this._height = geometry.height;\n this.emit('charsizechanged');\n }\n }\n}\n","/**\n * @license MIT\n */\n\n// TODO: This module should be merged into a buffer or buffer line class\n\nconst LINE_DATA_CHAR_INDEX = 1;\nconst LINE_DATA_WIDTH_INDEX = 2;\n\n/**\n * Translates a buffer line to a string, with optional start and end columns.\n * Wide characters will count as two columns in the resulting string. This\n * function is useful for getting the actual text underneath the raw selection\n * position.\n * @param line The line being translated.\n * @param trimRight Whether to trim whitespace to the right.\n * @param startCol The column to start at.\n * @param endCol The column to end at.\n */\nexport function translateBufferLineToString(line: any, trimRight: boolean, startCol: number = 0, endCol: number = null): string {\n // Get full line\n let lineString = '';\n let widthAdjustedStartCol = startCol;\n let widthAdjustedEndCol = endCol;\n for (let i = 0; i < line.length; i++) {\n const char = line[i];\n lineString += char[LINE_DATA_CHAR_INDEX];\n // Adjust start and end cols for wide characters if they affect their\n // column indexes\n if (char[LINE_DATA_WIDTH_INDEX] === 0) {\n if (startCol >= i) {\n widthAdjustedStartCol--;\n }\n if (endCol >= i) {\n widthAdjustedEndCol--;\n }\n }\n }\n\n // Calculate the final end col by trimming whitespace on the right of the\n // line if needed.\n let finalEndCol = widthAdjustedEndCol || line.length;\n if (trimRight) {\n const rightWhitespaceIndex = lineString.search(/\\s+$/);\n if (rightWhitespaceIndex !== -1) {\n finalEndCol = Math.min(finalEndCol, rightWhitespaceIndex);\n }\n // Return the empty string if only trimmed whitespace is selected\n if (finalEndCol <= widthAdjustedStartCol) {\n return '';\n }\n }\n\n return lineString.substring(widthAdjustedStartCol, finalEndCol);\n}\n","/**\n * Attributes and methods to help with identifying the current browser and platform.\n * @module xterm/utils/Browser\n * @license MIT\n */\n\nimport { contains } from './Generic';\n\nconst isNode = (typeof navigator === 'undefined') ? true : false;\nconst userAgent = (isNode) ? 'node' : navigator.userAgent;\nconst platform = (isNode) ? 'node' : navigator.platform;\n\nexport const isFirefox = !!~userAgent.indexOf('Firefox');\nexport const isMSIE = !!~userAgent.indexOf('MSIE') || !!~userAgent.indexOf('Trident');\n\n// Find the users platform. We use this to interpret the meta key\n// and ISO third level shifts.\n// http://stackoverflow.com/q/19877924/577598\nexport const isMac = contains(['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'], platform);\nexport const isIpad = platform === 'iPad';\nexport const isIphone = platform === 'iPhone';\nexport const isMSWindows = contains(['Windows', 'Win16', 'Win32', 'WinCE'], platform);\nexport const isLinux = platform.indexOf('Linux') >= 0;\n","/**\n * Clipboard handler module: exports methods for handling all clipboard-related events in the\n * terminal.\n * @module xterm/handlers/Clipboard\n * @license MIT\n */\n\nimport { ITerminal, ISelectionManager } from '../Interfaces';\n\ninterface IWindow extends Window {\n clipboardData?: {\n getData(format: string): string;\n setData(format: string, data: string);\n };\n}\n\ndeclare var window: IWindow;\n\n/**\n * Prepares text to be pasted into the terminal by normalizing the line endings\n * @param text The pasted text that needs processing before inserting into the terminal\n */\nexport function prepareTextForTerminal(text: string, isMSWindows: boolean): string {\n if (isMSWindows) {\n return text.replace(/\\r?\\n/g, '\\r');\n }\n return text;\n}\n\n/**\n * Binds copy functionality to the given terminal.\n * @param {ClipboardEvent} ev The original copy event to be handled\n */\nexport function copyHandler(ev: ClipboardEvent, term: ITerminal, selectionManager: ISelectionManager) {\n if (term.browser.isMSIE) {\n window.clipboardData.setData('Text', selectionManager.selectionText);\n } else {\n ev.clipboardData.setData('text/plain', selectionManager.selectionText);\n }\n\n // Prevent or the original text will be copied.\n ev.preventDefault();\n}\n\n/**\n * Redirect the clipboard's data to the terminal's input handler.\n * @param {ClipboardEvent} ev The original paste event to be handled\n * @param {Terminal} term The terminal on which to apply the handled paste event\n */\nexport function pasteHandler(ev: ClipboardEvent, term: ITerminal) {\n ev.stopPropagation();\n\n let text: string;\n\n let dispatchPaste = function(text) {\n text = prepareTextForTerminal(text, term.browser.isMSWindows);\n term.handler(text);\n term.textarea.value = '';\n term.emit('paste', text);\n\n return term.cancel(ev);\n };\n\n if (term.browser.isMSIE) {\n if (window.clipboardData) {\n text = window.clipboardData.getData('Text');\n dispatchPaste(text);\n }\n } else {\n if (ev.clipboardData) {\n text = ev.clipboardData.getData('text/plain');\n dispatchPaste(text);\n }\n }\n}\n\n/**\n * Moves the textarea under the mouse cursor and focuses it.\n * @param ev The original right click event to be handled.\n * @param textarea The terminal's textarea.\n */\nexport function moveTextAreaUnderMouseCursor(ev: MouseEvent, textarea: HTMLTextAreaElement) {\n // Bring textarea at the cursor position\n textarea.style.position = 'fixed';\n textarea.style.width = '20px';\n textarea.style.height = '20px';\n textarea.style.left = (ev.clientX - 10) + 'px';\n textarea.style.top = (ev.clientY - 10) + 'px';\n textarea.style.zIndex = '1000';\n\n textarea.focus();\n\n // Reset the terminal textarea's styling\n setTimeout(function () {\n textarea.style.position = null;\n textarea.style.width = null;\n textarea.style.height = null;\n textarea.style.left = null;\n textarea.style.top = null;\n textarea.style.zIndex = null;\n }, 4);\n}\n\n/**\n * Bind to right-click event and allow right-click copy and paste.\n * @param ev The original right click event to be handled.\n * @param textarea The terminal's textarea.\n * @param selectionManager The terminal's selection manager.\n */\nexport function rightClickHandler(ev: MouseEvent, textarea: HTMLTextAreaElement, selectionManager: ISelectionManager) {\n moveTextAreaUnderMouseCursor(ev, textarea);\n\n // Get textarea ready to copy from the context menu\n textarea.value = selectionManager.selectionText;\n textarea.select();\n}\n","/**\n * @license MIT\n */\n\nimport { ITerminal } from './Interfaces';\nimport { CharMeasure } from './utils/CharMeasure';\n\n/**\n * Represents the viewport of a terminal, the visible area within the larger buffer of output.\n * Logic for the virtual scroll bar is included in this object.\n */\nexport class Viewport {\n private currentRowHeight: number;\n private lastRecordedBufferLength: number;\n private lastRecordedViewportHeight: number;\n private lastTouchY: number;\n\n /**\n * Creates a new Viewport.\n * @param terminal The terminal this viewport belongs to.\n * @param viewportElement The DOM element acting as the viewport.\n * @param scrollArea The DOM element acting as the scroll area.\n * @param charMeasure A DOM element used to measure the character size of. the terminal.\n */\n constructor(\n private terminal: ITerminal,\n private viewportElement: HTMLElement,\n private scrollArea: HTMLElement,\n private charMeasure: CharMeasure\n ) {\n this.currentRowHeight = 0;\n this.lastRecordedBufferLength = 0;\n this.lastRecordedViewportHeight = 0;\n\n this.terminal.on('scroll', this.syncScrollArea.bind(this));\n this.terminal.on('resize', this.syncScrollArea.bind(this));\n this.viewportElement.addEventListener('scroll', this.onScroll.bind(this));\n\n // Perform this async to ensure the CharMeasure is ready.\n setTimeout(() => this.syncScrollArea(), 0);\n }\n\n /**\n * Refreshes row height, setting line-height, viewport height and scroll area height if\n * necessary.\n */\n private refresh(): void {\n if (this.charMeasure.height > 0) {\n const rowHeightChanged = this.charMeasure.height !== this.currentRowHeight;\n if (rowHeightChanged) {\n this.currentRowHeight = this.charMeasure.height;\n this.viewportElement.style.lineHeight = this.charMeasure.height + 'px';\n this.terminal.rowContainer.style.lineHeight = this.charMeasure.height + 'px';\n }\n const viewportHeightChanged = this.lastRecordedViewportHeight !== this.terminal.rows;\n if (rowHeightChanged || viewportHeightChanged) {\n this.lastRecordedViewportHeight = this.terminal.rows;\n this.viewportElement.style.height = this.charMeasure.height * this.terminal.rows + 'px';\n this.terminal.selectionContainer.style.height = this.viewportElement.style.height;\n }\n this.scrollArea.style.height = (this.charMeasure.height * this.lastRecordedBufferLength) + 'px';\n }\n }\n\n /**\n * Updates dimensions and synchronizes the scroll area if necessary.\n */\n public syncScrollArea(): void {\n if (this.lastRecordedBufferLength !== this.terminal.buffer.lines.length) {\n // If buffer height changed\n this.lastRecordedBufferLength = this.terminal.buffer.lines.length;\n this.refresh();\n } else if (this.lastRecordedViewportHeight !== this.terminal.rows) {\n // If viewport height changed\n this.refresh();\n } else {\n // If size has changed, refresh viewport\n if (this.charMeasure.height !== this.currentRowHeight) {\n this.refresh();\n }\n }\n\n // Sync scrollTop\n const scrollTop = this.terminal.buffer.ydisp * this.currentRowHeight;\n if (this.viewportElement.scrollTop !== scrollTop) {\n this.viewportElement.scrollTop = scrollTop;\n }\n }\n\n /**\n * Handles scroll events on the viewport, calculating the new viewport and requesting the\n * terminal to scroll to it.\n * @param ev The scroll event.\n */\n private onScroll(ev: Event) {\n const newRow = Math.round(this.viewportElement.scrollTop / this.currentRowHeight);\n const diff = newRow - this.terminal.buffer.ydisp;\n this.terminal.scrollDisp(diff, true);\n }\n\n /**\n * Handles mouse wheel events by adjusting the viewport's scrollTop and delegating the actual\n * scrolling to `onScroll`, this event needs to be attached manually by the consumer of\n * `Viewport`.\n * @param ev The mouse wheel event.\n */\n public onWheel(ev: WheelEvent) {\n if (ev.deltaY === 0) {\n // Do nothing if it's not a vertical scroll event\n return;\n }\n // Fallback to WheelEvent.DOM_DELTA_PIXEL\n let multiplier = 1;\n if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) {\n multiplier = this.currentRowHeight;\n } else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) {\n multiplier = this.currentRowHeight * this.terminal.rows;\n }\n this.viewportElement.scrollTop += ev.deltaY * multiplier;\n // Prevent the page from scrolling when the terminal scrolls\n ev.preventDefault();\n };\n\n /**\n * Handles the touchstart event, recording the touch occurred.\n * @param ev The touch event.\n */\n public onTouchStart(ev: TouchEvent) {\n this.lastTouchY = ev.touches[0].pageY;\n };\n\n /**\n * Handles the touchmove event, scrolling the viewport if the position shifted.\n * @param ev The touch event.\n */\n public onTouchMove(ev: TouchEvent) {\n let deltaY = this.lastTouchY - ev.touches[0].pageY;\n this.lastTouchY = ev.touches[0].pageY;\n if (deltaY === 0) {\n return;\n }\n this.viewportElement.scrollTop += deltaY;\n ev.preventDefault();\n };\n}\n","/**\n * @license MIT\n */\n\nimport { ITerminal } from './Interfaces';\n\n/**\n * Represents a selection within the buffer. This model only cares about column\n * and row coordinates, not wide characters.\n */\nexport class SelectionModel {\n /**\n * Whether select all is currently active.\n */\n public isSelectAllActive: boolean;\n\n /**\n * The [x, y] position the selection starts at.\n */\n public selectionStart: [number, number];\n\n /**\n * The minimal length of the selection from the start position. When double\n * clicking on a word, the word will be selected which makes the selection\n * start at the start of the word and makes this variable the length.\n */\n public selectionStartLength: number;\n\n /**\n * The [x, y] position the selection ends at.\n */\n public selectionEnd: [number, number];\n\n constructor(\n private _terminal: ITerminal\n ) {\n this.clearSelection();\n }\n\n /**\n * Clears the current selection.\n */\n public clearSelection(): void {\n this.selectionStart = null;\n this.selectionEnd = null;\n this.isSelectAllActive = false;\n this.selectionStartLength = 0;\n }\n\n /**\n * The final selection start, taking into consideration select all.\n */\n public get finalSelectionStart(): [number, number] {\n if (this.isSelectAllActive) {\n return [0, 0];\n }\n\n if (!this.selectionEnd || !this.selectionStart) {\n return this.selectionStart;\n }\n\n return this.areSelectionValuesReversed() ? this.selectionEnd : this.selectionStart;\n }\n\n /**\n * The final selection end, taking into consideration select all, double click\n * word selection and triple click line selection.\n */\n public get finalSelectionEnd(): [number, number] {\n if (this.isSelectAllActive) {\n return [this._terminal.cols, this._terminal.buffer.ybase + this._terminal.rows - 1];\n }\n\n if (!this.selectionStart) {\n return null;\n }\n\n // Use the selection start if the end doesn't exist or they're reversed\n if (!this.selectionEnd || this.areSelectionValuesReversed()) {\n return [this.selectionStart[0] + this.selectionStartLength, this.selectionStart[1]];\n }\n\n // Ensure the the word/line is selected after a double/triple click\n if (this.selectionStartLength) {\n // Select the larger of the two when start and end are on the same line\n if (this.selectionEnd[1] === this.selectionStart[1]) {\n return [Math.max(this.selectionStart[0] + this.selectionStartLength, this.selectionEnd[0]), this.selectionEnd[1]];\n }\n }\n return this.selectionEnd;\n }\n\n /**\n * Returns whether the selection start and end are reversed.\n */\n public areSelectionValuesReversed(): boolean {\n const start = this.selectionStart;\n const end = this.selectionEnd;\n return start[1] > end[1] || (start[1] === end[1] && start[0] > end[0]);\n }\n\n /**\n * Handle the buffer being trimmed, adjust the selection position.\n * @param amount The amount the buffer is being trimmed.\n * @return Whether a refresh is necessary.\n */\n public onTrim(amount: number): boolean {\n // Adjust the selection position based on the trimmed amount.\n if (this.selectionStart) {\n this.selectionStart[1] -= amount;\n }\n if (this.selectionEnd) {\n this.selectionEnd[1] -= amount;\n }\n\n // The selection has moved off the buffer, clear it.\n if (this.selectionEnd && this.selectionEnd[1] < 0) {\n this.clearSelection();\n return true;\n }\n\n // If the selection start is trimmed, ensure the start column is 0.\n if (this.selectionStart && this.selectionStart[1] < 0) {\n this.selectionStart[1] = 0;\n }\n return false;\n }\n}\n","/**\n * @license MIT\n */\n\nimport * as Mouse from './utils/Mouse';\nimport * as Browser from './utils/Browser';\nimport { CharMeasure } from './utils/CharMeasure';\nimport { CircularList } from './utils/CircularList';\nimport { EventEmitter } from './EventEmitter';\nimport { ITerminal, ICircularList } from './Interfaces';\nimport { SelectionModel } from './SelectionModel';\nimport { translateBufferLineToString } from './utils/BufferLine';\n\n/**\n * The number of pixels the mouse needs to be above or below the viewport in\n * order to scroll at the maximum speed.\n */\nconst DRAG_SCROLL_MAX_THRESHOLD = 50;\n\n/**\n * The maximum scrolling speed\n */\nconst DRAG_SCROLL_MAX_SPEED = 15;\n\n/**\n * The number of milliseconds between drag scroll updates.\n */\nconst DRAG_SCROLL_INTERVAL = 50;\n\n/**\n * A string containing all characters that are considered word separated by the\n * double click to select work logic.\n */\nconst WORD_SEPARATORS = ' ()[]{}\\'\"';\n\n// TODO: Move these constants elsewhere, they belong in a buffer or buffer\n// data/line class.\nconst LINE_DATA_CHAR_INDEX = 1;\nconst LINE_DATA_WIDTH_INDEX = 2;\n\nconst NON_BREAKING_SPACE_CHAR = String.fromCharCode(160);\nconst ALL_NON_BREAKING_SPACE_REGEX = new RegExp(NON_BREAKING_SPACE_CHAR, 'g');\n\n/**\n * Represents a position of a word on a line.\n */\ninterface IWordPosition {\n start: number;\n length: number;\n}\n\n/**\n * A selection mode, this drives how the selection behaves on mouse move.\n */\nenum SelectionMode {\n NORMAL,\n WORD,\n LINE\n}\n\n/**\n * A class that manages the selection of the terminal. With help from\n * SelectionModel, SelectionManager handles with all logic associated with\n * dealing with the selection, including handling mouse interaction, wide\n * characters and fetching the actual text within the selection. Rendering is\n * not handled by the SelectionManager but a 'refresh' event is fired when the\n * selection is ready to be redrawn.\n */\nexport class SelectionManager extends EventEmitter {\n protected _model: SelectionModel;\n\n /**\n * The amount to scroll every drag scroll update (depends on how far the mouse\n * drag is above or below the terminal).\n */\n private _dragScrollAmount: number;\n\n /**\n * The current selection mode.\n */\n private _activeSelectionMode: SelectionMode;\n\n /**\n * A setInterval timer that is active while the mouse is down whose callback\n * scrolls the viewport when necessary.\n */\n private _dragScrollIntervalTimer: NodeJS.Timer;\n\n /**\n * The animation frame ID used for refreshing the selection.\n */\n private _refreshAnimationFrame: number;\n\n /**\n * Whether selection is enabled.\n */\n private _enabled = true;\n\n private _mouseMoveListener: EventListener;\n private _mouseUpListener: EventListener;\n\n constructor(\n private _terminal: ITerminal,\n private _buffer: ICircularList<[number, string, number][]>,\n private _rowContainer: HTMLElement,\n private _charMeasure: CharMeasure\n ) {\n super();\n this._initListeners();\n this.enable();\n\n this._model = new SelectionModel(_terminal);\n this._activeSelectionMode = SelectionMode.NORMAL;\n }\n\n /**\n * Initializes listener variables.\n */\n private _initListeners() {\n this._mouseMoveListener = event => this._onMouseMove(event);\n this._mouseUpListener = event => this._onMouseUp(event);\n\n this._rowContainer.addEventListener('mousedown', event => this._onMouseDown(event));\n\n // Only adjust the selection on trim, shiftElements is rarely used (only in\n // reverseIndex) and delete in a splice is only ever used when the same\n // number of elements was just added. Given this is could actually be\n // beneficial to leave the selection as is for these cases.\n this._buffer.on('trim', (amount: number) => this._onTrim(amount));\n }\n\n /**\n * Disables the selection manager. This is useful for when terminal mouse\n * are enabled.\n */\n public disable() {\n this.clearSelection();\n this._enabled = false;\n }\n\n /**\n * Enable the selection manager.\n */\n public enable() {\n this._enabled = true;\n }\n\n /**\n * Sets the active buffer, this should be called when the alt buffer is\n * switched in or out.\n * @param buffer The active buffer.\n */\n public setBuffer(buffer: ICircularList<[number, string, number][]>): void {\n this._buffer = buffer;\n this.clearSelection();\n }\n\n public get selectionStart(): [number, number] { return this._model.finalSelectionStart; }\n public get selectionEnd(): [number, number] { return this._model.finalSelectionEnd; }\n\n /**\n * Gets whether there is an active text selection.\n */\n public get hasSelection(): boolean {\n const start = this._model.finalSelectionStart;\n const end = this._model.finalSelectionEnd;\n if (!start || !end) {\n return false;\n }\n return start[0] !== end[0] || start[1] !== end[1];\n }\n\n /**\n * Gets the text currently selected.\n */\n public get selectionText(): string {\n const start = this._model.finalSelectionStart;\n const end = this._model.finalSelectionEnd;\n if (!start || !end) {\n return '';\n }\n\n // Get first row\n const startRowEndCol = start[1] === end[1] ? end[0] : null;\n let result: string[] = [];\n result.push(translateBufferLineToString(this._buffer.get(start[1]), true, start[0], startRowEndCol));\n\n // Get middle rows\n for (let i = start[1] + 1; i <= end[1] - 1; i++) {\n const bufferLine = this._buffer.get(i);\n const lineText = translateBufferLineToString(bufferLine, true);\n if ((bufferLine).isWrapped) {\n result[result.length - 1] += lineText;\n } else {\n result.push(lineText);\n }\n }\n\n // Get final row\n if (start[1] !== end[1]) {\n const bufferLine = this._buffer.get(end[1]);\n const lineText = translateBufferLineToString(bufferLine, true, 0, end[0]);\n if ((bufferLine).isWrapped) {\n result[result.length - 1] += lineText;\n } else {\n result.push(lineText);\n }\n }\n\n // Format string by replacing non-breaking space chars with regular spaces\n // and joining the array into a multi-line string.\n const formattedResult = result.map(line => {\n return line.replace(ALL_NON_BREAKING_SPACE_REGEX, ' ');\n }).join(Browser.isMSWindows ? '\\r\\n' : '\\n');\n\n return formattedResult;\n }\n\n /**\n * Clears the current terminal selection.\n */\n public clearSelection(): void {\n this._model.clearSelection();\n this._removeMouseDownListeners();\n this.refresh();\n }\n\n /**\n * Queues a refresh, redrawing the selection on the next opportunity.\n * @param isNewSelection Whether the selection should be registered as a new\n * selection on Linux.\n */\n public refresh(isNewSelection?: boolean): void {\n // Queue the refresh for the renderer\n if (!this._refreshAnimationFrame) {\n this._refreshAnimationFrame = window.requestAnimationFrame(() => this._refresh());\n }\n\n // If the platform is Linux and the refresh call comes from a mouse event,\n // we need to update the selection for middle click to paste selection.\n if (Browser.isLinux && isNewSelection) {\n const selectionText = this.selectionText;\n if (selectionText.length) {\n this.emit('newselection', this.selectionText);\n }\n }\n }\n\n /**\n * Fires the refresh event, causing consumers to pick it up and redraw the\n * selection state.\n */\n private _refresh(): void {\n this._refreshAnimationFrame = null;\n this.emit('refresh', { start: this._model.finalSelectionStart, end: this._model.finalSelectionEnd });\n }\n\n /**\n * Selects all text within the terminal.\n */\n public selectAll(): void {\n this._model.isSelectAllActive = true;\n this.refresh();\n }\n\n /**\n * Handle the buffer being trimmed, adjust the selection position.\n * @param amount The amount the buffer is being trimmed.\n */\n private _onTrim(amount: number) {\n const needsRefresh = this._model.onTrim(amount);\n if (needsRefresh) {\n this.refresh();\n }\n }\n\n /**\n * Gets the 0-based [x, y] buffer coordinates of the current mouse event.\n * @param event The mouse event.\n */\n private _getMouseBufferCoords(event: MouseEvent): [number, number] {\n const coords = Mouse.getCoords(event, this._rowContainer, this._charMeasure, this._terminal.cols, this._terminal.rows, true);\n if (!coords) {\n return null;\n }\n\n // Convert to 0-based\n coords[0]--;\n coords[1]--;\n // Convert viewport coords to buffer coords\n coords[1] += this._terminal.buffer.ydisp;\n return coords;\n }\n\n /**\n * Gets the amount the viewport should be scrolled based on how far out of the\n * terminal the mouse is.\n * @param event The mouse event.\n */\n private _getMouseEventScrollAmount(event: MouseEvent): number {\n let offset = Mouse.getCoordsRelativeToElement(event, this._rowContainer)[1];\n const terminalHeight = this._terminal.rows * this._charMeasure.height;\n if (offset >= 0 && offset <= terminalHeight) {\n return 0;\n }\n if (offset > terminalHeight) {\n offset -= terminalHeight;\n }\n\n offset = Math.min(Math.max(offset, -DRAG_SCROLL_MAX_THRESHOLD), DRAG_SCROLL_MAX_THRESHOLD);\n offset /= DRAG_SCROLL_MAX_THRESHOLD;\n return (offset / Math.abs(offset)) + Math.round(offset * (DRAG_SCROLL_MAX_SPEED - 1));\n }\n\n /**\n * Handles te mousedown event, setting up for a new selection.\n * @param event The mousedown event.\n */\n private _onMouseDown(event: MouseEvent) {\n // If we have selection, we want the context menu on right click even if the\n // terminal is in mouse mode.\n if (event.button === 2 && this.hasSelection) {\n event.stopPropagation();\n return;\n }\n\n // Only action the primary button\n if (event.button !== 0) {\n return;\n }\n\n // Allow selection when using a specific modifier key, even when disabled\n if (!this._enabled) {\n const shouldForceSelection = Browser.isMac && event.altKey;\n\n if (!shouldForceSelection) {\n return;\n }\n\n // Don't send the mouse down event to the current process, we want to select\n event.stopPropagation();\n }\n\n // Tell the browser not to start a regular selection\n event.preventDefault();\n\n // Reset drag scroll state\n this._dragScrollAmount = 0;\n\n if (this._enabled && event.shiftKey) {\n this._onIncrementalClick(event);\n } else {\n if (event.detail === 1) {\n this._onSingleClick(event);\n } else if (event.detail === 2) {\n this._onDoubleClick(event);\n } else if (event.detail === 3) {\n this._onTripleClick(event);\n }\n }\n\n this._addMouseDownListeners();\n this.refresh(true);\n }\n\n /**\n * Adds listeners when mousedown is triggered.\n */\n private _addMouseDownListeners(): void {\n // Listen on the document so that dragging outside of viewport works\n this._rowContainer.ownerDocument.addEventListener('mousemove', this._mouseMoveListener);\n this._rowContainer.ownerDocument.addEventListener('mouseup', this._mouseUpListener);\n this._dragScrollIntervalTimer = setInterval(() => this._dragScroll(), DRAG_SCROLL_INTERVAL);\n }\n\n /**\n * Removes the listeners that are registered when mousedown is triggered.\n */\n private _removeMouseDownListeners(): void {\n this._rowContainer.ownerDocument.removeEventListener('mousemove', this._mouseMoveListener);\n this._rowContainer.ownerDocument.removeEventListener('mouseup', this._mouseUpListener);\n clearInterval(this._dragScrollIntervalTimer);\n this._dragScrollIntervalTimer = null;\n }\n\n /**\n * Performs an incremental click, setting the selection end position to the mouse\n * position.\n * @param event The mouse event.\n */\n private _onIncrementalClick(event: MouseEvent): void {\n if (this._model.selectionStart) {\n this._model.selectionEnd = this._getMouseBufferCoords(event);\n }\n }\n\n /**\n * Performs a single click, resetting relevant state and setting the selection\n * start position.\n * @param event The mouse event.\n */\n private _onSingleClick(event: MouseEvent): void {\n this._model.selectionStartLength = 0;\n this._model.isSelectAllActive = false;\n this._activeSelectionMode = SelectionMode.NORMAL;\n\n // Initialize the new selection\n this._model.selectionStart = this._getMouseBufferCoords(event);\n if (!this._model.selectionStart) {\n return;\n }\n this._model.selectionEnd = null;\n\n // Ensure the line exists\n const line = this._buffer.get(this._model.selectionStart[1]);\n if (!line) {\n return;\n }\n\n // If the mouse is over the second half of a wide character, adjust the\n // selection to cover the whole character\n const char = line[this._model.selectionStart[0]];\n if (char[LINE_DATA_WIDTH_INDEX] === 0) {\n this._model.selectionStart[0]++;\n }\n }\n\n /**\n * Performs a double click, selecting the current work.\n * @param event The mouse event.\n */\n private _onDoubleClick(event: MouseEvent): void {\n const coords = this._getMouseBufferCoords(event);\n if (coords) {\n this._activeSelectionMode = SelectionMode.WORD;\n this._selectWordAt(coords);\n }\n }\n\n /**\n * Performs a triple click, selecting the current line and activating line\n * select mode.\n * @param event The mouse event.\n */\n private _onTripleClick(event: MouseEvent): void {\n const coords = this._getMouseBufferCoords(event);\n if (coords) {\n this._activeSelectionMode = SelectionMode.LINE;\n this._selectLineAt(coords[1]);\n }\n }\n\n /**\n * Handles the mousemove event when the mouse button is down, recording the\n * end of the selection and refreshing the selection.\n * @param event The mousemove event.\n */\n private _onMouseMove(event: MouseEvent) {\n // Record the previous position so we know whether to redraw the selection\n // at the end.\n const previousSelectionEnd = this._model.selectionEnd ? [this._model.selectionEnd[0], this._model.selectionEnd[1]] : null;\n\n // Set the initial selection end based on the mouse coordinates\n this._model.selectionEnd = this._getMouseBufferCoords(event);\n if (!this._model.selectionEnd) {\n this.refresh(true);\n return;\n }\n\n // Select the entire line if line select mode is active.\n if (this._activeSelectionMode === SelectionMode.LINE) {\n if (this._model.selectionEnd[1] < this._model.selectionStart[1]) {\n this._model.selectionEnd[0] = 0;\n } else {\n this._model.selectionEnd[0] = this._terminal.cols;\n }\n } else if (this._activeSelectionMode === SelectionMode.WORD) {\n this._selectToWordAt(this._model.selectionEnd);\n }\n\n // Determine the amount of scrolling that will happen.\n this._dragScrollAmount = this._getMouseEventScrollAmount(event);\n\n // If the cursor was above or below the viewport, make sure it's at the\n // start or end of the viewport respectively.\n if (this._dragScrollAmount > 0) {\n this._model.selectionEnd[0] = this._terminal.cols - 1;\n } else if (this._dragScrollAmount < 0) {\n this._model.selectionEnd[0] = 0;\n }\n\n // If the character is a wide character include the cell to the right in the\n // selection. Note that selections at the very end of the line will never\n // have a character.\n if (this._model.selectionEnd[1] < this._buffer.length) {\n const char = this._buffer.get(this._model.selectionEnd[1])[this._model.selectionEnd[0]];\n if (char && char[2] === 0) {\n this._model.selectionEnd[0]++;\n }\n }\n\n // Only draw here if the selection changes.\n if (!previousSelectionEnd ||\n previousSelectionEnd[0] !== this._model.selectionEnd[0] ||\n previousSelectionEnd[1] !== this._model.selectionEnd[1]) {\n this.refresh(true);\n }\n }\n\n /**\n * The callback that occurs every DRAG_SCROLL_INTERVAL ms that does the\n * scrolling of the viewport.\n */\n private _dragScroll() {\n if (this._dragScrollAmount) {\n this._terminal.scrollDisp(this._dragScrollAmount, false);\n // Re-evaluate selection\n if (this._dragScrollAmount > 0) {\n this._model.selectionEnd = [this._terminal.cols - 1, this._terminal.buffer.ydisp + this._terminal.rows];\n } else {\n this._model.selectionEnd = [0, this._terminal.buffer.ydisp];\n }\n this.refresh();\n }\n }\n\n /**\n * Handles the mouseup event, removing the mousedown listeners.\n * @param event The mouseup event.\n */\n private _onMouseUp(event: MouseEvent) {\n this._removeMouseDownListeners();\n }\n\n /**\n * Converts a viewport column to the character index on the buffer line, the\n * latter takes into account wide characters.\n * @param coords The coordinates to find the 2 index for.\n */\n private _convertViewportColToCharacterIndex(bufferLine: any, coords: [number, number]): number {\n let charIndex = coords[0];\n for (let i = 0; coords[0] >= i; i++) {\n const char = bufferLine[i];\n if (char[LINE_DATA_WIDTH_INDEX] === 0) {\n charIndex--;\n }\n }\n return charIndex;\n }\n\n public setSelection(col: number, row: number, length: number): void {\n this._model.clearSelection();\n this._removeMouseDownListeners();\n this._model.selectionStart = [col, row];\n this._model.selectionStartLength = length;\n this.refresh();\n }\n\n /**\n * Gets positional information for the word at the coordinated specified.\n * @param coords The coordinates to get the word at.\n */\n private _getWordAt(coords: [number, number]): IWordPosition {\n const bufferLine = this._buffer.get(coords[1]);\n if (!bufferLine) {\n return null;\n }\n\n const line = translateBufferLineToString(bufferLine, false);\n\n // Get actual index, taking into consideration wide characters\n let endIndex = this._convertViewportColToCharacterIndex(bufferLine, coords);\n let startIndex = endIndex;\n\n // Record offset to be used later\n const charOffset = coords[0] - startIndex;\n let leftWideCharCount = 0;\n let rightWideCharCount = 0;\n\n if (line.charAt(startIndex) === ' ') {\n // Expand until non-whitespace is hit\n while (startIndex > 0 && line.charAt(startIndex - 1) === ' ') {\n startIndex--;\n }\n while (endIndex < line.length && line.charAt(endIndex + 1) === ' ') {\n endIndex++;\n }\n } else {\n // Expand until whitespace is hit. This algorithm works by scanning left\n // and right from the starting position, keeping both the index format\n // (line) and the column format (bufferLine) in sync. When a wide\n // character is hit, it is recorded and the column index is adjusted.\n let startCol = coords[0];\n let endCol = coords[0];\n // Consider the initial position, skip it and increment the wide char\n // variable\n if (bufferLine[startCol][LINE_DATA_WIDTH_INDEX] === 0) {\n leftWideCharCount++;\n startCol--;\n }\n if (bufferLine[endCol][LINE_DATA_WIDTH_INDEX] === 2) {\n rightWideCharCount++;\n endCol++;\n }\n // Expand the string in both directions until a space is hit\n while (startIndex > 0 && !this._isCharWordSeparator(line.charAt(startIndex - 1))) {\n if (bufferLine[startCol - 1][LINE_DATA_WIDTH_INDEX] === 0) {\n // If the next character is a wide char, record it and skip the column\n leftWideCharCount++;\n startCol--;\n }\n startIndex--;\n startCol--;\n }\n while (endIndex + 1 < line.length && !this._isCharWordSeparator(line.charAt(endIndex + 1))) {\n if (bufferLine[endCol + 1][LINE_DATA_WIDTH_INDEX] === 2) {\n // If the next character is a wide char, record it and skip the column\n rightWideCharCount++;\n endCol++;\n }\n endIndex++;\n endCol++;\n }\n }\n\n const start = startIndex + charOffset - leftWideCharCount;\n const length = Math.min(endIndex - startIndex + leftWideCharCount + rightWideCharCount + 1/*include endIndex char*/, this._terminal.cols);\n return { start, length };\n }\n\n /**\n * Selects the word at the coordinates specified.\n * @param coords The coordinates to get the word at.\n */\n protected _selectWordAt(coords: [number, number]): void {\n const wordPosition = this._getWordAt(coords);\n if (wordPosition) {\n this._model.selectionStart = [wordPosition.start, coords[1]];\n this._model.selectionStartLength = wordPosition.length;\n }\n }\n\n /**\n * Sets the selection end to the word at the coordinated specified.\n * @param coords The coordinates to get the word at.\n */\n private _selectToWordAt(coords: [number, number]): void {\n const wordPosition = this._getWordAt(coords);\n if (wordPosition) {\n this._model.selectionEnd = [this._model.areSelectionValuesReversed() ? wordPosition.start : (wordPosition.start + wordPosition.length), coords[1]];\n }\n }\n\n /**\n * Gets whether the character is considered a word separator by the select\n * word logic.\n * @param char The character to check.\n */\n private _isCharWordSeparator(char: string): boolean {\n return WORD_SEPARATORS.indexOf(char) >= 0;\n }\n\n /**\n * Selects the line specified.\n * @param line The line index.\n */\n protected _selectLineAt(line: number): void {\n this._model.selectionStart = [0, line];\n this._model.selectionStartLength = this._terminal.cols;\n }\n}\n","/**\n * @license MIT\n */\n\nimport { ITerminal } from './Interfaces';\nimport { DomElementObjectPool } from './utils/DomElementObjectPool';\n\n/**\n * The maximum number of refresh frames to skip when the write buffer is non-\n * empty. Note that these frames may be intermingled with frames that are\n * skipped via requestAnimationFrame's mechanism.\n */\nconst MAX_REFRESH_FRAME_SKIP = 5;\n\n/**\n * Flags used to render terminal text properly.\n */\nenum FLAGS {\n BOLD = 1,\n UNDERLINE = 2,\n BLINK = 4,\n INVERSE = 8,\n INVISIBLE = 16\n};\n\nlet brokenBold: boolean = null;\n\nexport class Renderer {\n /** A queue of the rows to be refreshed */\n private _refreshRowsQueue: {start: number, end: number}[] = [];\n private _refreshFramesSkipped = 0;\n private _refreshAnimationFrame = null;\n\n private _spanElementObjectPool = new DomElementObjectPool('span');\n\n constructor(private _terminal: ITerminal) {\n // Figure out whether boldness affects\n // the character width of monospace fonts.\n if (brokenBold === null) {\n brokenBold = checkBoldBroken((this._terminal).element);\n }\n this._spanElementObjectPool = new DomElementObjectPool('span');\n\n // TODO: Pull more DOM interactions into Renderer.constructor, element for\n // example should be owned by Renderer (and also exposed by Terminal due to\n // to established public API).\n }\n\n /**\n * Queues a refresh between two rows (inclusive), to be done on next animation\n * frame.\n * @param {number} start The start row.\n * @param {number} end The end row.\n */\n public queueRefresh(start: number, end: number): void {\n this._refreshRowsQueue.push({ start: start, end: end });\n if (!this._refreshAnimationFrame) {\n this._refreshAnimationFrame = window.requestAnimationFrame(this._refreshLoop.bind(this));\n }\n }\n\n /**\n * Performs the refresh loop callback, calling refresh only if a refresh is\n * necessary before queueing up the next one.\n */\n private _refreshLoop(): void {\n // Skip MAX_REFRESH_FRAME_SKIP frames if the writeBuffer is non-empty as it\n // will need to be immediately refreshed anyway. This saves a lot of\n // rendering time as the viewport DOM does not need to be refreshed, no\n // scroll events, no layouts, etc.\n const skipFrame = this._terminal.writeBuffer.length > 0 && this._refreshFramesSkipped++ <= MAX_REFRESH_FRAME_SKIP;\n if (skipFrame) {\n this._refreshAnimationFrame = window.requestAnimationFrame(this._refreshLoop.bind(this));\n return;\n }\n\n this._refreshFramesSkipped = 0;\n let start;\n let end;\n if (this._refreshRowsQueue.length > 4) {\n // Just do a full refresh when 5+ refreshes are queued\n start = 0;\n end = this._terminal.rows - 1;\n } else {\n // Get start and end rows that need refreshing\n start = this._refreshRowsQueue[0].start;\n end = this._refreshRowsQueue[0].end;\n for (let i = 1; i < this._refreshRowsQueue.length; i++) {\n if (this._refreshRowsQueue[i].start < start) {\n start = this._refreshRowsQueue[i].start;\n }\n if (this._refreshRowsQueue[i].end > end) {\n end = this._refreshRowsQueue[i].end;\n }\n }\n }\n this._refreshRowsQueue = [];\n this._refreshAnimationFrame = null;\n this._refresh(start, end);\n }\n\n /**\n * Refreshes (re-renders) terminal content within two rows (inclusive)\n *\n * Rendering Engine:\n *\n * In the screen buffer, each character is stored as a an array with a character\n * and a 32-bit integer:\n * - First value: a utf-16 character.\n * - Second value:\n * - Next 9 bits: background color (0-511).\n * - Next 9 bits: foreground color (0-511).\n * - Next 14 bits: a mask for misc. flags:\n * - 1=bold\n * - 2=underline\n * - 4=blink\n * - 8=inverse\n * - 16=invisible\n *\n * @param {number} start The row to start from (between 0 and terminal's height terminal - 1)\n * @param {number} end The row to end at (between fromRow and terminal's height terminal - 1)\n */\n private _refresh(start: number, end: number): void {\n // If this is a big refresh, remove the terminal rows from the DOM for faster calculations\n let parent;\n if (end - start >= this._terminal.rows / 2) {\n parent = this._terminal.element.parentNode;\n if (parent) {\n this._terminal.element.removeChild(this._terminal.rowContainer);\n }\n }\n\n let width = this._terminal.cols;\n let y = start;\n\n if (end >= this._terminal.rows) {\n this._terminal.log('`end` is too large. Most likely a bad CSR.');\n end = this._terminal.rows - 1;\n }\n\n for (; y <= end; y++) {\n let row = y + this._terminal.buffer.ydisp;\n\n let line = this._terminal.buffer.lines.get(row);\n\n let x;\n if (this._terminal.buffer.y === y - (this._terminal.buffer.ybase - this._terminal.buffer.ydisp) &&\n this._terminal.cursorState &&\n !this._terminal.cursorHidden) {\n x = this._terminal.buffer.x;\n } else {\n x = -1;\n }\n\n let attr = this._terminal.defAttr;\n\n const documentFragment = document.createDocumentFragment();\n let innerHTML = '';\n let currentElement;\n\n // Return the row's spans to the pool\n while (this._terminal.children[y].children.length) {\n const child = this._terminal.children[y].children[0];\n this._terminal.children[y].removeChild(child);\n this._spanElementObjectPool.release(child);\n }\n\n for (let i = 0; i < width; i++) {\n // TODO: Could data be a more specific type?\n let data: any = line[i][0];\n const ch = line[i][1];\n const ch_width: any = line[i][2];\n const isCursor: boolean = i === x;\n if (!ch_width) {\n continue;\n }\n\n if (data !== attr || isCursor) {\n if (attr !== this._terminal.defAttr && !isCursor) {\n if (innerHTML) {\n currentElement.innerHTML = innerHTML;\n innerHTML = '';\n }\n documentFragment.appendChild(currentElement);\n currentElement = null;\n }\n if (data !== this._terminal.defAttr || isCursor) {\n if (innerHTML && !currentElement) {\n currentElement = this._spanElementObjectPool.acquire();\n }\n if (currentElement) {\n if (innerHTML) {\n currentElement.innerHTML = innerHTML;\n innerHTML = '';\n }\n documentFragment.appendChild(currentElement);\n }\n currentElement = this._spanElementObjectPool.acquire();\n\n let bg = data & 0x1ff;\n let fg = (data >> 9) & 0x1ff;\n let flags = data >> 18;\n\n if (isCursor) {\n currentElement.classList.add('reverse-video');\n currentElement.classList.add('terminal-cursor');\n }\n\n if (flags & FLAGS.BOLD) {\n if (!brokenBold) {\n currentElement.classList.add('xterm-bold');\n }\n // See: XTerm*boldColors\n if (fg < 8) {\n fg += 8;\n }\n }\n\n if (flags & FLAGS.UNDERLINE) {\n currentElement.classList.add('xterm-underline');\n }\n\n if (flags & FLAGS.BLINK) {\n currentElement.classList.add('xterm-blink');\n }\n\n // If inverse flag is on, then swap the foreground and background variables.\n if (flags & FLAGS.INVERSE) {\n let temp = bg;\n bg = fg;\n fg = temp;\n // Should inverse just be before the above boldColors effect instead?\n if ((flags & 1) && fg < 8) {\n fg += 8;\n }\n }\n\n if (flags & FLAGS.INVISIBLE && !isCursor) {\n currentElement.classList.add('xterm-hidden');\n }\n\n /**\n * Weird situation: Invert flag used black foreground and white background results\n * in invalid background color, positioned at the 256 index of the 256 terminal\n * color map. Pin the colors manually in such a case.\n *\n * Source: https://github.com/sourcelair/xterm.js/issues/57\n */\n if (flags & FLAGS.INVERSE) {\n if (bg === 257) {\n bg = 15;\n }\n if (fg === 256) {\n fg = 0;\n }\n }\n\n if (bg < 256) {\n currentElement.classList.add(`xterm-bg-color-${bg}`);\n }\n\n if (fg < 256) {\n currentElement.classList.add(`xterm-color-${fg}`);\n }\n\n }\n }\n\n if (ch_width === 2) {\n // Wrap wide characters so they're sized correctly. It's more difficult to release these\n // from the object pool so just create new ones via innerHTML.\n innerHTML += `${ch}`;\n } else if (ch.charCodeAt(0) > 255) {\n // Wrap any non-wide unicode character as some fonts size them badly\n innerHTML += `${ch}`;\n } else {\n switch (ch) {\n case '&':\n innerHTML += '&';\n break;\n case '<':\n innerHTML += '<';\n break;\n case '>':\n innerHTML += '>';\n break;\n default:\n if (ch <= ' ') {\n innerHTML += ' ';\n } else {\n innerHTML += ch;\n }\n break;\n }\n }\n\n // The cursor needs its own element, therefore we set attr to -1\n // which will cause the next character to be rendered in a new element\n attr = isCursor ? -1 : data;\n\n }\n\n if (innerHTML && !currentElement) {\n currentElement = this._spanElementObjectPool.acquire();\n }\n if (currentElement) {\n if (innerHTML) {\n currentElement.innerHTML = innerHTML;\n innerHTML = '';\n }\n documentFragment.appendChild(currentElement);\n currentElement = null;\n }\n\n this._terminal.children[y].appendChild(documentFragment);\n }\n\n if (parent) {\n this._terminal.element.appendChild(this._terminal.rowContainer);\n }\n\n this._terminal.emit('refresh', {element: this._terminal.element, start: start, end: end});\n };\n\n /**\n * Refreshes the selection in the DOM.\n * @param start The selection start.\n * @param end The selection end.\n */\n public refreshSelection(start: [number, number], end: [number, number]) {\n // Remove all selections\n while (this._terminal.selectionContainer.children.length) {\n this._terminal.selectionContainer.removeChild(this._terminal.selectionContainer.children[0]);\n }\n\n // Selection does not exist\n if (!start || !end) {\n return;\n }\n\n // Translate from buffer position to viewport position\n const viewportStartRow = start[1] - this._terminal.buffer.ydisp;\n const viewportEndRow = end[1] - this._terminal.buffer.ydisp;\n const viewportCappedStartRow = Math.max(viewportStartRow, 0);\n const viewportCappedEndRow = Math.min(viewportEndRow, this._terminal.rows - 1);\n\n // No need to draw the selection\n if (viewportCappedStartRow >= this._terminal.rows || viewportCappedEndRow < 0) {\n return;\n }\n\n // Create the selections\n const documentFragment = document.createDocumentFragment();\n // Draw first row\n const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0;\n const endCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : this._terminal.cols;\n documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow, startCol, endCol));\n // Draw middle rows\n const middleRowsCount = viewportCappedEndRow - viewportCappedStartRow - 1;\n documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow + 1, 0, this._terminal.cols, middleRowsCount));\n // Draw final row\n if (viewportCappedStartRow !== viewportCappedEndRow) {\n // Only draw viewportEndRow if it's not the same as viewporttartRow\n const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : this._terminal.cols;\n documentFragment.appendChild(this._createSelectionElement(viewportCappedEndRow, 0, endCol));\n }\n this._terminal.selectionContainer.appendChild(documentFragment);\n }\n\n /**\n * Creates a selection element at the specified position.\n * @param row The row of the selection.\n * @param colStart The start column.\n * @param colEnd The end columns.\n */\n private _createSelectionElement(row: number, colStart: number, colEnd: number, rowCount: number = 1): HTMLElement {\n const element = document.createElement('div');\n element.style.height = `${rowCount * this._terminal.charMeasure.height}px`;\n element.style.top = `${row * this._terminal.charMeasure.height}px`;\n element.style.left = `${colStart * this._terminal.charMeasure.width}px`;\n element.style.width = `${this._terminal.charMeasure.width * (colEnd - colStart)}px`;\n return element;\n }\n}\n\n\n// If bold is broken, we can't use it in the terminal.\nfunction checkBoldBroken(terminal) {\n const document = terminal.ownerDocument;\n const el = document.createElement('span');\n el.innerHTML = 'hello world';\n terminal.appendChild(el);\n const w1 = el.offsetWidth;\n const h1 = el.offsetHeight;\n el.style.fontWeight = 'bold';\n const w2 = el.offsetWidth;\n const h2 = el.offsetHeight;\n terminal.removeChild(el);\n return w1 !== w2 || h1 !== h2;\n}\n","/**\n * @license MIT\n */\n\nimport { C0 } from './EscapeSequences';\nimport { IInputHandler } from './Interfaces';\nimport { CHARSETS, DEFAULT_CHARSET } from './Charsets';\n\nconst normalStateHandler: {[key: string]: (parser: Parser, handler: IInputHandler) => void} = {};\nnormalStateHandler[C0.BEL] = (parser, handler) => handler.bell();\nnormalStateHandler[C0.LF] = (parser, handler) => handler.lineFeed();\nnormalStateHandler[C0.VT] = normalStateHandler[C0.LF];\nnormalStateHandler[C0.FF] = normalStateHandler[C0.LF];\nnormalStateHandler[C0.CR] = (parser, handler) => handler.carriageReturn();\nnormalStateHandler[C0.BS] = (parser, handler) => handler.backspace();\nnormalStateHandler[C0.HT] = (parser, handler) => handler.tab();\nnormalStateHandler[C0.SO] = (parser, handler) => handler.shiftOut();\nnormalStateHandler[C0.SI] = (parser, handler) => handler.shiftIn();\nnormalStateHandler[C0.ESC] = (parser, handler) => parser.setState(ParserState.ESCAPED);\n\n// TODO: Remove terminal when parser owns params and currentParam\nconst escapedStateHandler: {[key: string]: (parser: Parser, terminal: any) => void} = {};\nescapedStateHandler['['] = (parser, terminal) => {\n // ESC [ Control Sequence Introducer (CSI is 0x9b)\n terminal.params = [];\n terminal.currentParam = 0;\n parser.setState(ParserState.CSI_PARAM);\n};\nescapedStateHandler[']'] = (parser, terminal) => {\n // ESC ] Operating System Command (OSC is 0x9d)\n terminal.params = [];\n terminal.currentParam = 0;\n parser.setState(ParserState.OSC);\n};\nescapedStateHandler['P'] = (parser, terminal) => {\n // ESC P Device Control String (DCS is 0x90)\n terminal.params = [];\n terminal.currentParam = 0;\n parser.setState(ParserState.DCS);\n};\nescapedStateHandler['_'] = (parser, terminal) => {\n // ESC _ Application Program Command ( APC is 0x9f).\n parser.setState(ParserState.IGNORE);\n};\nescapedStateHandler['^'] = (parser, terminal) => {\n // ESC ^ Privacy Message ( PM is 0x9e).\n parser.setState(ParserState.IGNORE);\n};\nescapedStateHandler['c'] = (parser, terminal) => {\n // ESC c Full Reset (RIS).\n terminal.reset();\n};\nescapedStateHandler['E'] = (parser, terminal) => {\n // ESC E Next Line ( NEL is 0x85).\n terminal.buffer.x = 0;\n terminal.index();\n parser.setState(ParserState.NORMAL);\n};\nescapedStateHandler['D'] = (parser, terminal) => {\n // ESC D Index ( IND is 0x84).\n terminal.index();\n parser.setState(ParserState.NORMAL);\n};\nescapedStateHandler['M'] = (parser, terminal) => {\n // ESC M Reverse Index ( RI is 0x8d).\n terminal.reverseIndex();\n parser.setState(ParserState.NORMAL);\n};\nescapedStateHandler['%'] = (parser, terminal) => {\n // ESC % Select default/utf-8 character set.\n // @ = default, G = utf-8\n terminal.setgLevel(0);\n terminal.setgCharset(0, DEFAULT_CHARSET); // US (default)\n parser.setState(ParserState.NORMAL);\n parser.skipNextChar();\n};\nescapedStateHandler[C0.CAN] = (parser) => parser.setState(ParserState.NORMAL);\n\nconst csiParamStateHandler: {[key: string]: (parser: Parser) => void} = {};\ncsiParamStateHandler['?'] = (parser) => parser.setPrefix('?');\ncsiParamStateHandler['>'] = (parser) => parser.setPrefix('>');\ncsiParamStateHandler['!'] = (parser) => parser.setPrefix('!');\ncsiParamStateHandler['0'] = (parser) => parser.setParam(parser.getParam() * 10);\ncsiParamStateHandler['1'] = (parser) => parser.setParam(parser.getParam() * 10 + 1);\ncsiParamStateHandler['2'] = (parser) => parser.setParam(parser.getParam() * 10 + 2);\ncsiParamStateHandler['3'] = (parser) => parser.setParam(parser.getParam() * 10 + 3);\ncsiParamStateHandler['4'] = (parser) => parser.setParam(parser.getParam() * 10 + 4);\ncsiParamStateHandler['5'] = (parser) => parser.setParam(parser.getParam() * 10 + 5);\ncsiParamStateHandler['6'] = (parser) => parser.setParam(parser.getParam() * 10 + 6);\ncsiParamStateHandler['7'] = (parser) => parser.setParam(parser.getParam() * 10 + 7);\ncsiParamStateHandler['8'] = (parser) => parser.setParam(parser.getParam() * 10 + 8);\ncsiParamStateHandler['9'] = (parser) => parser.setParam(parser.getParam() * 10 + 9);\ncsiParamStateHandler['$'] = (parser) => parser.setPostfix('$');\ncsiParamStateHandler['\"'] = (parser) => parser.setPostfix('\"');\ncsiParamStateHandler[' '] = (parser) => parser.setPostfix(' ');\ncsiParamStateHandler['\\''] = (parser) => parser.setPostfix('\\'');\ncsiParamStateHandler[';'] = (parser) => parser.finalizeParam();\ncsiParamStateHandler[C0.CAN] = (parser) => parser.setState(ParserState.NORMAL);\n\nconst csiStateHandler: {[key: string]: (handler: IInputHandler, params: number[], prefix: string, postfix: string, parser: Parser) => void} = {};\ncsiStateHandler['@'] = (handler, params, prefix) => handler.insertChars(params);\ncsiStateHandler['A'] = (handler, params, prefix) => handler.cursorUp(params);\ncsiStateHandler['B'] = (handler, params, prefix) => handler.cursorDown(params);\ncsiStateHandler['C'] = (handler, params, prefix) => handler.cursorForward(params);\ncsiStateHandler['D'] = (handler, params, prefix) => handler.cursorBackward(params);\ncsiStateHandler['E'] = (handler, params, prefix) => handler.cursorNextLine(params);\ncsiStateHandler['F'] = (handler, params, prefix) => handler.cursorPrecedingLine(params);\ncsiStateHandler['G'] = (handler, params, prefix) => handler.cursorCharAbsolute(params);\ncsiStateHandler['H'] = (handler, params, prefix) => handler.cursorPosition(params);\ncsiStateHandler['I'] = (handler, params, prefix) => handler.cursorForwardTab(params);\ncsiStateHandler['J'] = (handler, params, prefix) => handler.eraseInDisplay(params);\ncsiStateHandler['K'] = (handler, params, prefix) => handler.eraseInLine(params);\ncsiStateHandler['L'] = (handler, params, prefix) => handler.insertLines(params);\ncsiStateHandler['M'] = (handler, params, prefix) => handler.deleteLines(params);\ncsiStateHandler['P'] = (handler, params, prefix) => handler.deleteChars(params);\ncsiStateHandler['S'] = (handler, params, prefix) => handler.scrollUp(params);\ncsiStateHandler['T'] = (handler, params, prefix) => {\n if (params.length < 2 && !prefix) {\n handler.scrollDown(params);\n }\n};\ncsiStateHandler['X'] = (handler, params, prefix) => handler.eraseChars(params);\ncsiStateHandler['Z'] = (handler, params, prefix) => handler.cursorBackwardTab(params);\ncsiStateHandler['`'] = (handler, params, prefix) => handler.charPosAbsolute(params);\ncsiStateHandler['a'] = (handler, params, prefix) => handler.HPositionRelative(params);\ncsiStateHandler['b'] = (handler, params, prefix) => handler.repeatPrecedingCharacter(params);\ncsiStateHandler['c'] = (handler, params, prefix) => handler.sendDeviceAttributes(params);\ncsiStateHandler['d'] = (handler, params, prefix) => handler.linePosAbsolute(params);\ncsiStateHandler['e'] = (handler, params, prefix) => handler.VPositionRelative(params);\ncsiStateHandler['f'] = (handler, params, prefix) => handler.HVPosition(params);\ncsiStateHandler['g'] = (handler, params, prefix) => handler.tabClear(params);\ncsiStateHandler['h'] = (handler, params, prefix) => handler.setMode(params);\ncsiStateHandler['l'] = (handler, params, prefix) => handler.resetMode(params);\ncsiStateHandler['m'] = (handler, params, prefix) => handler.charAttributes(params);\ncsiStateHandler['n'] = (handler, params, prefix) => handler.deviceStatus(params);\ncsiStateHandler['p'] = (handler, params, prefix) => {\n switch (prefix) {\n case '!': handler.softReset(params); break;\n }\n};\ncsiStateHandler['q'] = (handler, params, prefix, postfix) => {\n if (postfix === ' ') {\n handler.setCursorStyle(params);\n }\n};\ncsiStateHandler['r'] = (handler, params) => handler.setScrollRegion(params);\ncsiStateHandler['s'] = (handler, params) => handler.saveCursor(params);\ncsiStateHandler['u'] = (handler, params) => handler.restoreCursor(params);\ncsiStateHandler[C0.CAN] = (handler, params, prefix, postfix, parser) => parser.setState(ParserState.NORMAL);\n\nenum ParserState {\n NORMAL = 0,\n ESCAPED = 1,\n CSI_PARAM = 2,\n CSI = 3,\n OSC = 4,\n CHARSET = 5,\n DCS = 6,\n IGNORE = 7\n}\n\n/**\n * The terminal's parser, all input into the terminal goes through the parser\n * which parses and defers the actual input handling the the IInputHandler\n * specified in the constructor.\n */\nexport class Parser {\n private _state: ParserState;\n private _position: number;\n\n // TODO: Remove terminal when handler can do everything\n constructor(\n private _inputHandler: IInputHandler,\n private _terminal: any\n ) {\n this._state = ParserState.NORMAL;\n }\n\n /**\n * Parse and handle data.\n *\n * @param data The data to parse.\n */\n public parse(data: string): ParserState {\n let l = data.length, j, cs, ch, code, low;\n\n if (this._terminal.debug) {\n this._terminal.log('data: ' + data);\n }\n\n this._position = 0;\n // apply leftover surrogate high from last write\n if (this._terminal.surrogate_high) {\n data = this._terminal.surrogate_high + data;\n this._terminal.surrogate_high = '';\n }\n\n for (; this._position < l; this._position++) {\n ch = data[this._position];\n\n // FIXME: higher chars than 0xa0 are not allowed in escape sequences\n // --> maybe move to default\n code = data.charCodeAt(this._position);\n if (0xD800 <= code && code <= 0xDBFF) {\n // we got a surrogate high\n // get surrogate low (next 2 bytes)\n low = data.charCodeAt(this._position + 1);\n if (isNaN(low)) {\n // end of data stream, save surrogate high\n this._terminal.surrogate_high = ch;\n continue;\n }\n code = ((code - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000;\n ch += data.charAt(this._position + 1);\n }\n // surrogate low - already handled above\n if (0xDC00 <= code && code <= 0xDFFF)\n continue;\n\n switch (this._state) {\n case ParserState.NORMAL:\n if (ch in normalStateHandler) {\n normalStateHandler[ch](this, this._inputHandler);\n } else {\n this._inputHandler.addChar(ch, code);\n }\n break;\n case ParserState.ESCAPED:\n if (ch in escapedStateHandler) {\n escapedStateHandler[ch](this, this._terminal);\n // Skip switch as it was just handled\n break;\n }\n switch (ch) {\n\n // ESC (,),*,+,-,. Designate G0-G2 Character Set.\n case '(': // <-- this seems to get all the attention\n case ')':\n case '*':\n case '+':\n case '-':\n case '.':\n switch (ch) {\n case '(':\n this._terminal.gcharset = 0;\n break;\n case ')':\n this._terminal.gcharset = 1;\n break;\n case '*':\n this._terminal.gcharset = 2;\n break;\n case '+':\n this._terminal.gcharset = 3;\n break;\n case '-':\n this._terminal.gcharset = 1;\n break;\n case '.':\n this._terminal.gcharset = 2;\n break;\n }\n this._state = ParserState.CHARSET;\n break;\n\n // Designate G3 Character Set (VT300).\n // A = ISO Latin-1 Supplemental.\n // Not implemented.\n case '/':\n this._terminal.gcharset = 3;\n this._state = ParserState.CHARSET;\n this._position--;\n break;\n\n // ESC N\n // Single Shift Select of G2 Character Set\n // ( SS2 is 0x8e). This affects next character only.\n case 'N':\n break;\n // ESC O\n // Single Shift Select of G3 Character Set\n // ( SS3 is 0x8f). This affects next character only.\n case 'O':\n break;\n // ESC n\n // Invoke the G2 Character Set as GL (LS2).\n case 'n':\n this._terminal.setgLevel(2);\n break;\n // ESC o\n // Invoke the G3 Character Set as GL (LS3).\n case 'o':\n this._terminal.setgLevel(3);\n break;\n // ESC |\n // Invoke the G3 Character Set as GR (LS3R).\n case '|':\n this._terminal.setgLevel(3);\n break;\n // ESC }\n // Invoke the G2 Character Set as GR (LS2R).\n case '}':\n this._terminal.setgLevel(2);\n break;\n // ESC ~\n // Invoke the G1 Character Set as GR (LS1R).\n case '~':\n this._terminal.setgLevel(1);\n break;\n\n // ESC 7 Save Cursor (DECSC).\n case '7':\n this._inputHandler.saveCursor();\n this._state = ParserState.NORMAL;\n break;\n\n // ESC 8 Restore Cursor (DECRC).\n case '8':\n this._inputHandler.restoreCursor();\n this._state = ParserState.NORMAL;\n break;\n\n // ESC # 3 DEC line height/width\n case '#':\n this._state = ParserState.NORMAL;\n this._position++;\n break;\n\n // ESC H Tab Set (HTS is 0x88).\n case 'H':\n this._terminal.tabSet();\n this._state = ParserState.NORMAL;\n break;\n\n // ESC = Application Keypad (DECKPAM).\n case '=':\n this._terminal.log('Serial port requested application keypad.');\n this._terminal.applicationKeypad = true;\n this._terminal.viewport.syncScrollArea();\n this._state = ParserState.NORMAL;\n break;\n\n // ESC > Normal Keypad (DECKPNM).\n case '>':\n this._terminal.log('Switching back to normal keypad.');\n this._terminal.applicationKeypad = false;\n this._terminal.viewport.syncScrollArea();\n this._state = ParserState.NORMAL;\n break;\n\n default:\n this._state = ParserState.NORMAL;\n this._terminal.error('Unknown ESC control: %s.', ch);\n break;\n }\n break;\n\n case ParserState.CHARSET:\n if (ch in CHARSETS) {\n cs = CHARSETS[ch];\n if (ch === '/') { // ISOLatin is actually /A\n this.skipNextChar();\n }\n } else {\n cs = DEFAULT_CHARSET;\n }\n this._terminal.setgCharset(this._terminal.gcharset, cs);\n this._terminal.gcharset = null;\n this._state = ParserState.NORMAL;\n break;\n\n case ParserState.OSC:\n // OSC Ps ; Pt ST\n // OSC Ps ; Pt BEL\n // Set Text Parameters.\n if (ch === C0.ESC || ch === C0.BEL) {\n if (ch === C0.ESC) this._position++;\n\n this._terminal.params.push(this._terminal.currentParam);\n\n switch (this._terminal.params[0]) {\n case 0:\n case 1:\n case 2:\n if (this._terminal.params[1]) {\n this._terminal.title = this._terminal.params[1];\n this._terminal.handleTitle(this._terminal.title);\n }\n break;\n case 3:\n // set X property\n break;\n case 4:\n case 5:\n // change dynamic colors\n break;\n case 10:\n case 11:\n case 12:\n case 13:\n case 14:\n case 15:\n case 16:\n case 17:\n case 18:\n case 19:\n // change dynamic ui colors\n break;\n case 46:\n // change log file\n break;\n case 50:\n // dynamic font\n break;\n case 51:\n // emacs shell\n break;\n case 52:\n // manipulate selection data\n break;\n case 104:\n case 105:\n case 110:\n case 111:\n case 112:\n case 113:\n case 114:\n case 115:\n case 116:\n case 117:\n case 118:\n // reset colors\n break;\n }\n\n this._terminal.params = [];\n this._terminal.currentParam = 0;\n this._state = ParserState.NORMAL;\n } else {\n if (!this._terminal.params.length) {\n if (ch >= '0' && ch <= '9') {\n this._terminal.currentParam =\n this._terminal.currentParam * 10 + ch.charCodeAt(0) - 48;\n } else if (ch === ';') {\n this._terminal.params.push(this._terminal.currentParam);\n this._terminal.currentParam = '';\n }\n } else {\n this._terminal.currentParam += ch;\n }\n }\n break;\n\n case ParserState.CSI_PARAM:\n if (ch in csiParamStateHandler) {\n csiParamStateHandler[ch](this);\n break;\n }\n this.finalizeParam();\n // Fall through the CSI as this character should be the CSI code.\n this._state = ParserState.CSI;\n\n case ParserState.CSI:\n if (ch in csiStateHandler) {\n if (this._terminal.debug) {\n this._terminal.log(`CSI ${this._terminal.prefix ? this._terminal.prefix : ''} ${this._terminal.params ? this._terminal.params.join(';') : ''} ${this._terminal.postfix ? this._terminal.postfix : ''} ${ch}`);\n }\n csiStateHandler[ch](this._inputHandler, this._terminal.params, this._terminal.prefix, this._terminal.postfix, this);\n } else {\n this._terminal.error('Unknown CSI code: %s.', ch);\n }\n\n this._state = ParserState.NORMAL;\n this._terminal.prefix = '';\n this._terminal.postfix = '';\n break;\n\n case ParserState.DCS:\n if (ch === C0.ESC || ch === C0.BEL) {\n if (ch === C0.ESC) this._position++;\n let pt;\n let valid: boolean;\n\n switch (this._terminal.prefix) {\n // User-Defined Keys (DECUDK).\n case '':\n break;\n\n // Request Status String (DECRQSS).\n // test: echo -e '\\eP$q\"p\\e\\\\'\n case '$q':\n pt = this._terminal.currentParam;\n valid = false;\n\n switch (pt) {\n // DECSCA\n case '\"q':\n pt = '0\"q';\n break;\n\n // DECSCL\n case '\"p':\n pt = '61\"p';\n break;\n\n // DECSTBM\n case 'r':\n pt = ''\n + (this._terminal.buffer.scrollTop + 1)\n + ';'\n + (this._terminal.buffer.scrollBottom + 1)\n + 'r';\n break;\n\n // SGR\n case 'm':\n pt = '0m';\n break;\n\n default:\n this._terminal.error('Unknown DCS Pt: %s.', pt);\n pt = '';\n break;\n }\n\n this._terminal.send(C0.ESC + 'P' + +valid + '$r' + pt + C0.ESC + '\\\\');\n break;\n\n // Set Termcap/Terminfo Data (xterm, experimental).\n case '+p':\n break;\n\n // Request Termcap/Terminfo String (xterm, experimental)\n // Regular xterm does not even respond to this sequence.\n // This can cause a small glitch in vim.\n // test: echo -ne '\\eP+q6b64\\e\\\\'\n case '+q':\n pt = this._terminal.currentParam;\n valid = false;\n\n this._terminal.send(C0.ESC + 'P' + +valid + '+r' + pt + C0.ESC + '\\\\');\n break;\n\n default:\n this._terminal.error('Unknown DCS prefix: %s.', this._terminal.prefix);\n break;\n }\n\n this._terminal.currentParam = 0;\n this._terminal.prefix = '';\n this._state = ParserState.NORMAL;\n } else if (!this._terminal.currentParam) {\n if (!this._terminal.prefix && ch !== '$' && ch !== '+') {\n this._terminal.currentParam = ch;\n } else if (this._terminal.prefix.length === 2) {\n this._terminal.currentParam = ch;\n } else {\n this._terminal.prefix += ch;\n }\n } else {\n this._terminal.currentParam += ch;\n }\n break;\n\n case ParserState.IGNORE:\n // For PM and APC.\n if (ch === C0.ESC || ch === C0.BEL) {\n if (ch === C0.ESC) this._position++;\n this._state = ParserState.NORMAL;\n }\n break;\n }\n }\n return this._state;\n }\n\n /**\n * Set the parser's current parsing state.\n *\n * @param state The new state.\n */\n public setState(state: ParserState): void {\n this._state = state;\n }\n\n /**\n * Sets the parsier's current prefix. CSI codes can have prefixes of '?', '>'\n * or '!'.\n *\n * @param prefix The prefix.\n */\n public setPrefix(prefix: string): void {\n this._terminal.prefix = prefix;\n }\n\n /**\n * Sets the parsier's current prefix. CSI codes can have postfixes of '$',\n * '\"', ' ', '\\''.\n *\n * @param postfix The postfix.\n */\n public setPostfix(postfix: string): void {\n this._terminal.postfix = postfix;\n }\n\n /**\n * Sets the parser's current parameter.\n *\n * @param param the parameter.\n */\n public setParam(param: number) {\n this._terminal.currentParam = param;\n }\n\n /**\n * Gets the parser's current parameter.\n */\n public getParam(): number {\n return this._terminal.currentParam;\n }\n\n /**\n * Finalizes the parser's current parameter, adding it to the list of\n * parameters and setting the new current parameter to 0.\n */\n public finalizeParam(): void {\n this._terminal.params.push(this._terminal.currentParam);\n this._terminal.currentParam = 0;\n }\n\n /**\n * Tell the parser to skip the next character.\n */\n public skipNextChar(): void {\n this._position++;\n }\n\n /**\n * Tell the parser to repeat parsing the current character (for example if it\n * needs parsing using a different state.\n */\n // public repeatChar(): void {\n // this._position--;\n // }\n}\n","/**\n * @license MIT\n */\n\nimport { LinkMatcherOptions } from './Interfaces';\nimport { LinkMatcher, LinkMatcherHandler, LinkMatcherValidationCallback } from './Types';\n\nconst INVALID_LINK_CLASS = 'xterm-invalid-link';\n\nconst protocolClause = '(https?:\\\\/\\\\/)';\nconst domainCharacterSet = '[\\\\da-z\\\\.-]+';\nconst negatedDomainCharacterSet = '[^\\\\da-z\\\\.-]+';\nconst domainBodyClause = '(' + domainCharacterSet + ')';\nconst tldClause = '([a-z\\\\.]{2,6})';\nconst ipClause = '((\\\\d{1,3}\\\\.){3}\\\\d{1,3})';\nconst localHostClause = '(localhost)';\nconst portClause = '(:\\\\d{1,5})';\nconst hostClause = '((' + domainBodyClause + '\\\\.' + tldClause + ')|' + ipClause + '|' + localHostClause + ')' + portClause + '?';\nconst pathClause = '(\\\\/[\\\\/\\\\w\\\\.\\\\-%~]*)*';\nconst queryStringHashFragmentCharacterSet = '[0-9\\\\w\\\\[\\\\]\\\\(\\\\)\\\\/\\\\?\\\\!#@$%&\\'*+,:;~\\\\=\\\\.\\\\-]*';\nconst queryStringClause = '(\\\\?' + queryStringHashFragmentCharacterSet + ')?';\nconst hashFragmentClause = '(#' + queryStringHashFragmentCharacterSet + ')?';\nconst negatedPathCharacterSet = '[^\\\\/\\\\w\\\\.\\\\-%]+';\nconst bodyClause = hostClause + pathClause + queryStringClause + hashFragmentClause;\nconst start = '(?:^|' + negatedDomainCharacterSet + ')(';\nconst end = ')($|' + negatedPathCharacterSet + ')';\nconst strictUrlRegex = new RegExp(start + protocolClause + bodyClause + end);\n\n/**\n * The ID of the built in http(s) link matcher.\n */\nconst HYPERTEXT_LINK_MATCHER_ID = 0;\n\n/**\n * The Linkifier applies links to rows shortly after they have been refreshed.\n */\nexport class Linkifier {\n /**\n * The time to wait after a row is changed before it is linkified. This prevents\n * the costly operation of searching every row multiple times, potentially a\n * huge amount of times.\n */\n protected static TIME_BEFORE_LINKIFY = 200;\n\n protected _linkMatchers: LinkMatcher[];\n\n private _document: Document;\n private _rows: HTMLElement[];\n private _rowTimeoutIds: number[];\n private _nextLinkMatcherId = HYPERTEXT_LINK_MATCHER_ID;\n\n constructor() {\n this._rowTimeoutIds = [];\n this._linkMatchers = [];\n this.registerLinkMatcher(strictUrlRegex, null, { matchIndex: 1 });\n }\n\n /**\n * Attaches the linkifier to the DOM, enabling linkification.\n * @param document The document object.\n * @param rows The array of rows to apply links to.\n */\n public attachToDom(document: Document, rows: HTMLElement[]) {\n this._document = document;\n this._rows = rows;\n }\n\n /**\n * Queues a row for linkification.\n * @param {number} rowIndex The index of the row to linkify.\n */\n public linkifyRow(rowIndex: number): void {\n // Don't attempt linkify if not yet attached to DOM\n if (!this._document) {\n return;\n }\n\n const timeoutId = this._rowTimeoutIds[rowIndex];\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n this._rowTimeoutIds[rowIndex] = setTimeout(this._linkifyRow.bind(this, rowIndex), Linkifier.TIME_BEFORE_LINKIFY);\n }\n\n /**\n * Attaches a handler for hypertext links, overriding default behavior\n * for standard http(s) links.\n * @param {LinkHandler} handler The handler to use, this can be cleared with\n * null.\n */\n public setHypertextLinkHandler(handler: LinkMatcherHandler): void {\n this._linkMatchers[HYPERTEXT_LINK_MATCHER_ID].handler = handler;\n }\n\n /**\n * Attaches a validation callback for hypertext links.\n * @param {LinkMatcherValidationCallback} callback The callback to use, this\n * can be cleared with null.\n */\n public setHypertextValidationCallback(callback: LinkMatcherValidationCallback): void {\n this._linkMatchers[HYPERTEXT_LINK_MATCHER_ID].validationCallback = callback;\n }\n\n /**\n * Registers a link matcher, allowing custom link patterns to be matched and\n * handled.\n * @param {RegExp} regex The regular expression to search for, specifically\n * this searches the textContent of the rows. You will want to use \\s to match\n * a space ' ' character for example.\n * @param {LinkHandler} handler The callback when the link is called.\n * @param {LinkMatcherOptions} [options] Options for the link matcher.\n * @return {number} The ID of the new matcher, this can be used to deregister.\n */\n public registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options: LinkMatcherOptions = {}): number {\n if (this._nextLinkMatcherId !== HYPERTEXT_LINK_MATCHER_ID && !handler) {\n throw new Error('handler must be defined');\n }\n const matcher: LinkMatcher = {\n id: this._nextLinkMatcherId++,\n regex,\n handler,\n matchIndex: options.matchIndex,\n validationCallback: options.validationCallback,\n priority: options.priority || 0\n };\n this._addLinkMatcherToList(matcher);\n return matcher.id;\n }\n\n /**\n * Inserts a link matcher to the list in the correct position based on the\n * priority of each link matcher. New link matchers of equal priority are\n * considered after older link matchers.\n * @param matcher The link matcher to be added.\n */\n private _addLinkMatcherToList(matcher: LinkMatcher): void {\n if (this._linkMatchers.length === 0) {\n this._linkMatchers.push(matcher);\n return;\n }\n\n for (let i = this._linkMatchers.length - 1; i >= 0; i--) {\n if (matcher.priority <= this._linkMatchers[i].priority) {\n this._linkMatchers.splice(i + 1, 0, matcher);\n return;\n }\n }\n\n this._linkMatchers.splice(0, 0, matcher);\n }\n\n /**\n * Deregisters a link matcher if it has been registered.\n * @param {number} matcherId The link matcher's ID (returned after register)\n * @return {boolean} Whether a link matcher was found and deregistered.\n */\n public deregisterLinkMatcher(matcherId: number): boolean {\n // ID 0 is the hypertext link matcher which cannot be deregistered\n for (let i = 1; i < this._linkMatchers.length; i++) {\n if (this._linkMatchers[i].id === matcherId) {\n this._linkMatchers.splice(i, 1);\n return true;\n }\n }\n return false;\n }\n\n /**\n * Linkifies a row.\n * @param {number} rowIndex The index of the row to linkify.\n */\n private _linkifyRow(rowIndex: number): void {\n const row = this._rows[rowIndex];\n if (!row) {\n return;\n }\n const text = row.textContent;\n for (let i = 0; i < this._linkMatchers.length; i++) {\n const matcher = this._linkMatchers[i];\n const linkElements = this._doLinkifyRow(row, matcher);\n if (linkElements.length > 0) {\n // Fire validation callback\n if (matcher.validationCallback) {\n for (let j = 0; j < linkElements.length; j++) {\n const element = linkElements[j];\n matcher.validationCallback(element.textContent, element, isValid => {\n if (!isValid) {\n element.classList.add(INVALID_LINK_CLASS);\n }\n });\n }\n }\n // Only allow a single LinkMatcher to trigger on any given row.\n return;\n }\n }\n }\n\n /**\n * Linkifies a row given a specific handler.\n * @param {HTMLElement} row The row to linkify.\n * @param {LinkMatcher} matcher The link matcher for this line.\n * @return The link element(s) that were added.\n */\n private _doLinkifyRow(row: HTMLElement, matcher: LinkMatcher): HTMLElement[] {\n // Iterate over nodes as we want to consider text nodes\n let result = [];\n const isHttpLinkMatcher = matcher.id === HYPERTEXT_LINK_MATCHER_ID;\n const nodes = row.childNodes;\n\n // Find the first match\n let match = row.textContent.match(matcher.regex);\n if (!match || match.length === 0) {\n return result;\n }\n let uri = match[typeof matcher.matchIndex !== 'number' ? 0 : matcher.matchIndex];\n // Set the next searches start index\n let rowStartIndex = match.index + uri.length;\n\n for (let i = 0; i < nodes.length; i++) {\n const node = nodes[i];\n const searchIndex = node.textContent.indexOf(uri);\n if (searchIndex >= 0) {\n const linkElement = this._createAnchorElement(uri, matcher.handler, isHttpLinkMatcher);\n if (node.textContent.length === uri.length) {\n // Matches entire string\n if (node.nodeType === 3 /*Node.TEXT_NODE*/) {\n this._replaceNode(node, linkElement);\n } else {\n const element = (node);\n if (element.nodeName === 'A') {\n // This row has already been linkified\n return result;\n }\n element.innerHTML = '';\n element.appendChild(linkElement);\n }\n } else if (node.childNodes.length > 1) {\n // Matches part of string in an element with multiple child nodes\n for (let j = 0; j < node.childNodes.length; j++) {\n const childNode = node.childNodes[j];\n const childSearchIndex = childNode.textContent.indexOf(uri);\n if (childSearchIndex !== -1) {\n // Match found in currentNode\n this._replaceNodeSubstringWithNode(childNode, linkElement, uri, childSearchIndex);\n // Don't need to count nodesAdded by replacing the node as this\n // is a child node, not a top-level node.\n break;\n }\n }\n } else {\n // Matches part of string in a single text node\n const nodesAdded = this._replaceNodeSubstringWithNode(node, linkElement, uri, searchIndex);\n // No need to consider the new nodes\n i += nodesAdded;\n }\n result.push(linkElement);\n\n // Find the next match\n match = row.textContent.substring(rowStartIndex).match(matcher.regex);\n if (!match || match.length === 0) {\n return result;\n }\n uri = match[typeof matcher.matchIndex !== 'number' ? 0 : matcher.matchIndex];\n rowStartIndex += match.index + uri.length;\n }\n }\n return result;\n }\n\n /**\n * Creates a link anchor element.\n * @param {string} uri The uri of the link.\n * @return {HTMLAnchorElement} The link.\n */\n private _createAnchorElement(uri: string, handler: LinkMatcherHandler, isHypertextLinkHandler: boolean): HTMLAnchorElement {\n const element = this._document.createElement('a');\n element.textContent = uri;\n element.draggable = false;\n if (isHypertextLinkHandler) {\n element.href = uri;\n // Force link on another tab so work is not lost\n element.target = '_blank';\n element.addEventListener('click', (event: MouseEvent) => {\n if (handler) {\n return handler(event, uri);\n }\n });\n } else {\n element.addEventListener('click', (event: MouseEvent) => {\n // Don't execute the handler if the link is flagged as invalid\n if (element.classList.contains(INVALID_LINK_CLASS)) {\n return;\n }\n return handler(event, uri);\n });\n }\n return element;\n }\n\n /**\n * Replace a node with 1 or more other nodes.\n * @param {Node} oldNode The node to replace.\n * @param {Node[]} newNodes The new nodes to insert in order.\n */\n private _replaceNode(oldNode: Node, ...newNodes: Node[]): void {\n const parent = oldNode.parentNode;\n for (let i = 0; i < newNodes.length; i++) {\n parent.insertBefore(newNodes[i], oldNode);\n }\n parent.removeChild(oldNode);\n }\n\n /**\n * Replace a substring within a node with a new node.\n * @param {Node} targetNode The target node; either a text node or a \n * containing a single text node.\n * @param {Node} newNode The new node to insert.\n * @param {string} substring The substring to replace.\n * @param {number} substringIndex The index of the substring within the string.\n * @return The number of nodes to skip when searching for the next uri.\n */\n private _replaceNodeSubstringWithNode(targetNode: Node, newNode: Node, substring: string, substringIndex: number): number {\n // If the targetNode is a non-text node with a single child, make the child\n // the new targetNode.\n if (targetNode.childNodes.length === 1) {\n targetNode = targetNode.childNodes[0];\n }\n\n // The targetNode will be either a text node or a . The text node\n // (targetNode or its only-child) needs to be replaced with newNode plus new\n // text nodes potentially on either side.\n if (targetNode.nodeType !== 3/*Node.TEXT_NODE*/) {\n throw new Error('targetNode must be a text node or only contain a single text node');\n }\n\n const fullText = targetNode.textContent;\n\n if (substringIndex === 0) {\n // Replace with \n const rightText = fullText.substring(substring.length);\n const rightTextNode = this._document.createTextNode(rightText);\n this._replaceNode(targetNode, newNode, rightTextNode);\n return 0;\n }\n\n if (substringIndex === targetNode.textContent.length - substring.length) {\n // Replace with \n const leftText = fullText.substring(0, substringIndex);\n const leftTextNode = this._document.createTextNode(leftText);\n this._replaceNode(targetNode, leftTextNode, newNode);\n return 0;\n }\n\n // Replace with \n const leftText = fullText.substring(0, substringIndex);\n const leftTextNode = this._document.createTextNode(leftText);\n const rightText = fullText.substring(substringIndex + substring.length);\n const rightTextNode = this._document.createTextNode(rightText);\n this._replaceNode(targetNode, leftTextNode, newNode, rightTextNode);\n return 1;\n }\n}\n","/**\n * @license MIT\n */\n\nimport { IInputHandler, ITerminal } from './Interfaces';\nimport { C0 } from './EscapeSequences';\nimport { DEFAULT_CHARSET } from './Charsets';\n\n/**\n * The terminal's standard implementation of IInputHandler, this handles all\n * input from the Parser.\n *\n * Refer to http://invisible-island.net/xterm/ctlseqs/ctlseqs.html to understand\n * each function's header comment.\n */\nexport class InputHandler implements IInputHandler {\n // TODO: We want to type _terminal when it's pulled into TS\n constructor(private _terminal: any) { }\n\n public addChar(char: string, code: number): void {\n if (char >= ' ') {\n // calculate print space\n // expensive call, therefore we save width in line buffer\n const ch_width = wcwidth(code);\n\n if (this._terminal.charset && this._terminal.charset[char]) {\n char = this._terminal.charset[char];\n }\n\n let row = this._terminal.buffer.y + this._terminal.buffer.ybase;\n\n // insert combining char in last cell\n // FIXME: needs handling after cursor jumps\n if (!ch_width && this._terminal.buffer.x) {\n // dont overflow left\n if (this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1]) {\n if (!this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][2]) {\n\n // found empty cell after fullwidth, need to go 2 cells back\n if (this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 2])\n this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 2][1] += char;\n\n } else {\n this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][1] += char;\n }\n this._terminal.updateRange(this._terminal.buffer.y);\n }\n return;\n }\n\n // goto next line if ch would overflow\n // TODO: needs a global min terminal width of 2\n if (this._terminal.buffer.x + ch_width - 1 >= this._terminal.cols) {\n // autowrap - DECAWM\n if (this._terminal.wraparoundMode) {\n this._terminal.buffer.x = 0;\n this._terminal.buffer.y++;\n if (this._terminal.buffer.y > this._terminal.buffer.scrollBottom) {\n this._terminal.buffer.y--;\n this._terminal.scroll(true);\n } else {\n // The line already exists (eg. the initial viewport), mark it as a\n // wrapped line\n this._terminal.buffer.lines.get(this._terminal.buffer.y).isWrapped = true;\n }\n } else {\n if (ch_width === 2) // FIXME: check for xterm behavior\n return;\n }\n }\n row = this._terminal.buffer.y + this._terminal.buffer.ybase;\n\n // insert mode: move characters to right\n if (this._terminal.insertMode) {\n // do this twice for a fullwidth char\n for (let moves = 0; moves < ch_width; ++moves) {\n // remove last cell, if it's width is 0\n // we have to adjust the second last cell as well\n const removed = this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).pop();\n if (removed[2] === 0\n && this._terminal.buffer.lines.get(row)[this._terminal.cols - 2]\n && this._terminal.buffer.lines.get(row)[this._terminal.cols - 2][2] === 2) {\n this._terminal.buffer.lines.get(row)[this._terminal.cols - 2] = [this._terminal.curAttr, ' ', 1];\n }\n\n // insert empty cell at cursor\n this._terminal.buffer.lines.get(row).splice(this._terminal.buffer.x, 0, [this._terminal.curAttr, ' ', 1]);\n }\n }\n\n this._terminal.buffer.lines.get(row)[this._terminal.buffer.x] = [this._terminal.curAttr, char, ch_width];\n this._terminal.buffer.x++;\n this._terminal.updateRange(this._terminal.buffer.y);\n\n // fullwidth char - set next cell width to zero and advance cursor\n if (ch_width === 2) {\n this._terminal.buffer.lines.get(row)[this._terminal.buffer.x] = [this._terminal.curAttr, '', 0];\n this._terminal.buffer.x++;\n }\n }\n }\n\n /**\n * BEL\n * Bell (Ctrl-G).\n */\n public bell(): void {\n if (!this._terminal.visualBell) {\n return;\n }\n this._terminal.element.style.borderColor = 'white';\n setTimeout(() => this._terminal.element.style.borderColor = '', 10);\n if (this._terminal.popOnBell) {\n this._terminal.focus();\n }\n }\n\n /**\n * LF\n * Line Feed or New Line (NL). (LF is Ctrl-J).\n */\n public lineFeed(): void {\n if (this._terminal.convertEol) {\n this._terminal.buffer.x = 0;\n }\n this._terminal.buffer.y++;\n if (this._terminal.buffer.y > this._terminal.buffer.scrollBottom) {\n this._terminal.buffer.y--;\n this._terminal.scroll();\n }\n // If the end of the line is hit, prevent this action from wrapping around to the next line.\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x--;\n }\n /**\n * This event is emitted whenever the terminal outputs a LF or NL.\n *\n * @event lineFeed\n */\n this._terminal.emit('lineFeed');\n }\n\n /**\n * CR\n * Carriage Return (Ctrl-M).\n */\n public carriageReturn(): void {\n this._terminal.buffer.x = 0;\n }\n\n /**\n * BS\n * Backspace (Ctrl-H).\n */\n public backspace(): void {\n if (this._terminal.buffer.x > 0) {\n this._terminal.buffer.x--;\n }\n }\n\n /**\n * TAB\n * Horizontal Tab (HT) (Ctrl-I).\n */\n public tab(): void {\n this._terminal.buffer.x = this._terminal.nextStop();\n }\n\n /**\n * SO\n * Shift Out (Ctrl-N) -> Switch to Alternate Character Set. This invokes the\n * G1 character set.\n */\n public shiftOut(): void {\n this._terminal.setgLevel(1);\n }\n\n /**\n * SI\n * Shift In (Ctrl-O) -> Switch to Standard Character Set. This invokes the G0\n * character set (the default).\n */\n public shiftIn(): void {\n this._terminal.setgLevel(0);\n }\n\n /**\n * CSI Ps @\n * Insert Ps (Blank) Character(s) (default = 1) (ICH).\n */\n public insertChars(params: number[]): void {\n let param, row, j, ch;\n\n param = params[0];\n if (param < 1) param = 1;\n\n row = this._terminal.buffer.y + this._terminal.buffer.ybase;\n j = this._terminal.buffer.x;\n ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm\n\n while (param-- && j < this._terminal.cols) {\n this._terminal.buffer.lines.get(row).splice(j++, 0, ch);\n this._terminal.buffer.lines.get(row).pop();\n }\n }\n\n /**\n * CSI Ps A\n * Cursor Up Ps Times (default = 1) (CUU).\n */\n public cursorUp(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.y -= param;\n if (this._terminal.buffer.y < 0) {\n this._terminal.buffer.y = 0;\n }\n }\n\n /**\n * CSI Ps B\n * Cursor Down Ps Times (default = 1) (CUD).\n */\n public cursorDown(params: number[]) {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.y += param;\n if (this._terminal.buffer.y >= this._terminal.rows) {\n this._terminal.buffer.y = this._terminal.rows - 1;\n }\n // If the end of the line is hit, prevent this action from wrapping around to the next line.\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x--;\n }\n }\n\n /**\n * CSI Ps C\n * Cursor Forward Ps Times (default = 1) (CUF).\n */\n public cursorForward(params: number[]) {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.x += param;\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x = this._terminal.cols - 1;\n }\n }\n\n /**\n * CSI Ps D\n * Cursor Backward Ps Times (default = 1) (CUB).\n */\n public cursorBackward(params: number[]) {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n // If the end of the line is hit, prevent this action from wrapping around to the next line.\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x--;\n }\n this._terminal.buffer.x -= param;\n if (this._terminal.buffer.x < 0) {\n this._terminal.buffer.x = 0;\n }\n }\n\n /**\n * CSI Ps E\n * Cursor Next Line Ps Times (default = 1) (CNL).\n * same as CSI Ps B ?\n */\n public cursorNextLine(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.y += param;\n if (this._terminal.buffer.y >= this._terminal.rows) {\n this._terminal.buffer.y = this._terminal.rows - 1;\n }\n this._terminal.buffer.x = 0;\n }\n\n\n /**\n * CSI Ps F\n * Cursor Preceding Line Ps Times (default = 1) (CNL).\n * reuse CSI Ps A ?\n */\n public cursorPrecedingLine(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.y -= param;\n if (this._terminal.buffer.y < 0) {\n this._terminal.buffer.y = 0;\n }\n this._terminal.buffer.x = 0;\n }\n\n\n /**\n * CSI Ps G\n * Cursor Character Absolute [column] (default = [row,1]) (CHA).\n */\n public cursorCharAbsolute(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.x = param - 1;\n }\n\n /**\n * CSI Ps ; Ps H\n * Cursor Position [row;column] (default = [1,1]) (CUP).\n */\n public cursorPosition(params: number[]): void {\n let row, col;\n\n row = params[0] - 1;\n\n if (params.length >= 2) {\n col = params[1] - 1;\n } else {\n col = 0;\n }\n\n if (row < 0) {\n row = 0;\n } else if (row >= this._terminal.rows) {\n row = this._terminal.rows - 1;\n }\n\n if (col < 0) {\n col = 0;\n } else if (col >= this._terminal.cols) {\n col = this._terminal.cols - 1;\n }\n\n this._terminal.buffer.x = col;\n this._terminal.buffer.y = row;\n }\n\n /**\n * CSI Ps I\n * Cursor Forward Tabulation Ps tab stops (default = 1) (CHT).\n */\n public cursorForwardTab(params: number[]): void {\n let param = params[0] || 1;\n while (param--) {\n this._terminal.buffer.x = this._terminal.nextStop();\n }\n }\n\n /**\n * CSI Ps J Erase in Display (ED).\n * Ps = 0 -> Erase Below (default).\n * Ps = 1 -> Erase Above.\n * Ps = 2 -> Erase All.\n * Ps = 3 -> Erase Saved Lines (xterm).\n * CSI ? Ps J\n * Erase in Display (DECSED).\n * Ps = 0 -> Selective Erase Below (default).\n * Ps = 1 -> Selective Erase Above.\n * Ps = 2 -> Selective Erase All.\n */\n public eraseInDisplay(params: number[]): void {\n let j;\n switch (params[0]) {\n case 0:\n this._terminal.eraseRight(this._terminal.buffer.x, this._terminal.buffer.y);\n j = this._terminal.buffer.y + 1;\n for (; j < this._terminal.rows; j++) {\n this._terminal.eraseLine(j);\n }\n break;\n case 1:\n this._terminal.eraseLeft(this._terminal.buffer.x, this._terminal.buffer.y);\n j = this._terminal.buffer.y;\n while (j--) {\n this._terminal.eraseLine(j);\n }\n break;\n case 2:\n j = this._terminal.rows;\n while (j--) this._terminal.eraseLine(j);\n break;\n case 3:\n // Clear scrollback (everything not in viewport)\n const scrollBackSize = this._terminal.buffer.lines.length - this._terminal.rows;\n if (scrollBackSize > 0) {\n this._terminal.buffer.lines.trimStart(scrollBackSize);\n this._terminal.buffer.ybase = Math.max(this._terminal.buffer.ybase - scrollBackSize, 0);\n this._terminal.buffer.ydisp = Math.max(this._terminal.buffer.ydisp - scrollBackSize, 0);\n // Force a scroll event to refresh viewport\n this._terminal.emit('scroll', 0);\n }\n break;\n }\n }\n\n /**\n * CSI Ps K Erase in Line (EL).\n * Ps = 0 -> Erase to Right (default).\n * Ps = 1 -> Erase to Left.\n * Ps = 2 -> Erase All.\n * CSI ? Ps K\n * Erase in Line (DECSEL).\n * Ps = 0 -> Selective Erase to Right (default).\n * Ps = 1 -> Selective Erase to Left.\n * Ps = 2 -> Selective Erase All.\n */\n public eraseInLine(params: number[]): void {\n switch (params[0]) {\n case 0:\n this._terminal.eraseRight(this._terminal.buffer.x, this._terminal.buffer.y);\n break;\n case 1:\n this._terminal.eraseLeft(this._terminal.buffer.x, this._terminal.buffer.y);\n break;\n case 2:\n this._terminal.eraseLine(this._terminal.buffer.y);\n break;\n }\n }\n\n /**\n * CSI Ps L\n * Insert Ps Line(s) (default = 1) (IL).\n */\n public insertLines(params: number[]): void {\n let param, row, j;\n\n param = params[0];\n if (param < 1) {\n param = 1;\n }\n row = this._terminal.buffer.y + this._terminal.buffer.ybase;\n\n j = this._terminal.rows - 1 - this._terminal.buffer.scrollBottom;\n j = this._terminal.rows - 1 + this._terminal.buffer.ybase - j + 1;\n\n while (param--) {\n if (this._terminal.buffer.lines.length === this._terminal.buffer.lines.maxLength) {\n // Trim the start of lines to make room for the new line\n this._terminal.buffer.lines.trimStart(1);\n this._terminal.buffer.ybase--;\n this._terminal.buffer.ydisp--;\n row--;\n j--;\n }\n // test: echo -e '\\e[44m\\e[1L\\e[0m'\n // blankLine(true) - xterm/linux behavior\n this._terminal.buffer.lines.splice(row, 0, this._terminal.blankLine(true));\n this._terminal.buffer.lines.splice(j, 1);\n }\n\n // this.maxRange();\n this._terminal.updateRange(this._terminal.buffer.y);\n this._terminal.updateRange(this._terminal.buffer.scrollBottom);\n }\n\n /**\n * CSI Ps M\n * Delete Ps Line(s) (default = 1) (DL).\n */\n public deleteLines(params: number[]): void {\n let param, row, j;\n\n param = params[0];\n if (param < 1) {\n param = 1;\n }\n row = this._terminal.buffer.y + this._terminal.buffer.ybase;\n\n j = this._terminal.rows - 1 - this._terminal.buffer.scrollBottom;\n j = this._terminal.rows - 1 + this._terminal.buffer.ybase - j;\n\n while (param--) {\n if (this._terminal.buffer.lines.length === this._terminal.buffer.lines.maxLength) {\n // Trim the start of lines to make room for the new line\n this._terminal.buffer.lines.trimStart(1);\n this._terminal.buffer.ybase -= 1;\n this._terminal.buffer.ydisp -= 1;\n }\n // test: echo -e '\\e[44m\\e[1M\\e[0m'\n // blankLine(true) - xterm/linux behavior\n this._terminal.buffer.lines.splice(j + 1, 0, this._terminal.blankLine(true));\n this._terminal.buffer.lines.splice(row, 1);\n }\n\n // this.maxRange();\n this._terminal.updateRange(this._terminal.buffer.y);\n this._terminal.updateRange(this._terminal.buffer.scrollBottom);\n }\n\n /**\n * CSI Ps P\n * Delete Ps Character(s) (default = 1) (DCH).\n */\n public deleteChars(params: number[]): void {\n let param, row, ch;\n\n param = params[0];\n if (param < 1) {\n param = 1;\n }\n\n row = this._terminal.buffer.y + this._terminal.buffer.ybase;\n ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm\n\n while (param--) {\n this._terminal.buffer.lines.get(row).splice(this._terminal.buffer.x, 1);\n this._terminal.buffer.lines.get(row).push(ch);\n }\n }\n\n /**\n * CSI Ps S Scroll up Ps lines (default = 1) (SU).\n */\n public scrollUp(params: number[]): void {\n let param = params[0] || 1;\n while (param--) {\n this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollTop, 1);\n this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollBottom, 0, this._terminal.blankLine());\n }\n // this.maxRange();\n this._terminal.updateRange(this._terminal.buffer.scrollTop);\n this._terminal.updateRange(this._terminal.buffer.scrollBottom);\n }\n\n /**\n * CSI Ps T Scroll down Ps lines (default = 1) (SD).\n */\n public scrollDown(params: number[]): void {\n let param = params[0] || 1;\n while (param--) {\n this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollBottom, 1);\n this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollTop, 0, this._terminal.blankLine());\n }\n // this.maxRange();\n this._terminal.updateRange(this._terminal.buffer.scrollTop);\n this._terminal.updateRange(this._terminal.buffer.scrollBottom);\n }\n\n /**\n * CSI Ps X\n * Erase Ps Character(s) (default = 1) (ECH).\n */\n public eraseChars(params: number[]): void {\n let param, row, j, ch;\n\n param = params[0];\n if (param < 1) {\n param = 1;\n }\n\n row = this._terminal.buffer.y + this._terminal.buffer.ybase;\n j = this._terminal.buffer.x;\n ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm\n\n while (param-- && j < this._terminal.cols) {\n this._terminal.buffer.lines.get(row)[j++] = ch;\n }\n }\n\n /**\n * CSI Ps Z Cursor Backward Tabulation Ps tab stops (default = 1) (CBT).\n */\n public cursorBackwardTab(params: number[]): void {\n let param = params[0] || 1;\n while (param--) {\n this._terminal.buffer.x = this._terminal.prevStop();\n }\n }\n\n /**\n * CSI Pm ` Character Position Absolute\n * [column] (default = [row,1]) (HPA).\n */\n public charPosAbsolute(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.x = param - 1;\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x = this._terminal.cols - 1;\n }\n }\n\n /**\n * CSI Pm a Character Position Relative\n * [columns] (default = [row,col+1]) (HPR)\n * reuse CSI Ps C ?\n */\n public HPositionRelative(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.x += param;\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x = this._terminal.cols - 1;\n }\n }\n\n /**\n * CSI Ps b Repeat the preceding graphic character Ps times (REP).\n */\n public repeatPrecedingCharacter(params: number[]): void {\n let param = params[0] || 1\n , line = this._terminal.buffer.lines.get(this._terminal.buffer.ybase + this._terminal.buffer.y)\n , ch = line[this._terminal.buffer.x - 1] || [this._terminal.defAttr, ' ', 1];\n\n while (param--) {\n line[this._terminal.buffer.x++] = ch;\n }\n }\n\n /**\n * CSI Ps c Send Device Attributes (Primary DA).\n * Ps = 0 or omitted -> request attributes from terminal. The\n * response depends on the decTerminalID resource setting.\n * -> CSI ? 1 ; 2 c (``VT100 with Advanced Video Option'')\n * -> CSI ? 1 ; 0 c (``VT101 with No Options'')\n * -> CSI ? 6 c (``VT102'')\n * -> CSI ? 6 0 ; 1 ; 2 ; 6 ; 8 ; 9 ; 1 5 ; c (``VT220'')\n * The VT100-style response parameters do not mean anything by\n * themselves. VT220 parameters do, telling the host what fea-\n * tures the terminal supports:\n * Ps = 1 -> 132-columns.\n * Ps = 2 -> Printer.\n * Ps = 6 -> Selective erase.\n * Ps = 8 -> User-defined keys.\n * Ps = 9 -> National replacement character sets.\n * Ps = 1 5 -> Technical characters.\n * Ps = 2 2 -> ANSI color, e.g., VT525.\n * Ps = 2 9 -> ANSI text locator (i.e., DEC Locator mode).\n * CSI > Ps c\n * Send Device Attributes (Secondary DA).\n * Ps = 0 or omitted -> request the terminal's identification\n * code. The response depends on the decTerminalID resource set-\n * ting. It should apply only to VT220 and up, but xterm extends\n * this to VT100.\n * -> CSI > Pp ; Pv ; Pc c\n * where Pp denotes the terminal type\n * Pp = 0 -> ``VT100''.\n * Pp = 1 -> ``VT220''.\n * and Pv is the firmware version (for xterm, this was originally\n * the XFree86 patch number, starting with 95). In a DEC termi-\n * nal, Pc indicates the ROM cartridge registration number and is\n * always zero.\n * More information:\n * xterm/charproc.c - line 2012, for more information.\n * vim responds with ^[[?0c or ^[[?1c after the terminal's response (?)\n */\n public sendDeviceAttributes(params: number[]): void {\n if (params[0] > 0) {\n return;\n }\n\n if (!this._terminal.prefix) {\n if (this._terminal.is('xterm') || this._terminal.is('rxvt-unicode') || this._terminal.is('screen')) {\n this._terminal.send(C0.ESC + '[?1;2c');\n } else if (this._terminal.is('linux')) {\n this._terminal.send(C0.ESC + '[?6c');\n }\n } else if (this._terminal.prefix === '>') {\n // xterm and urxvt\n // seem to spit this\n // out around ~370 times (?).\n if (this._terminal.is('xterm')) {\n this._terminal.send(C0.ESC + '[>0;276;0c');\n } else if (this._terminal.is('rxvt-unicode')) {\n this._terminal.send(C0.ESC + '[>85;95;0c');\n } else if (this._terminal.is('linux')) {\n // not supported by linux console.\n // linux console echoes parameters.\n this._terminal.send(params[0] + 'c');\n } else if (this._terminal.is('screen')) {\n this._terminal.send(C0.ESC + '[>83;40003;0c');\n }\n }\n }\n\n /**\n * CSI Pm d Vertical Position Absolute (VPA)\n * [row] (default = [1,column])\n */\n public linePosAbsolute(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.y = param - 1;\n if (this._terminal.buffer.y >= this._terminal.rows) {\n this._terminal.buffer.y = this._terminal.rows - 1;\n }\n }\n\n /**\n * CSI Pm e Vertical Position Relative (VPR)\n * [rows] (default = [row+1,column])\n * reuse CSI Ps B ?\n */\n public VPositionRelative(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.y += param;\n if (this._terminal.buffer.y >= this._terminal.rows) {\n this._terminal.buffer.y = this._terminal.rows - 1;\n }\n // If the end of the line is hit, prevent this action from wrapping around to the next line.\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x--;\n }\n }\n\n /**\n * CSI Ps ; Ps f\n * Horizontal and Vertical Position [row;column] (default =\n * [1,1]) (HVP).\n */\n public HVPosition(params: number[]): void {\n if (params[0] < 1) params[0] = 1;\n if (params[1] < 1) params[1] = 1;\n\n this._terminal.buffer.y = params[0] - 1;\n if (this._terminal.buffer.y >= this._terminal.rows) {\n this._terminal.buffer.y = this._terminal.rows - 1;\n }\n\n this._terminal.buffer.x = params[1] - 1;\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x = this._terminal.cols - 1;\n }\n }\n\n /**\n * CSI Ps g Tab Clear (TBC).\n * Ps = 0 -> Clear Current Column (default).\n * Ps = 3 -> Clear All.\n * Potentially:\n * Ps = 2 -> Clear Stops on Line.\n * http://vt100.net/annarbor/aaa-ug/section6.html\n */\n public tabClear(params: number[]): void {\n let param = params[0];\n if (param <= 0) {\n delete this._terminal.buffer.tabs[this._terminal.buffer.x];\n } else if (param === 3) {\n this._terminal.buffer.tabs = {};\n }\n }\n\n /**\n * CSI Pm h Set Mode (SM).\n * Ps = 2 -> Keyboard Action Mode (AM).\n * Ps = 4 -> Insert Mode (IRM).\n * Ps = 1 2 -> Send/receive (SRM).\n * Ps = 2 0 -> Automatic Newline (LNM).\n * CSI ? Pm h\n * DEC Private Mode Set (DECSET).\n * Ps = 1 -> Application Cursor Keys (DECCKM).\n * Ps = 2 -> Designate USASCII for character sets G0-G3\n * (DECANM), and set VT100 mode.\n * Ps = 3 -> 132 Column Mode (DECCOLM).\n * Ps = 4 -> Smooth (Slow) Scroll (DECSCLM).\n * Ps = 5 -> Reverse Video (DECSCNM).\n * Ps = 6 -> Origin Mode (DECOM).\n * Ps = 7 -> Wraparound Mode (DECAWM).\n * Ps = 8 -> Auto-repeat Keys (DECARM).\n * Ps = 9 -> Send Mouse X & Y on button press. See the sec-\n * tion Mouse Tracking.\n * Ps = 1 0 -> Show toolbar (rxvt).\n * Ps = 1 2 -> Start Blinking Cursor (att610).\n * Ps = 1 8 -> Print form feed (DECPFF).\n * Ps = 1 9 -> Set print extent to full screen (DECPEX).\n * Ps = 2 5 -> Show Cursor (DECTCEM).\n * Ps = 3 0 -> Show scrollbar (rxvt).\n * Ps = 3 5 -> Enable font-shifting functions (rxvt).\n * Ps = 3 8 -> Enter Tektronix Mode (DECTEK).\n * Ps = 4 0 -> Allow 80 -> 132 Mode.\n * Ps = 4 1 -> more(1) fix (see curses resource).\n * Ps = 4 2 -> Enable Nation Replacement Character sets (DECN-\n * RCM).\n * Ps = 4 4 -> Turn On Margin Bell.\n * Ps = 4 5 -> Reverse-wraparound Mode.\n * Ps = 4 6 -> Start Logging. This is normally disabled by a\n * compile-time option.\n * Ps = 4 7 -> Use Alternate Screen Buffer. (This may be dis-\n * abled by the titeInhibit resource).\n * Ps = 6 6 -> Application keypad (DECNKM).\n * Ps = 6 7 -> Backarrow key sends backspace (DECBKM).\n * Ps = 1 0 0 0 -> Send Mouse X & Y on button press and\n * release. See the section Mouse Tracking.\n * Ps = 1 0 0 1 -> Use Hilite Mouse Tracking.\n * Ps = 1 0 0 2 -> Use Cell Motion Mouse Tracking.\n * Ps = 1 0 0 3 -> Use All Motion Mouse Tracking.\n * Ps = 1 0 0 4 -> Send FocusIn/FocusOut events.\n * Ps = 1 0 0 5 -> Enable Extended Mouse Mode.\n * Ps = 1 0 1 0 -> Scroll to bottom on tty output (rxvt).\n * Ps = 1 0 1 1 -> Scroll to bottom on key press (rxvt).\n * Ps = 1 0 3 4 -> Interpret \"meta\" key, sets eighth bit.\n * (enables the eightBitInput resource).\n * Ps = 1 0 3 5 -> Enable special modifiers for Alt and Num-\n * Lock keys. (This enables the numLock resource).\n * Ps = 1 0 3 6 -> Send ESC when Meta modifies a key. (This\n * enables the metaSendsEscape resource).\n * Ps = 1 0 3 7 -> Send DEL from the editing-keypad Delete\n * key.\n * Ps = 1 0 3 9 -> Send ESC when Alt modifies a key. (This\n * enables the altSendsEscape resource).\n * Ps = 1 0 4 0 -> Keep selection even if not highlighted.\n * (This enables the keepSelection resource).\n * Ps = 1 0 4 1 -> Use the CLIPBOARD selection. (This enables\n * the selectToClipboard resource).\n * Ps = 1 0 4 2 -> Enable Urgency window manager hint when\n * Control-G is received. (This enables the bellIsUrgent\n * resource).\n * Ps = 1 0 4 3 -> Enable raising of the window when Control-G\n * is received. (enables the popOnBell resource).\n * Ps = 1 0 4 7 -> Use Alternate Screen Buffer. (This may be\n * disabled by the titeInhibit resource).\n * Ps = 1 0 4 8 -> Save cursor as in DECSC. (This may be dis-\n * abled by the titeInhibit resource).\n * Ps = 1 0 4 9 -> Save cursor as in DECSC and use Alternate\n * Screen Buffer, clearing it first. (This may be disabled by\n * the titeInhibit resource). This combines the effects of the 1\n * 0 4 7 and 1 0 4 8 modes. Use this with terminfo-based\n * applications rather than the 4 7 mode.\n * Ps = 1 0 5 0 -> Set terminfo/termcap function-key mode.\n * Ps = 1 0 5 1 -> Set Sun function-key mode.\n * Ps = 1 0 5 2 -> Set HP function-key mode.\n * Ps = 1 0 5 3 -> Set SCO function-key mode.\n * Ps = 1 0 6 0 -> Set legacy keyboard emulation (X11R6).\n * Ps = 1 0 6 1 -> Set VT220 keyboard emulation.\n * Ps = 2 0 0 4 -> Set bracketed paste mode.\n * Modes:\n * http: *vt100.net/docs/vt220-rm/chapter4.html\n */\n public setMode(params: number[]): void {\n if (params.length > 1) {\n for (let i = 0; i < params.length; i++) {\n this.setMode([params[i]]);\n }\n\n return;\n }\n\n if (!this._terminal.prefix) {\n switch (params[0]) {\n case 4:\n this._terminal.insertMode = true;\n break;\n case 20:\n // this._terminal.convertEol = true;\n break;\n }\n } else if (this._terminal.prefix === '?') {\n switch (params[0]) {\n case 1:\n this._terminal.applicationCursor = true;\n break;\n case 2:\n this._terminal.setgCharset(0, DEFAULT_CHARSET);\n this._terminal.setgCharset(1, DEFAULT_CHARSET);\n this._terminal.setgCharset(2, DEFAULT_CHARSET);\n this._terminal.setgCharset(3, DEFAULT_CHARSET);\n // set VT100 mode here\n break;\n case 3: // 132 col mode\n this._terminal.savedCols = this._terminal.cols;\n this._terminal.resize(132, this._terminal.rows);\n break;\n case 6:\n this._terminal.originMode = true;\n break;\n case 7:\n this._terminal.wraparoundMode = true;\n break;\n case 12:\n // this.cursorBlink = true;\n break;\n case 66:\n this._terminal.log('Serial port requested application keypad.');\n this._terminal.applicationKeypad = true;\n this._terminal.viewport.syncScrollArea();\n break;\n case 9: // X10 Mouse\n // no release, no motion, no wheel, no modifiers.\n case 1000: // vt200 mouse\n // no motion.\n // no modifiers, except control on the wheel.\n case 1002: // button event mouse\n case 1003: // any event mouse\n // any event - sends motion events,\n // even if there is no button held down.\n\n // TODO: Why are params[0] compares nested within a switch for params[0]?\n\n this._terminal.x10Mouse = params[0] === 9;\n this._terminal.vt200Mouse = params[0] === 1000;\n this._terminal.normalMouse = params[0] > 1000;\n this._terminal.mouseEvents = true;\n this._terminal.element.classList.add('enable-mouse-events');\n this._terminal.selectionManager.disable();\n this._terminal.log('Binding to mouse events.');\n break;\n case 1004: // send focusin/focusout events\n // focusin: ^[[I\n // focusout: ^[[O\n this._terminal.sendFocus = true;\n break;\n case 1005: // utf8 ext mode mouse\n this._terminal.utfMouse = true;\n // for wide terminals\n // simply encodes large values as utf8 characters\n break;\n case 1006: // sgr ext mode mouse\n this._terminal.sgrMouse = true;\n // for wide terminals\n // does not add 32 to fields\n // press: ^[[ Keyboard Action Mode (AM).\n * Ps = 4 -> Replace Mode (IRM).\n * Ps = 1 2 -> Send/receive (SRM).\n * Ps = 2 0 -> Normal Linefeed (LNM).\n * CSI ? Pm l\n * DEC Private Mode Reset (DECRST).\n * Ps = 1 -> Normal Cursor Keys (DECCKM).\n * Ps = 2 -> Designate VT52 mode (DECANM).\n * Ps = 3 -> 80 Column Mode (DECCOLM).\n * Ps = 4 -> Jump (Fast) Scroll (DECSCLM).\n * Ps = 5 -> Normal Video (DECSCNM).\n * Ps = 6 -> Normal Cursor Mode (DECOM).\n * Ps = 7 -> No Wraparound Mode (DECAWM).\n * Ps = 8 -> No Auto-repeat Keys (DECARM).\n * Ps = 9 -> Don't send Mouse X & Y on button press.\n * Ps = 1 0 -> Hide toolbar (rxvt).\n * Ps = 1 2 -> Stop Blinking Cursor (att610).\n * Ps = 1 8 -> Don't print form feed (DECPFF).\n * Ps = 1 9 -> Limit print to scrolling region (DECPEX).\n * Ps = 2 5 -> Hide Cursor (DECTCEM).\n * Ps = 3 0 -> Don't show scrollbar (rxvt).\n * Ps = 3 5 -> Disable font-shifting functions (rxvt).\n * Ps = 4 0 -> Disallow 80 -> 132 Mode.\n * Ps = 4 1 -> No more(1) fix (see curses resource).\n * Ps = 4 2 -> Disable Nation Replacement Character sets (DEC-\n * NRCM).\n * Ps = 4 4 -> Turn Off Margin Bell.\n * Ps = 4 5 -> No Reverse-wraparound Mode.\n * Ps = 4 6 -> Stop Logging. (This is normally disabled by a\n * compile-time option).\n * Ps = 4 7 -> Use Normal Screen Buffer.\n * Ps = 6 6 -> Numeric keypad (DECNKM).\n * Ps = 6 7 -> Backarrow key sends delete (DECBKM).\n * Ps = 1 0 0 0 -> Don't send Mouse X & Y on button press and\n * release. See the section Mouse Tracking.\n * Ps = 1 0 0 1 -> Don't use Hilite Mouse Tracking.\n * Ps = 1 0 0 2 -> Don't use Cell Motion Mouse Tracking.\n * Ps = 1 0 0 3 -> Don't use All Motion Mouse Tracking.\n * Ps = 1 0 0 4 -> Don't send FocusIn/FocusOut events.\n * Ps = 1 0 0 5 -> Disable Extended Mouse Mode.\n * Ps = 1 0 1 0 -> Don't scroll to bottom on tty output\n * (rxvt).\n * Ps = 1 0 1 1 -> Don't scroll to bottom on key press (rxvt).\n * Ps = 1 0 3 4 -> Don't interpret \"meta\" key. (This disables\n * the eightBitInput resource).\n * Ps = 1 0 3 5 -> Disable special modifiers for Alt and Num-\n * Lock keys. (This disables the numLock resource).\n * Ps = 1 0 3 6 -> Don't send ESC when Meta modifies a key.\n * (This disables the metaSendsEscape resource).\n * Ps = 1 0 3 7 -> Send VT220 Remove from the editing-keypad\n * Delete key.\n * Ps = 1 0 3 9 -> Don't send ESC when Alt modifies a key.\n * (This disables the altSendsEscape resource).\n * Ps = 1 0 4 0 -> Do not keep selection when not highlighted.\n * (This disables the keepSelection resource).\n * Ps = 1 0 4 1 -> Use the PRIMARY selection. (This disables\n * the selectToClipboard resource).\n * Ps = 1 0 4 2 -> Disable Urgency window manager hint when\n * Control-G is received. (This disables the bellIsUrgent\n * resource).\n * Ps = 1 0 4 3 -> Disable raising of the window when Control-\n * G is received. (This disables the popOnBell resource).\n * Ps = 1 0 4 7 -> Use Normal Screen Buffer, clearing screen\n * first if in the Alternate Screen. (This may be disabled by\n * the titeInhibit resource).\n * Ps = 1 0 4 8 -> Restore cursor as in DECRC. (This may be\n * disabled by the titeInhibit resource).\n * Ps = 1 0 4 9 -> Use Normal Screen Buffer and restore cursor\n * as in DECRC. (This may be disabled by the titeInhibit\n * resource). This combines the effects of the 1 0 4 7 and 1 0\n * 4 8 modes. Use this with terminfo-based applications rather\n * than the 4 7 mode.\n * Ps = 1 0 5 0 -> Reset terminfo/termcap function-key mode.\n * Ps = 1 0 5 1 -> Reset Sun function-key mode.\n * Ps = 1 0 5 2 -> Reset HP function-key mode.\n * Ps = 1 0 5 3 -> Reset SCO function-key mode.\n * Ps = 1 0 6 0 -> Reset legacy keyboard emulation (X11R6).\n * Ps = 1 0 6 1 -> Reset keyboard emulation to Sun/PC style.\n * Ps = 2 0 0 4 -> Reset bracketed paste mode.\n */\n public resetMode(params: number[]): void {\n if (params.length > 1) {\n for (let i = 0; i < params.length; i++) {\n this.resetMode([params[i]]);\n }\n\n return;\n }\n\n if (!this._terminal.prefix) {\n switch (params[0]) {\n case 4:\n this._terminal.insertMode = false;\n break;\n case 20:\n // this._terminal.convertEol = false;\n break;\n }\n } else if (this._terminal.prefix === '?') {\n switch (params[0]) {\n case 1:\n this._terminal.applicationCursor = false;\n break;\n case 3:\n if (this._terminal.cols === 132 && this._terminal.savedCols) {\n this._terminal.resize(this._terminal.savedCols, this._terminal.rows);\n }\n delete this._terminal.savedCols;\n break;\n case 6:\n this._terminal.originMode = false;\n break;\n case 7:\n this._terminal.wraparoundMode = false;\n break;\n case 12:\n // this.cursorBlink = false;\n break;\n case 66:\n this._terminal.log('Switching back to normal keypad.');\n this._terminal.applicationKeypad = false;\n this._terminal.viewport.syncScrollArea();\n break;\n case 9: // X10 Mouse\n case 1000: // vt200 mouse\n case 1002: // button event mouse\n case 1003: // any event mouse\n this._terminal.x10Mouse = false;\n this._terminal.vt200Mouse = false;\n this._terminal.normalMouse = false;\n this._terminal.mouseEvents = false;\n this._terminal.element.classList.remove('enable-mouse-events');\n this._terminal.selectionManager.enable();\n break;\n case 1004: // send focusin/focusout events\n this._terminal.sendFocus = false;\n break;\n case 1005: // utf8 ext mode mouse\n this._terminal.utfMouse = false;\n break;\n case 1006: // sgr ext mode mouse\n this._terminal.sgrMouse = false;\n break;\n case 1015: // urxvt ext mode mouse\n this._terminal.urxvtMouse = false;\n break;\n case 25: // hide cursor\n this._terminal.cursorHidden = true;\n break;\n case 1049: // alt screen buffer cursor\n // FALL-THROUGH\n case 47: // normal screen buffer\n case 1047: // normal screen buffer - clearing it first\n // Ensure the selection manager has the correct buffer\n this._terminal.buffers.activateNormalBuffer();\n // TODO: Not sure if we need to save/restore after switching the buffer\n // if (params[0] === 1049) {\n // this.restoreCursor(params);\n // }\n this._terminal.selectionManager.setBuffer(this._terminal.buffer.lines);\n this._terminal.refresh(0, this._terminal.rows - 1);\n this._terminal.viewport.syncScrollArea();\n this._terminal.showCursor();\n break;\n }\n }\n }\n\n /**\n * CSI Pm m Character Attributes (SGR).\n * Ps = 0 -> Normal (default).\n * Ps = 1 -> Bold.\n * Ps = 4 -> Underlined.\n * Ps = 5 -> Blink (appears as Bold).\n * Ps = 7 -> Inverse.\n * Ps = 8 -> Invisible, i.e., hidden (VT300).\n * Ps = 2 2 -> Normal (neither bold nor faint).\n * Ps = 2 4 -> Not underlined.\n * Ps = 2 5 -> Steady (not blinking).\n * Ps = 2 7 -> Positive (not inverse).\n * Ps = 2 8 -> Visible, i.e., not hidden (VT300).\n * Ps = 3 0 -> Set foreground color to Black.\n * Ps = 3 1 -> Set foreground color to Red.\n * Ps = 3 2 -> Set foreground color to Green.\n * Ps = 3 3 -> Set foreground color to Yellow.\n * Ps = 3 4 -> Set foreground color to Blue.\n * Ps = 3 5 -> Set foreground color to Magenta.\n * Ps = 3 6 -> Set foreground color to Cyan.\n * Ps = 3 7 -> Set foreground color to White.\n * Ps = 3 9 -> Set foreground color to default (original).\n * Ps = 4 0 -> Set background color to Black.\n * Ps = 4 1 -> Set background color to Red.\n * Ps = 4 2 -> Set background color to Green.\n * Ps = 4 3 -> Set background color to Yellow.\n * Ps = 4 4 -> Set background color to Blue.\n * Ps = 4 5 -> Set background color to Magenta.\n * Ps = 4 6 -> Set background color to Cyan.\n * Ps = 4 7 -> Set background color to White.\n * Ps = 4 9 -> Set background color to default (original).\n *\n * If 16-color support is compiled, the following apply. Assume\n * that xterm's resources are set so that the ISO color codes are\n * the first 8 of a set of 16. Then the aixterm colors are the\n * bright versions of the ISO colors:\n * Ps = 9 0 -> Set foreground color to Black.\n * Ps = 9 1 -> Set foreground color to Red.\n * Ps = 9 2 -> Set foreground color to Green.\n * Ps = 9 3 -> Set foreground color to Yellow.\n * Ps = 9 4 -> Set foreground color to Blue.\n * Ps = 9 5 -> Set foreground color to Magenta.\n * Ps = 9 6 -> Set foreground color to Cyan.\n * Ps = 9 7 -> Set foreground color to White.\n * Ps = 1 0 0 -> Set background color to Black.\n * Ps = 1 0 1 -> Set background color to Red.\n * Ps = 1 0 2 -> Set background color to Green.\n * Ps = 1 0 3 -> Set background color to Yellow.\n * Ps = 1 0 4 -> Set background color to Blue.\n * Ps = 1 0 5 -> Set background color to Magenta.\n * Ps = 1 0 6 -> Set background color to Cyan.\n * Ps = 1 0 7 -> Set background color to White.\n *\n * If xterm is compiled with the 16-color support disabled, it\n * supports the following, from rxvt:\n * Ps = 1 0 0 -> Set foreground and background color to\n * default.\n *\n * If 88- or 256-color support is compiled, the following apply.\n * Ps = 3 8 ; 5 ; Ps -> Set foreground color to the second\n * Ps.\n * Ps = 4 8 ; 5 ; Ps -> Set background color to the second\n * Ps.\n */\n public charAttributes(params: number[]): void {\n // Optimize a single SGR0.\n if (params.length === 1 && params[0] === 0) {\n this._terminal.curAttr = this._terminal.defAttr;\n return;\n }\n\n let l = params.length\n , i = 0\n , flags = this._terminal.curAttr >> 18\n , fg = (this._terminal.curAttr >> 9) & 0x1ff\n , bg = this._terminal.curAttr & 0x1ff\n , p;\n\n for (; i < l; i++) {\n p = params[i];\n if (p >= 30 && p <= 37) {\n // fg color 8\n fg = p - 30;\n } else if (p >= 40 && p <= 47) {\n // bg color 8\n bg = p - 40;\n } else if (p >= 90 && p <= 97) {\n // fg color 16\n p += 8;\n fg = p - 90;\n } else if (p >= 100 && p <= 107) {\n // bg color 16\n p += 8;\n bg = p - 100;\n } else if (p === 0) {\n // default\n flags = this._terminal.defAttr >> 18;\n fg = (this._terminal.defAttr >> 9) & 0x1ff;\n bg = this._terminal.defAttr & 0x1ff;\n // flags = 0;\n // fg = 0x1ff;\n // bg = 0x1ff;\n } else if (p === 1) {\n // bold text\n flags |= 1;\n } else if (p === 4) {\n // underlined text\n flags |= 2;\n } else if (p === 5) {\n // blink\n flags |= 4;\n } else if (p === 7) {\n // inverse and positive\n // test with: echo -e '\\e[31m\\e[42mhello\\e[7mworld\\e[27mhi\\e[m'\n flags |= 8;\n } else if (p === 8) {\n // invisible\n flags |= 16;\n } else if (p === 22) {\n // not bold\n flags &= ~1;\n } else if (p === 24) {\n // not underlined\n flags &= ~2;\n } else if (p === 25) {\n // not blink\n flags &= ~4;\n } else if (p === 27) {\n // not inverse\n flags &= ~8;\n } else if (p === 28) {\n // not invisible\n flags &= ~16;\n } else if (p === 39) {\n // reset fg\n fg = (this._terminal.defAttr >> 9) & 0x1ff;\n } else if (p === 49) {\n // reset bg\n bg = this._terminal.defAttr & 0x1ff;\n } else if (p === 38) {\n // fg color 256\n if (params[i + 1] === 2) {\n i += 2;\n fg = this._terminal.matchColor(\n params[i] & 0xff,\n params[i + 1] & 0xff,\n params[i + 2] & 0xff);\n if (fg === -1) fg = 0x1ff;\n i += 2;\n } else if (params[i + 1] === 5) {\n i += 2;\n p = params[i] & 0xff;\n fg = p;\n }\n } else if (p === 48) {\n // bg color 256\n if (params[i + 1] === 2) {\n i += 2;\n bg = this._terminal.matchColor(\n params[i] & 0xff,\n params[i + 1] & 0xff,\n params[i + 2] & 0xff);\n if (bg === -1) bg = 0x1ff;\n i += 2;\n } else if (params[i + 1] === 5) {\n i += 2;\n p = params[i] & 0xff;\n bg = p;\n }\n } else if (p === 100) {\n // reset fg/bg\n fg = (this._terminal.defAttr >> 9) & 0x1ff;\n bg = this._terminal.defAttr & 0x1ff;\n } else {\n this._terminal.error('Unknown SGR attribute: %d.', p);\n }\n }\n\n this._terminal.curAttr = (flags << 18) | (fg << 9) | bg;\n }\n\n /**\n * CSI Ps n Device Status Report (DSR).\n * Ps = 5 -> Status Report. Result (``OK'') is\n * CSI 0 n\n * Ps = 6 -> Report Cursor Position (CPR) [row;column].\n * Result is\n * CSI r ; c R\n * CSI ? Ps n\n * Device Status Report (DSR, DEC-specific).\n * Ps = 6 -> Report Cursor Position (CPR) [row;column] as CSI\n * ? r ; c R (assumes page is zero).\n * Ps = 1 5 -> Report Printer status as CSI ? 1 0 n (ready).\n * or CSI ? 1 1 n (not ready).\n * Ps = 2 5 -> Report UDK status as CSI ? 2 0 n (unlocked)\n * or CSI ? 2 1 n (locked).\n * Ps = 2 6 -> Report Keyboard status as\n * CSI ? 2 7 ; 1 ; 0 ; 0 n (North American).\n * The last two parameters apply to VT400 & up, and denote key-\n * board ready and LK01 respectively.\n * Ps = 5 3 -> Report Locator status as\n * CSI ? 5 3 n Locator available, if compiled-in, or\n * CSI ? 5 0 n No Locator, if not.\n */\n public deviceStatus(params: number[]): void {\n if (!this._terminal.prefix) {\n switch (params[0]) {\n case 5:\n // status report\n this._terminal.send(C0.ESC + '[0n');\n break;\n case 6:\n // cursor position\n this._terminal.send(C0.ESC + '['\n + (this._terminal.buffer.y + 1)\n + ';'\n + (this._terminal.buffer.x + 1)\n + 'R');\n break;\n }\n } else if (this._terminal.prefix === '?') {\n // modern xterm doesnt seem to\n // respond to any of these except ?6, 6, and 5\n switch (params[0]) {\n case 6:\n // cursor position\n this._terminal.send(C0.ESC + '[?'\n + (this._terminal.buffer.y + 1)\n + ';'\n + (this._terminal.buffer.x + 1)\n + 'R');\n break;\n case 15:\n // no printer\n // this.send(C0.ESC + '[?11n');\n break;\n case 25:\n // dont support user defined keys\n // this.send(C0.ESC + '[?21n');\n break;\n case 26:\n // north american keyboard\n // this.send(C0.ESC + '[?27;1;0;0n');\n break;\n case 53:\n // no dec locator/mouse\n // this.send(C0.ESC + '[?50n');\n break;\n }\n }\n }\n\n /**\n * CSI ! p Soft terminal reset (DECSTR).\n * http://vt100.net/docs/vt220-rm/table4-10.html\n */\n public softReset(params: number[]): void {\n this._terminal.cursorHidden = false;\n this._terminal.insertMode = false;\n this._terminal.originMode = false;\n this._terminal.wraparoundMode = true; // defaults: xterm - true, vt100 - false\n this._terminal.applicationKeypad = false; // ?\n this._terminal.viewport.syncScrollArea();\n this._terminal.applicationCursor = false;\n this._terminal.buffer.scrollTop = 0;\n this._terminal.buffer.scrollBottom = this._terminal.rows - 1;\n this._terminal.curAttr = this._terminal.defAttr;\n this._terminal.buffer.x = this._terminal.buffer.y = 0; // ?\n this._terminal.charset = null;\n this._terminal.glevel = 0; // ??\n this._terminal.charsets = [null]; // ??\n }\n\n /**\n * CSI Ps SP q Set cursor style (DECSCUSR, VT520).\n * Ps = 0 -> blinking block.\n * Ps = 1 -> blinking block (default).\n * Ps = 2 -> steady block.\n * Ps = 3 -> blinking underline.\n * Ps = 4 -> steady underline.\n * Ps = 5 -> blinking bar (xterm).\n * Ps = 6 -> steady bar (xterm).\n */\n public setCursorStyle(params?: number[]): void {\n const param = params[0] < 1 ? 1 : params[0];\n switch (param) {\n case 1:\n case 2:\n this._terminal.setOption('cursorStyle', 'block');\n break;\n case 3:\n case 4:\n this._terminal.setOption('cursorStyle', 'underline');\n break;\n case 5:\n case 6:\n this._terminal.setOption('cursorStyle', 'bar');\n break;\n }\n const isBlinking = param % 2 === 1;\n this._terminal.setOption('cursorBlink', isBlinking);\n }\n\n /**\n * CSI Ps ; Ps r\n * Set Scrolling Region [top;bottom] (default = full size of win-\n * dow) (DECSTBM).\n * CSI ? Pm r\n */\n public setScrollRegion(params: number[]): void {\n if (this._terminal.prefix) return;\n this._terminal.buffer.scrollTop = (params[0] || 1) - 1;\n this._terminal.buffer.scrollBottom = (params[1] && params[1] <= this._terminal.rows ? params[1] : this._terminal.rows) - 1;\n this._terminal.buffer.x = 0;\n this._terminal.buffer.y = 0;\n }\n\n\n /**\n * CSI s\n * Save cursor (ANSI.SYS).\n */\n public saveCursor(params: number[]): void {\n this._terminal.buffer.savedX = this._terminal.buffer.x;\n this._terminal.buffer.savedY = this._terminal.buffer.y;\n }\n\n\n /**\n * CSI u\n * Restore cursor (ANSI.SYS).\n */\n public restoreCursor(params: number[]): void {\n this._terminal.buffer.x = this._terminal.buffer.savedX || 0;\n this._terminal.buffer.y = this._terminal.buffer.savedY || 0;\n }\n}\n\nexport const wcwidth = (function(opts) {\n // extracted from https://www.cl.cam.ac.uk/%7Emgk25/ucs/wcwidth.c\n // combining characters\n const COMBINING_BMP = [\n [0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489],\n [0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2],\n [0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603],\n [0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670],\n [0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED],\n [0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A],\n [0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902],\n [0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D],\n [0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981],\n [0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD],\n [0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C],\n [0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D],\n [0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC],\n [0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD],\n [0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C],\n [0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D],\n [0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0],\n [0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48],\n [0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC],\n [0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD],\n [0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D],\n [0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6],\n [0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E],\n [0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC],\n [0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35],\n [0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E],\n [0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97],\n [0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030],\n [0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039],\n [0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F],\n [0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753],\n [0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD],\n [0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD],\n [0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922],\n [0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B],\n [0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34],\n [0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42],\n [0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF],\n [0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063],\n [0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F],\n [0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B],\n [0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F],\n [0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB],\n ];\n const COMBINING_HIGH = [\n [0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F],\n [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169],\n [0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD],\n [0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F],\n [0xE0100, 0xE01EF]\n ];\n // binary search\n function bisearch(ucs, data) {\n let min = 0;\n let max = data.length - 1;\n let mid;\n if (ucs < data[0][0] || ucs > data[max][1])\n return false;\n while (max >= min) {\n mid = (min + max) >> 1;\n if (ucs > data[mid][1])\n min = mid + 1;\n else if (ucs < data[mid][0])\n max = mid - 1;\n else\n return true;\n }\n return false;\n }\n function wcwidthBMP(ucs) {\n // test for 8-bit control characters\n if (ucs === 0)\n return opts.nul;\n if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0))\n return opts.control;\n // binary search in table of non-spacing characters\n if (bisearch(ucs, COMBINING_BMP))\n return 0;\n // if we arrive here, ucs is not a combining or C0/C1 control character\n if (isWideBMP(ucs)) {\n return 2;\n }\n return 1;\n }\n function isWideBMP(ucs) {\n return (\n ucs >= 0x1100 && (\n ucs <= 0x115f || // Hangul Jamo init. consonants\n ucs === 0x2329 ||\n ucs === 0x232a ||\n (ucs >= 0x2e80 && ucs <= 0xa4cf && ucs !== 0x303f) || // CJK..Yi\n (ucs >= 0xac00 && ucs <= 0xd7a3) || // Hangul Syllables\n (ucs >= 0xf900 && ucs <= 0xfaff) || // CJK Compat Ideographs\n (ucs >= 0xfe10 && ucs <= 0xfe19) || // Vertical forms\n (ucs >= 0xfe30 && ucs <= 0xfe6f) || // CJK Compat Forms\n (ucs >= 0xff00 && ucs <= 0xff60) || // Fullwidth Forms\n (ucs >= 0xffe0 && ucs <= 0xffe6)));\n }\n function wcwidthHigh(ucs) {\n if (bisearch(ucs, COMBINING_HIGH))\n return 0;\n if ((ucs >= 0x20000 && ucs <= 0x2fffd) || (ucs >= 0x30000 && ucs <= 0x3fffd)) {\n return 2;\n }\n return 1;\n }\n const control = opts.control | 0;\n let table = null;\n function init_table() {\n // lookup table for BMP\n const CODEPOINTS = 65536; // BMP holds 65536 codepoints\n const BITWIDTH = 2; // a codepoint can have a width of 0, 1 or 2\n const ITEMSIZE = 32; // using uint32_t\n const CONTAINERSIZE = CODEPOINTS * BITWIDTH / ITEMSIZE;\n const CODEPOINTS_PER_ITEM = ITEMSIZE / BITWIDTH;\n table = (typeof Uint32Array === 'undefined')\n ? new Array(CONTAINERSIZE)\n : new Uint32Array(CONTAINERSIZE);\n for (let i = 0; i < CONTAINERSIZE; ++i) {\n let num = 0;\n let pos = CODEPOINTS_PER_ITEM;\n while (pos--)\n num = (num << 2) | wcwidthBMP(CODEPOINTS_PER_ITEM * i + pos);\n table[i] = num;\n }\n return table;\n }\n // get width from lookup table\n // position in container : num / CODEPOINTS_PER_ITEM\n // ==> n = table[Math.floor(num / 16)]\n // ==> n = table[num >> 4]\n // 16 codepoints per number: FFEEDDCCBBAA99887766554433221100\n // position in number : (num % CODEPOINTS_PER_ITEM) * BITWIDTH\n // ==> m = (n % 16) * 2\n // ==> m = (num & 15) << 1\n // right shift to position m\n // ==> n = n >> m e.g. m=12 000000000000FFEEDDCCBBAA99887766\n // we are only interested in 2 LSBs, cut off higher bits\n // ==> n = n & 3 e.g. 000000000000000000000000000000XX\n return function (num) {\n num = num | 0; // get asm.js like optimization under V8\n if (num < 32)\n return control | 0;\n if (num < 127)\n return 1;\n let t = table || init_table();\n if (num < 65536)\n return t[num >> 4] >> ((num & 15) << 1) & 3;\n // do a full search for high codepoints\n return wcwidthHigh(num);\n };\n})({nul: 0, control: 0}); // configurable options\n","/**\n * @license MIT\n */\n\nimport { IEventEmitter } from './Interfaces';\n\ninterface ListenerType {\n (): void;\n listener?: () => void;\n};\n\nexport class EventEmitter implements IEventEmitter {\n private _events: {[type: string]: ListenerType[]};\n\n constructor() {\n // Restore the previous events if available, this will happen if the\n // constructor is called multiple times on the same object (terminal reset).\n this._events = this._events || {};\n }\n\n public on(type, listener): void {\n this._events[type] = this._events[type] || [];\n this._events[type].push(listener);\n }\n\n public off(type, listener): void {\n if (!this._events[type]) {\n return;\n }\n\n let obj = this._events[type];\n let i = obj.length;\n\n while (i--) {\n if (obj[i] === listener || obj[i].listener === listener) {\n obj.splice(i, 1);\n return;\n }\n }\n }\n\n public removeAllListeners(type): void {\n if (this._events[type]) {\n delete this._events[type];\n }\n }\n\n public once(type, listener): any {\n function on() {\n let args = Array.prototype.slice.call(arguments);\n this.off(type, on);\n return listener.apply(this, args);\n }\n (on).listener = listener;\n return this.on(type, on);\n }\n\n public emit(type: string, ...args: any[]): void {\n if (!this._events[type]) {\n return;\n }\n let obj = this._events[type];\n for (let i = 0; i < obj.length; i++) {\n obj[i].apply(this, args);\n }\n }\n\n public listeners(type): ListenerType[] {\n return this._events[type] || [];\n }\n}\n","/**\n * @license MIT\n */\n\n/**\n * C0 control codes\n * See = https://en.wikipedia.org/wiki/C0_and_C1_control_codes\n */\nexport namespace C0 {\n /** Null (Caret = ^@, C = \\0) */\n export const NUL = '\\x00';\n /** Start of Heading (Caret = ^A) */\n export const SOH = '\\x01';\n /** Start of Text (Caret = ^B) */\n export const STX = '\\x02';\n /** End of Text (Caret = ^C) */\n export const ETX = '\\x03';\n /** End of Transmission (Caret = ^D) */\n export const EOT = '\\x04';\n /** Enquiry (Caret = ^E) */\n export const ENQ = '\\x05';\n /** Acknowledge (Caret = ^F) */\n export const ACK = '\\x06';\n /** Bell (Caret = ^G, C = \\a) */\n export const BEL = '\\x07';\n /** Backspace (Caret = ^H, C = \\b) */\n export const BS = '\\x08';\n /** Character Tabulation, Horizontal Tabulation (Caret = ^I, C = \\t) */\n export const HT = '\\x09';\n /** Line Feed (Caret = ^J, C = \\n) */\n export const LF = '\\x0a';\n /** Line Tabulation, Vertical Tabulation (Caret = ^K, C = \\v) */\n export const VT = '\\x0b';\n /** Form Feed (Caret = ^L, C = \\f) */\n export const FF = '\\x0c';\n /** Carriage Return (Caret = ^M, C = \\r) */\n export const CR = '\\x0d';\n /** Shift Out (Caret = ^N) */\n export const SO = '\\x0e';\n /** Shift In (Caret = ^O) */\n export const SI = '\\x0f';\n /** Data Link Escape (Caret = ^P) */\n export const DLE = '\\x10';\n /** Device Control One (XON) (Caret = ^Q) */\n export const DC1 = '\\x11';\n /** Device Control Two (Caret = ^R) */\n export const DC2 = '\\x12';\n /** Device Control Three (XOFF) (Caret = ^S) */\n export const DC3 = '\\x13';\n /** Device Control Four (Caret = ^T) */\n export const DC4 = '\\x14';\n /** Negative Acknowledge (Caret = ^U) */\n export const NAK = '\\x15';\n /** Synchronous Idle (Caret = ^V) */\n export const SYN = '\\x16';\n /** End of Transmission Block (Caret = ^W) */\n export const ETB = '\\x17';\n /** Cancel (Caret = ^X) */\n export const CAN = '\\x18';\n /** End of Medium (Caret = ^Y) */\n export const EM = '\\x19';\n /** Substitute (Caret = ^Z) */\n export const SUB = '\\x1a';\n /** Escape (Caret = ^[, C = \\e) */\n export const ESC = '\\x1b';\n /** File Separator (Caret = ^\\) */\n export const FS = '\\x1c';\n /** Group Separator (Caret = ^]) */\n export const GS = '\\x1d';\n /** Record Separator (Caret = ^^) */\n export const RS = '\\x1e';\n /** Unit Separator (Caret = ^_) */\n export const US = '\\x1f';\n /** Space */\n export const SP = '\\x20';\n /** Delete (Caret = ^?) */\n export const DEL = '\\x7f';\n};\n","/**\n * @license MIT\n */\n\nimport { ITerminal } from './Interfaces';\n\ninterface IPosition {\n start: number;\n end: number;\n}\n\n/**\n * Encapsulates the logic for handling compositionstart, compositionupdate and compositionend\n * events, displaying the in-progress composition to the UI and forwarding the final composition\n * to the handler.\n */\nexport class CompositionHelper {\n /**\n * Whether input composition is currently happening, eg. via a mobile keyboard, speech input or\n * IME. This variable determines whether the compositionText should be displayed on the UI.\n */\n private isComposing: boolean;\n\n /**\n * The position within the input textarea's value of the current composition.\n */\n private compositionPosition: IPosition;\n\n /**\n * Whether a composition is in the process of being sent, setting this to false will cancel any\n * in-progress composition.\n */\n private isSendingComposition: boolean;\n\n /**\n * Creates a new CompositionHelper.\n * @param textarea The textarea that xterm uses for input.\n * @param compositionView The element to display the in-progress composition in.\n * @param terminal The Terminal to forward the finished composition to.\n */\n constructor(\n private textarea: HTMLTextAreaElement,\n private compositionView: HTMLElement,\n private terminal: ITerminal\n ) {\n this.isComposing = false;\n this.isSendingComposition = false;\n this.compositionPosition = { start: null, end: null };\n }\n\n /**\n * Handles the compositionstart event, activating the composition view.\n */\n public compositionstart() {\n this.isComposing = true;\n this.compositionPosition.start = this.textarea.value.length;\n this.compositionView.textContent = '';\n this.compositionView.classList.add('active');\n }\n\n /**\n * Handles the compositionupdate event, updating the composition view.\n * @param {CompositionEvent} ev The event.\n */\n public compositionupdate(ev: CompositionEvent) {\n this.compositionView.textContent = ev.data;\n this.updateCompositionElements();\n setTimeout(() => {\n this.compositionPosition.end = this.textarea.value.length;\n }, 0);\n }\n\n /**\n * Handles the compositionend event, hiding the composition view and sending the composition to\n * the handler.\n */\n public compositionend() {\n this.finalizeComposition(true);\n }\n\n /**\n * Handles the keydown event, routing any necessary events to the CompositionHelper functions.\n * @param ev The keydown event.\n * @return Whether the Terminal should continue processing the keydown event.\n */\n public keydown(ev: KeyboardEvent) {\n if (this.isComposing || this.isSendingComposition) {\n if (ev.keyCode === 229) {\n // Continue composing if the keyCode is the \"composition character\"\n return false;\n } else if (ev.keyCode === 16 || ev.keyCode === 17 || ev.keyCode === 18) {\n // Continue composing if the keyCode is a modifier key\n return false;\n } else {\n // Finish composition immediately. This is mainly here for the case where enter is\n // pressed and the handler needs to be triggered before the command is executed.\n this.finalizeComposition(false);\n }\n }\n\n if (ev.keyCode === 229) {\n // If the \"composition character\" is used but gets to this point it means a non-composition\n // character (eg. numbers and punctuation) was pressed when the IME was active.\n this.handleAnyTextareaChanges();\n return false;\n }\n\n return true;\n }\n\n /**\n * Finalizes the composition, resuming regular input actions. This is called when a composition\n * is ending.\n * @param waitForPropogation Whether to wait for events to propogate before sending\n * the input. This should be false if a non-composition keystroke is entered before the\n * compositionend event is triggered, such as enter, so that the composition is send before\n * the command is executed.\n */\n private finalizeComposition(waitForPropogation: boolean) {\n this.compositionView.classList.remove('active');\n this.isComposing = false;\n this.clearTextareaPosition();\n\n if (!waitForPropogation) {\n // Cancel any delayed composition send requests and send the input immediately.\n this.isSendingComposition = false;\n const input = this.textarea.value.substring(this.compositionPosition.start, this.compositionPosition.end);\n this.terminal.handler(input);\n } else {\n // Make a deep copy of the composition position here as a new compositionstart event may\n // fire before the setTimeout executes.\n const currentCompositionPosition = {\n start: this.compositionPosition.start,\n end: this.compositionPosition.end,\n };\n\n // Since composition* events happen before the changes take place in the textarea on most\n // browsers, use a setTimeout with 0ms time to allow the native compositionend event to\n // complete. This ensures the correct character is retrieved, this solution was used\n // because:\n // - The compositionend event's data property is unreliable, at least on Chromium\n // - The last compositionupdate event's data property does not always accurately describe\n // the character, a counter example being Korean where an ending consonsant can move to\n // the following character if the following input is a vowel.\n this.isSendingComposition = true;\n setTimeout(() => {\n // Ensure that the input has not already been sent\n if (this.isSendingComposition) {\n this.isSendingComposition = false;\n let input;\n if (this.isComposing) {\n // Use the end position to get the string if a new composition has started.\n input = this.textarea.value.substring(currentCompositionPosition.start, currentCompositionPosition.end);\n } else {\n // Don't use the end position here in order to pick up any characters after the\n // composition has finished, for example when typing a non-composition character\n // (eg. 2) after a composition character.\n input = this.textarea.value.substring(currentCompositionPosition.start);\n }\n this.terminal.handler(input);\n }\n }, 0);\n }\n }\n\n /**\n * Apply any changes made to the textarea after the current event chain is allowed to complete.\n * This should be called when not currently composing but a keydown event with the \"composition\n * character\" (229) is triggered, in order to allow non-composition text to be entered when an\n * IME is active.\n */\n private handleAnyTextareaChanges() {\n const oldValue = this.textarea.value;\n setTimeout(() => {\n // Ignore if a composition has started since the timeout\n if (!this.isComposing) {\n const newValue = this.textarea.value;\n const diff = newValue.replace(oldValue, '');\n if (diff.length > 0) {\n this.terminal.handler(diff);\n }\n }\n }, 0);\n }\n\n /**\n * Positions the composition view on top of the cursor and the textarea just below it (so the\n * IME helper dialog is positioned correctly).\n * @param dontRecurse Whether to use setTimeout to recursively trigger another update, this is\n * necessary as the IME events across browsers are not consistently triggered.\n */\n public updateCompositionElements(dontRecurse?: boolean) {\n if (!this.isComposing) {\n return;\n }\n const cursor = this.terminal.element.querySelector('.terminal-cursor');\n if (cursor) {\n // Take .xterm-rows offsetTop into account as well in case it's positioned absolutely within\n // the .xterm element.\n const xtermRows = this.terminal.element.querySelector('.xterm-rows');\n const cursorTop = xtermRows.offsetTop + cursor.offsetTop;\n\n this.compositionView.style.left = cursor.offsetLeft + 'px';\n this.compositionView.style.top = cursorTop + 'px';\n this.compositionView.style.height = cursor.offsetHeight + 'px';\n this.compositionView.style.lineHeight = cursor.offsetHeight + 'px';\n // Sync the textarea to the exact position of the composition view so the IME knows where the\n // text is.\n const compositionViewBounds = this.compositionView.getBoundingClientRect();\n this.textarea.style.left = cursor.offsetLeft + 'px';\n this.textarea.style.top = cursorTop + 'px';\n this.textarea.style.width = compositionViewBounds.width + 'px';\n this.textarea.style.height = compositionViewBounds.height + 'px';\n this.textarea.style.lineHeight = compositionViewBounds.height + 'px';\n }\n if (!dontRecurse) {\n setTimeout(() => this.updateCompositionElements(true), 0);\n }\n };\n\n /**\n * Clears the textarea's position so that the cursor does not blink on IE.\n * @private\n */\n private clearTextareaPosition() {\n this.textarea.style.left = '';\n this.textarea.style.top = '';\n };\n}\n","/**\n * @license MIT\n */\n\n/**\n * The character sets supported by the terminal. These enable several languages\n * to be represented within the terminal with only 8-bit encoding. See ISO 2022\n * for a discussion on character sets. Only VT100 character sets are supported.\n */\nexport const CHARSETS: {[key: string]: {[key: string]: string}} = {};\n\n/**\n * The default character set, US.\n */\nexport const DEFAULT_CHARSET = CHARSETS['B'];\n\n/**\n * DEC Special Character and Line Drawing Set.\n * Reference: http://vt100.net/docs/vt102-ug/table5-13.html\n * A lot of curses apps use this if they see TERM=xterm.\n * testing: echo -e '\\e(0a\\e(B'\n * The xterm output sometimes seems to conflict with the\n * reference above. xterm seems in line with the reference\n * when running vttest however.\n * The table below now uses xterm's output from vttest.\n */\nCHARSETS['0'] = {\n '`': '\\u25c6', // '◆'\n 'a': '\\u2592', // '▒'\n 'b': '\\u0009', // '\\t'\n 'c': '\\u000c', // '\\f'\n 'd': '\\u000d', // '\\r'\n 'e': '\\u000a', // '\\n'\n 'f': '\\u00b0', // '°'\n 'g': '\\u00b1', // '±'\n 'h': '\\u2424', // '\\u2424' (NL)\n 'i': '\\u000b', // '\\v'\n 'j': '\\u2518', // '┘'\n 'k': '\\u2510', // '┐'\n 'l': '\\u250c', // '┌'\n 'm': '\\u2514', // '└'\n 'n': '\\u253c', // '┼'\n 'o': '\\u23ba', // '⎺'\n 'p': '\\u23bb', // '⎻'\n 'q': '\\u2500', // '─'\n 'r': '\\u23bc', // '⎼'\n 's': '\\u23bd', // '⎽'\n 't': '\\u251c', // '├'\n 'u': '\\u2524', // '┤'\n 'v': '\\u2534', // '┴'\n 'w': '\\u252c', // '┬'\n 'x': '\\u2502', // '│'\n 'y': '\\u2264', // '≤'\n 'z': '\\u2265', // '≥'\n '{': '\\u03c0', // 'π'\n '|': '\\u2260', // '≠'\n '}': '\\u00a3', // '£'\n '~': '\\u00b7' // '·'\n};\n\n/**\n * British character set\n * ESC (A\n * Reference: http://vt100.net/docs/vt220-rm/table2-5.html\n */\nCHARSETS['A'] = {\n '#': '£'\n};\n\n/**\n * United States character set\n * ESC (B\n */\nCHARSETS['B'] = null;\n\n/**\n * Dutch character set\n * ESC (4\n * Reference: http://vt100.net/docs/vt220-rm/table2-6.html\n */\nCHARSETS['4'] = {\n '#': '£',\n '@': '¾',\n '[': 'ij',\n '\\\\': '½',\n ']': '|',\n '{': '¨',\n '|': 'f',\n '}': '¼',\n '~': '´'\n};\n\n/**\n * Finnish character set\n * ESC (C or ESC (5\n * Reference: http://vt100.net/docs/vt220-rm/table2-7.html\n */\nCHARSETS['C'] =\nCHARSETS['5'] = {\n '[': 'Ä',\n '\\\\': 'Ö',\n ']': 'Å',\n '^': 'Ü',\n '`': 'é',\n '{': 'ä',\n '|': 'ö',\n '}': 'å',\n '~': 'ü'\n};\n\n/**\n * French character set\n * ESC (R\n * Reference: http://vt100.net/docs/vt220-rm/table2-8.html\n */\nCHARSETS['R'] = {\n '#': '£',\n '@': 'à',\n '[': '°',\n '\\\\': 'ç',\n ']': '§',\n '{': 'é',\n '|': 'ù',\n '}': 'è',\n '~': '¨'\n};\n\n/**\n * French Canadian character set\n * ESC (Q\n * Reference: http://vt100.net/docs/vt220-rm/table2-9.html\n */\nCHARSETS['Q'] = {\n '@': 'à',\n '[': 'â',\n '\\\\': 'ç',\n ']': 'ê',\n '^': 'î',\n '`': 'ô',\n '{': 'é',\n '|': 'ù',\n '}': 'è',\n '~': 'û'\n};\n\n/**\n * German character set\n * ESC (K\n * Reference: http://vt100.net/docs/vt220-rm/table2-10.html\n */\nCHARSETS['K'] = {\n '@': '§',\n '[': 'Ä',\n '\\\\': 'Ö',\n ']': 'Ü',\n '{': 'ä',\n '|': 'ö',\n '}': 'ü',\n '~': 'ß'\n};\n\n/**\n * Italian character set\n * ESC (Y\n * Reference: http://vt100.net/docs/vt220-rm/table2-11.html\n */\nCHARSETS['Y'] = {\n '#': '£',\n '@': '§',\n '[': '°',\n '\\\\': 'ç',\n ']': 'é',\n '`': 'ù',\n '{': 'à',\n '|': 'ò',\n '}': 'è',\n '~': 'ì'\n};\n\n/**\n * Norwegian/Danish character set\n * ESC (E or ESC (6\n * Reference: http://vt100.net/docs/vt220-rm/table2-12.html\n */\nCHARSETS['E'] =\nCHARSETS['6'] = {\n '@': 'Ä',\n '[': 'Æ',\n '\\\\': 'Ø',\n ']': 'Å',\n '^': 'Ü',\n '`': 'ä',\n '{': 'æ',\n '|': 'ø',\n '}': 'å',\n '~': 'ü'\n};\n\n/**\n * Spanish character set\n * ESC (Z\n * Reference: http://vt100.net/docs/vt220-rm/table2-13.html\n */\nCHARSETS['Z'] = {\n '#': '£',\n '@': '§',\n '[': '¡',\n '\\\\': 'Ñ',\n ']': '¿',\n '{': '°',\n '|': 'ñ',\n '}': 'ç'\n};\n\n/**\n * Swedish character set\n * ESC (H or ESC (7\n * Reference: http://vt100.net/docs/vt220-rm/table2-14.html\n */\nCHARSETS['H'] =\nCHARSETS['7'] = {\n '@': 'É',\n '[': 'Ä',\n '\\\\': 'Ö',\n ']': 'Å',\n '^': 'Ü',\n '`': 'é',\n '{': 'ä',\n '|': 'ö',\n '}': 'å',\n '~': 'ü'\n};\n\n/**\n * Swiss character set\n * ESC (=\n * Reference: http://vt100.net/docs/vt220-rm/table2-15.html\n */\nCHARSETS['='] = {\n '#': 'ù',\n '@': 'à',\n '[': 'é',\n '\\\\': 'ç',\n ']': 'ê',\n '^': 'î',\n '_': 'è',\n '`': 'ô',\n '{': 'ä',\n '|': 'ö',\n '}': 'ü',\n '~': 'û'\n};\n","/**\n * @license MIT\n */\n\nimport { ITerminal, IBufferSet } from './Interfaces';\nimport { Buffer } from './Buffer';\nimport { EventEmitter } from './EventEmitter';\n\n/**\n * The BufferSet represents the set of two buffers used by xterm terminals (normal and alt) and\n * provides also utilities for working with them.\n */\nexport class BufferSet extends EventEmitter implements IBufferSet {\n private _normal: Buffer;\n private _alt: Buffer;\n private _activeBuffer: Buffer;\n\n /**\n * Create a new BufferSet for the given terminal.\n * @param {Terminal} terminal - The terminal the BufferSet will belong to\n */\n constructor(private _terminal: ITerminal) {\n super();\n this._normal = new Buffer(this._terminal);\n this._alt = new Buffer(this._terminal);\n this._activeBuffer = this._normal;\n }\n\n /**\n * Returns the alt Buffer of the BufferSet\n * @returns {Buffer}\n */\n public get alt(): Buffer {\n return this._alt;\n }\n\n /**\n * Returns the normal Buffer of the BufferSet\n * @returns {Buffer}\n */\n public get active(): Buffer {\n return this._activeBuffer;\n }\n\n /**\n * Returns the currently active Buffer of the BufferSet\n * @returns {Buffer}\n */\n public get normal(): Buffer {\n return this._normal;\n }\n\n /**\n * Sets the normal Buffer of the BufferSet as its currently active Buffer\n */\n public activateNormalBuffer(): void {\n this._activeBuffer = this._normal;\n this.emit('activate', this._normal);\n }\n\n /**\n * Sets the alt Buffer of the BufferSet as its currently active Buffer\n */\n public activateAltBuffer(): void {\n this._activeBuffer = this._alt;\n this.emit('activate', this._alt);\n }\n}\n","/**\n * @license MIT\n */\n\nimport { ITerminal } from './Interfaces';\nimport { CircularList } from './utils/CircularList';\n\n/**\n * This class represents a terminal buffer (an internal state of the terminal), where the\n * following information is stored (in high-level):\n * - text content of this particular buffer\n * - cursor position\n * - scroll position\n */\nexport class Buffer {\n public lines: CircularList<[number, string, number][]>;\n\n public savedY: number;\n public savedX: number;\n\n /**\n * Create a new Buffer.\n * @param {Terminal} terminal - The terminal the Buffer will belong to\n * @param {number} ydisp - The scroll position of the Buffer in the viewport\n * @param {number} ybase - The scroll position of the y cursor (ybase + y = the y position within the Buffer)\n * @param {number} y - The cursor's y position after ybase\n * @param {number} x - The cursor's x position after ybase\n */\n constructor(\n private terminal: ITerminal,\n public ydisp: number = 0,\n public ybase: number = 0,\n public y: number = 0,\n public x: number = 0,\n public scrollBottom: number = 0,\n public scrollTop: number = 0,\n public tabs: any = {},\n ) {\n this.lines = new CircularList<[number, string, number][]>(this.terminal.scrollback);\n this.scrollBottom = this.terminal.rows - 1;\n }\n}\n",null],"names":[],"mappings":"AsBAA;;;ADKA;AASA;AAcA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AA3Ba;;;;;;;;;;;;;;;;;ADTb;AACA;AAMA;AAAA;AASA;AAAA;AAAA;AAEA;AACA;AACA;;AACA;AAMA;AAAA;AACA;AACA;;;AAAA;AAMA;AAAA;AACA;AACA;;;AAAA;AAMA;AAAA;AACA;AACA;;;AAAA;AAKA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AAAA;AAvDa;;;;;;;ADHA;AAKA;AAYb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AAMA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;AD3OA;AAwBA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAMA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAOA;AACA;AACA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAGA;AACA;AACA;AAEA;AAGA;AACA;AACA;AAEA;AACA;AAUA;AAAA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAAA;AAGA;AACA;AACA;AACA;AAUA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAAA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AAAA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAMA;AACA;AACA;AACA;AAAA;AACA;AAAA;AApNa;;;;;;;ADRb;AAAA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AACA;AAAC;;;;;;;ADpEA;AAED;AAGA;AAGA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AAAA;AAAA;;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AA3Da;;;;;;;ADNb;AACA;AASA;AAEA;AAAA;AAAA;AAEA;AACA;AAGA;AAEA;AACA;AACA;AAEA;AAIA;AAEA;AACA;AAGA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAIA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAGA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAGA;AAEA;AAGA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAMA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAMA;AACA;AAMA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAOA;AACA;AACA;AAOA;AACA;AACA;AAMA;AACA;AAEA;AACA;AAAA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AAEA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AAcA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAaA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AAMA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AAMA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAMA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AAIA;AACA;AACA;AACA;AAuCA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AAIA;AACA;AACA;AAAA;AACA;AACA;AAAA;AAGA;AACA;AAAA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAOA;AACA;AAAA;AACA;AAAA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAUA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAwFA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AAKA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAoFA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAkEA;AAEA;AACA;AACA;AACA;AAEA;AAOA;AACA;AACA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AACA;AAAA;AAEA;AACA;AACA;AAAA;AAEA;AACA;AACA;AAIA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAGA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AACA;AAIA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAAA;AAEA;AACA;AACA;AAIA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAAA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAyBA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAGA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAGA;AACA;AAGA;AACA;AAGA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAYA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AAAA;AA/6Ca;AAi7CA;AAGb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAaA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;;;;;;;ADplDA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AAKA;AAeA;AAFA;AAGA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AAMA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AAOA;AACA;AACA;AAYA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAOA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA;AAAA;AAOA;AACA;AAEA;AACA;AACA;AACA;AAQA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AAEA;AACA;AACA;AACA;AAEA;AAGA;AACA;AACA;AACA;AAAA;AAEA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AAAA;AAAA;AAAA;;AACA;AACA;AACA;AACA;AACA;AACA;AAWA;AAGA;AACA;AACA;AAKA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAhUmB;AANN;;;;;;;ADhCb;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AAKA;AACA;AACA;AAEA;AACA;AAOA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAIA;AACA;AAGA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AAKA;AACA;AAIA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAIA;AACA;AAAA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AAEA;AACA;AAIA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAGA;AACA;AAMA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AAQA;AACA;AACA;AAQA;AACA;AACA;AAOA;AACA;AACA;AAKA;AACA;AACA;AAMA;AACA;AACA;AACA;AAKA;AACA;AACA;AASA;AAAA;AA9da;;;;;;;ADjKb;AAOA;AAKA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAAC;AAED;AAEA;AAQA;AAAA;AANA;AACA;AACA;AAEA;AAKA;AACA;AACA;AACA;AAKA;AAQA;AACA;AACA;AACA;AACA;AACA;AAMA;AAKA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAuBA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAGA;AACA;AAAA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAIA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAAA;AAOA;AAEA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AAGA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAQA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AApWa;AAwWb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;AD3YA;AACA;AAGA;AAEA;AACA;AAMA;AAKA;AAKA;AAMA;AAIA;AACA;AAEA;AACA;AAaA;AAAA;AACA;AACA;AACA;AACA;AAUA;AAAA;AAiCA;AAAA;AACA;AACA;AACA;AACA;AATA;AAYA;AACA;AAEA;AACA;;AACA;AAKA;AAAA;AACA;AACA;AAEA;AAMA;AACA;AAMA;AACA;AACA;AACA;AAKA;AACA;AACA;AAOA;AACA;AACA;AACA;AAEA;AAAA;;;AAAA;AACA;AAAA;;;AAAA;AAKA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAAA;AAKA;AAAA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAIA;AACA;AACA;AAEA;AACA;;;AAAA;AAKA;AACA;AACA;AACA;AACA;AAOA;AAAA;AAEA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAGA;AACA;AAEA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAMA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AAEA;AACA;AACA;AAGA;AACA;AAGA;AAGA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AAKA;AAAA;AAEA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AAGA;AAGA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AAGA;AAIA;AACA;AACA;AAAA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AAGA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAKA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAAA;AA1lBa;;;;;;;AD1Db;AAuBA;AACA;AAEA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAKA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;;;AAAA;AAMA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AACA;AAGA;AAEA;AACA;AACA;AACA;AACA;AACA;;;AAAA;AAKA;AACA;AACA;AACA;AACA;AAOA;AAEA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAAA;AArHa;;;;;;;ADCb;AAaA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AAEA;AACA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AAQA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAAA;AAMA;AACA;AACA;AAAA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AAAA;AArIa;;;;;;;ADWb;AACA;AACA;AACA;AACA;AACA;AALA;AAWA;AACA;AACA;AACA;AAAA;AACA;AACA;AAGA;AACA;AATA;AAgBA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAzBA;AAgCA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AApBA;AA4BA;AACA;AAGA;AACA;AACA;AANA;;;;;;;ADvGA;AAEA;AACA;AACA;AAEa;AACA;AAKA;AACA;AACA;AACA;AACA;;;;;;;ADhBb;AACA;AAYA;AAAA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAnCA;;;;;;;;;;;;;;;;;ADdA;AAKA;AAAA;AAOA;AAAA;AAEA;AACA;;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAnDa;;;;;;;;;;;;;;;;;ADJb;AAEA;AAAA;AAKA;AAAA;AAEA;AACA;AACA;;AACA;AAEA;AAAA;AACA;AACA;AAEA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAXA;AAaA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AATA;AAWA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAAA;AAUA;AACA;AACA;AAUA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAMA;AACA;AACA;AAWA;AAAA;AAAA;AAAA;;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AAAA;AAhMa;;;;;;;ADCb;AAYA;AAAA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAAA;AAhE0B;AAET;AAHJ;;;;;;;ADEb;AACA;AACA;AAFA;AAEC;;;;;;;ADPD;AAEA;AACA;AACA;AAEA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AAjBA;AAgCA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AAGA;AACA;AAEA;AACA;AApBA;AAgCA;AACA;AACA;AACA;AAGA;AACA;AAEA;AACA;AAVA;;;;;;;AD1DA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAcA;AAOA;AAMA;AAOA;AAkBA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AAEA;AAAA;AACA;AAEA;AACA;AAEA;AAIA;AAIA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAGA;AACA;AAQA;AAGA;AAGA;AAGA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAGA;AACA;AAEA;AAKA;AAEA;AACA;AAOA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAIA;AACA;AAKA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AAKA;AACA;AACA;AAMA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AAAA;AACA;AAEA;AACA;AACA;AAGA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAKA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAQA;AAAA;AACA;AAEA;AAEA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AAIA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AAGA;AAIA;AAMA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAIA;AAOA;AACA;AAQA;AACA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AAYA;AACA;AAKA;AACA;AAIA;AAGA;AACA;AAAA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAIA;AACA;AACA;AAIA;AACA;AAGA;AACA;AAAA;AAIA;AAEA;AACA;AAIA;AACA;AACA;AAAA;AACA;AAAA;AACA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAQA;AAOA;AAGA;AACA;AACA;AACA;AACA;AAAA;AACA;AAAA;AACA;AAAA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAAA;AACA;AAAA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAaA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AAGA;AAEA;AACA;AAAA;AACA;AACA;AAGA;AAEA;AACA;AAEA;AAIA;AACA;AAEA;AAAA;AAGA;AAIA;AACA;AACA;AACA;AACA;AAGA;AAAA;AAGA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AAMA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAIA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAQA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AACA;AAGA;AAGA;AAEA;AAEA;AACA;AAAA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAQA;AACA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAMA;AACA;AACA;AAKA;AACA;AACA;AAKA;AACA;AACA;AAMA;AACA;AAKA;AAGA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AAEA;AACA;AAOA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAMA;AACA;AACA;AAQA;AACA;AACA;AACA;AACA;AAUA;AACA;AACA;AAQA;AACA;AACA;AACA;AACA;AAEA;AACA;AAQA;AACA;AACA;AACA;AACA;AAEA;AACA;AAYA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AAMA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AASA;AACA;AAGA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAIA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AAIA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AAGA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AAGA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AACA;AAAA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAMA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AAEA;AAGA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAMA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAMA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AAKA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AAKA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAOA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AAAA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAGA;AACA;AACA;AACA;AACA;AAEA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AAAA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AAMA;AACA;AAAA;AACA;AAAA;AAOA;AAKA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAOA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AAOA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAQA;AACA;AACA;AACA;AAEA;AAMA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAOA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AAOA;AAEA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAOA;AAOA;AACA;AAUA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAQA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AAGA;AACA;AAGA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AASA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAGA;AACA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAQA;AACA;AACA;AAEA;;;"} \ No newline at end of file +{"version":3,"file":"xterm.js","sources":["../src/xterm.js","../src/utils/Mouse.ts","../src/utils/Generic.ts","../src/utils/DomElementObjectPool.ts","../src/utils/CircularList.ts","../src/utils/CharMeasure.ts","../src/utils/BufferLine.ts","../src/utils/Browser.ts","../src/handlers/Clipboard.ts","../src/Viewport.ts","../src/SelectionModel.ts","../src/SelectionManager.ts","../src/Renderer.ts","../src/Parser.ts","../src/Linkifier.ts","../src/InputHandler.ts","../src/EventEmitter.ts","../src/EscapeSequences.ts","../src/CompositionHelper.ts","../src/Charsets.ts","../src/BufferSet.ts","../src/Buffer.ts","../node_modules/browserify/node_modules/browser-pack/_prelude.js"],"sourcesContent":["/**\n * xterm.js: xterm, in the browser\n * Originally forked from (with the author's permission):\n * Fabrice Bellard's javascript vt100 for jslinux:\n * http://bellard.org/jslinux/\n * Copyright (c) 2011 Fabrice Bellard\n * The original design remains. The terminal itself\n * has been extended to include xterm CSI codes, among\n * other features.\n * @license MIT\n */\n\nimport { BufferSet } from './BufferSet';\nimport { CompositionHelper } from './CompositionHelper';\nimport { EventEmitter } from './EventEmitter';\nimport { Viewport } from './Viewport';\nimport { rightClickHandler, moveTextAreaUnderMouseCursor, pasteHandler, copyHandler } from './handlers/Clipboard';\nimport { CircularList } from './utils/CircularList';\nimport { C0 } from './EscapeSequences';\nimport { InputHandler } from './InputHandler';\nimport { Parser } from './Parser';\nimport { Renderer } from './Renderer';\nimport { Linkifier } from './Linkifier';\nimport { SelectionManager } from './SelectionManager';\nimport { CharMeasure } from './utils/CharMeasure';\nimport * as Browser from './utils/Browser';\nimport * as Mouse from './utils/Mouse';\nimport { CHARSETS } from './Charsets';\nimport { getRawByteCoords } from './utils/Mouse';\nimport { translateBufferLineToString } from './utils/BufferLine';\n\n/**\n * Terminal Emulation References:\n * http://vt100.net/\n * http://invisible-island.net/xterm/ctlseqs/ctlseqs.txt\n * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html\n * http://invisible-island.net/vttest/\n * http://www.inwap.com/pdp10/ansicode.txt\n * http://linux.die.net/man/4/console_codes\n * http://linux.die.net/man/7/urxvt\n */\n\n// Let it work inside Node.js for automated testing purposes.\nvar document = (typeof window != 'undefined') ? window.document : null;\n\n/**\n * The amount of write requests to queue before sending an XOFF signal to the\n * pty process. This number must be small in order for ^C and similar sequences\n * to be responsive.\n */\nvar WRITE_BUFFER_PAUSE_THRESHOLD = 5;\n\n/**\n * The number of writes to perform in a single batch before allowing the\n * renderer to catch up with a 0ms setTimeout.\n */\nvar WRITE_BATCH_SIZE = 300;\n\n/**\n * The time between cursor blinks. This is driven by JS rather than a CSS\n * animation due to a bug in Chromium that causes it to use excessive CPU time.\n * See https://github.com/Microsoft/vscode/issues/22900\n */\nvar CURSOR_BLINK_INTERVAL = 600;\n\n/**\n * Terminal\n */\n\n/**\n * Creates a new `Terminal` object.\n *\n * @param {object} options An object containing a set of options, the available options are:\n * - `cursorBlink` (boolean): Whether the terminal cursor blinks\n * - `cols` (number): The number of columns of the terminal (horizontal size)\n * - `rows` (number): The number of rows of the terminal (vertical size)\n *\n * @public\n * @class Xterm Xterm\n * @alias module:xterm/src/xterm\n */\nfunction Terminal(options) {\n var self = this;\n\n if (!(this instanceof Terminal)) {\n return new Terminal(arguments[0], arguments[1], arguments[2]);\n }\n\n self.browser = Browser;\n self.cancel = Terminal.cancel;\n\n EventEmitter.call(this);\n\n if (typeof options === 'number') {\n options = {\n cols: arguments[0],\n rows: arguments[1],\n handler: arguments[2]\n };\n }\n\n options = options || {};\n\n\n Object.keys(Terminal.defaults).forEach(function(key) {\n if (options[key] == null) {\n options[key] = Terminal.options[key];\n\n if (Terminal[key] !== Terminal.defaults[key]) {\n options[key] = Terminal[key];\n }\n }\n self[key] = options[key];\n });\n\n if (options.colors.length === 8) {\n options.colors = options.colors.concat(Terminal._colors.slice(8));\n } else if (options.colors.length === 16) {\n options.colors = options.colors.concat(Terminal._colors.slice(16));\n } else if (options.colors.length === 10) {\n options.colors = options.colors.slice(0, -2).concat(\n Terminal._colors.slice(8, -2), options.colors.slice(-2));\n } else if (options.colors.length === 18) {\n options.colors = options.colors.concat(\n Terminal._colors.slice(16, -2), options.colors.slice(-2));\n }\n this.colors = options.colors;\n\n this.options = options;\n\n // this.context = options.context || window;\n // this.document = options.document || document;\n this.parent = options.body || options.parent || (\n document ? document.getElementsByTagName('body')[0] : null\n );\n\n this.cols = options.cols || options.geometry[0];\n this.rows = options.rows || options.geometry[1];\n this.geometry = [this.cols, this.rows];\n\n if (options.handler) {\n this.on('data', options.handler);\n }\n\n this.cursorState = 0;\n this.cursorHidden = false;\n this.convertEol;\n this.queue = '';\n this.customKeyEventHandler = null;\n this.cursorBlinkInterval = null;\n\n // modes\n this.applicationKeypad = false;\n this.applicationCursor = false;\n this.originMode = false;\n this.insertMode = false;\n this.wraparoundMode = true; // defaults: xterm - true, vt100 - false\n\n // charset\n this.charset = null;\n this.gcharset = null;\n this.glevel = 0;\n this.charsets = [null];\n\n // mouse properties\n this.decLocator;\n this.x10Mouse;\n this.vt200Mouse;\n this.vt300Mouse;\n this.normalMouse;\n this.mouseEvents;\n this.sendFocus;\n this.utfMouse;\n this.sgrMouse;\n this.urxvtMouse;\n\n // misc\n this.element;\n this.children;\n this.refreshStart;\n this.refreshEnd;\n this.savedX;\n this.savedY;\n this.savedCols;\n\n // stream\n this.readable = true;\n this.writable = true;\n\n this.defAttr = (0 << 18) | (257 << 9) | (256 << 0);\n this.curAttr = this.defAttr;\n\n this.params = [];\n this.currentParam = 0;\n this.prefix = '';\n this.postfix = '';\n\n this.inputHandler = new InputHandler(this);\n this.parser = new Parser(this.inputHandler, this);\n // Reuse renderer if the Terminal is being recreated via a Terminal.reset call.\n this.renderer = this.renderer || null;\n this.selectionManager = this.selectionManager || null;\n this.linkifier = this.linkifier || new Linkifier();\n\n // user input states\n this.writeBuffer = [];\n this.writeInProgress = false;\n\n /**\n * Whether _xterm.js_ sent XOFF in order to catch up with the pty process.\n * This is a distinct state from writeStopped so that if the user requested\n * XOFF via ^S that it will not automatically resume when the writeBuffer goes\n * below threshold.\n */\n this.xoffSentToCatchUp = false;\n\n /** Whether writing has been stopped as a result of XOFF */\n this.writeStopped = false;\n\n // leftover surrogate high from previous write invocation\n this.surrogate_high = '';\n\n // Create the terminal's buffers and set the current buffer\n this.buffers = new BufferSet(this);\n this.buffer = this.buffers.active; // Convenience shortcut;\n this.buffers.on('activate', function (buffer) {\n this._terminal.buffer = buffer;\n });\n\n // Ensure the selection manager has the correct buffer\n if (this.selectionManager) {\n this.selectionManager.setBuffer(this.buffer.lines);\n }\n\n this.setupStops();\n\n // Store if user went browsing history in scrollback\n this.userScrolling = false;\n}\n\ninherits(Terminal, EventEmitter);\n\n/**\n * back_color_erase feature for xterm.\n */\nTerminal.prototype.eraseAttr = function() {\n // if (this.is('screen')) return this.defAttr;\n return (this.defAttr & ~0x1ff) | (this.curAttr & 0x1ff);\n};\n\n/**\n * Colors\n */\n\n// Colors 0-15\nTerminal.tangoColors = [\n // dark:\n '#2e3436',\n '#cc0000',\n '#4e9a06',\n '#c4a000',\n '#3465a4',\n '#75507b',\n '#06989a',\n '#d3d7cf',\n // bright:\n '#555753',\n '#ef2929',\n '#8ae234',\n '#fce94f',\n '#729fcf',\n '#ad7fa8',\n '#34e2e2',\n '#eeeeec'\n];\n\n// Colors 0-15 + 16-255\n// Much thanks to TooTallNate for writing this.\nTerminal.colors = (function() {\n var colors = Terminal.tangoColors.slice()\n , r = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]\n , i;\n\n // 16-231\n i = 0;\n for (; i < 216; i++) {\n out(r[(i / 36) % 6 | 0], r[(i / 6) % 6 | 0], r[i % 6]);\n }\n\n // 232-255 (grey)\n i = 0;\n for (; i < 24; i++) {\n r = 8 + i * 10;\n out(r, r, r);\n }\n\n function out(r, g, b) {\n colors.push('#' + hex(r) + hex(g) + hex(b));\n }\n\n function hex(c) {\n c = c.toString(16);\n return c.length < 2 ? '0' + c : c;\n }\n\n return colors;\n})();\n\nTerminal._colors = Terminal.colors.slice();\n\nTerminal.vcolors = (function() {\n var out = []\n , colors = Terminal.colors\n , i = 0\n , color;\n\n for (; i < 256; i++) {\n color = parseInt(colors[i].substring(1), 16);\n out.push([\n (color >> 16) & 0xff,\n (color >> 8) & 0xff,\n color & 0xff\n ]);\n }\n\n return out;\n})();\n\n/**\n * Options\n */\n\nTerminal.defaults = {\n colors: Terminal.colors,\n theme: 'default',\n convertEol: false,\n termName: 'xterm',\n geometry: [80, 24],\n cursorBlink: false,\n cursorStyle: 'block',\n visualBell: false,\n popOnBell: false,\n scrollback: 1000,\n screenKeys: false,\n debug: false,\n cancelEvents: false,\n disableStdin: false,\n useFlowControl: false,\n tabStopWidth: 8\n // programFeatures: false,\n // focusKeys: false,\n};\n\nTerminal.options = {};\n\nTerminal.focus = null;\n\neach(keys(Terminal.defaults), function(key) {\n Terminal[key] = Terminal.defaults[key];\n Terminal.options[key] = Terminal.defaults[key];\n});\n\n/**\n * Focus the terminal. Delegates focus handling to the terminal's DOM element.\n */\nTerminal.prototype.focus = function() {\n return this.textarea.focus();\n};\n\n/**\n * Retrieves an option's value from the terminal.\n * @param {string} key The option key.\n */\nTerminal.prototype.getOption = function(key) {\n if (!(key in Terminal.defaults)) {\n throw new Error('No option with key \"' + key + '\"');\n }\n\n if (typeof this.options[key] !== 'undefined') {\n return this.options[key];\n }\n\n return this[key];\n};\n\n/**\n * Sets an option on the terminal.\n * @param {string} key The option key.\n * @param {string} value The option value.\n */\nTerminal.prototype.setOption = function(key, value) {\n if (!(key in Terminal.defaults)) {\n throw new Error('No option with key \"' + key + '\"');\n }\n switch (key) {\n case 'scrollback':\n if (value < this.rows) {\n let msg = 'Setting the scrollback value less than the number of rows ';\n\n msg += `(${this.rows}) is not allowed.`;\n\n console.warn(msg);\n return false;\n }\n\n if (this.options[key] !== value) {\n if (this.buffer.lines.length > value) {\n const amountToTrim = this.buffer.lines.length - value;\n const needsRefresh = (this.buffer.ydisp - amountToTrim < 0);\n this.buffer.lines.trimStart(amountToTrim);\n this.buffer.ybase = Math.max(this.buffer.ybase - amountToTrim, 0);\n this.buffer.ydisp = Math.max(this.buffer.ydisp - amountToTrim, 0);\n if (needsRefresh) {\n this.refresh(0, this.rows - 1);\n }\n }\n this.buffer.lines.maxLength = value;\n this.viewport.syncScrollArea();\n }\n break;\n }\n this[key] = value;\n this.options[key] = value;\n switch (key) {\n case 'cursorBlink': this.setCursorBlinking(value); break;\n case 'cursorStyle':\n this.element.classList.toggle(`xterm-cursor-style-block`, value === 'block');\n this.element.classList.toggle(`xterm-cursor-style-underline`, value === 'underline');\n this.element.classList.toggle(`xterm-cursor-style-bar`, value === 'bar');\n break;\n case 'tabStopWidth': this.setupStops(); break;\n }\n};\n\nTerminal.prototype.restartCursorBlinking = function () {\n this.setCursorBlinking(this.options.cursorBlink);\n};\n\nTerminal.prototype.setCursorBlinking = function (enabled) {\n this.element.classList.toggle('xterm-cursor-blink', enabled);\n this.clearCursorBlinkingInterval();\n if (enabled) {\n var self = this;\n this.cursorBlinkInterval = setInterval(function () {\n self.element.classList.toggle('xterm-cursor-blink-on');\n }, CURSOR_BLINK_INTERVAL);\n }\n};\n\nTerminal.prototype.clearCursorBlinkingInterval = function () {\n this.element.classList.remove('xterm-cursor-blink-on');\n if (this.cursorBlinkInterval) {\n clearInterval(this.cursorBlinkInterval);\n this.cursorBlinkInterval = null;\n }\n};\n\n/**\n * Binds the desired focus behavior on a given terminal object.\n *\n * @static\n */\nTerminal.bindFocus = function (term) {\n on(term.textarea, 'focus', function (ev) {\n if (term.sendFocus) {\n term.send(C0.ESC + '[I');\n }\n term.element.classList.add('focus');\n term.showCursor();\n term.restartCursorBlinking.apply(term);\n Terminal.focus = term;\n term.emit('focus', {terminal: term});\n });\n};\n\n/**\n * Blur the terminal. Delegates blur handling to the terminal's DOM element.\n */\nTerminal.prototype.blur = function() {\n return this.textarea.blur();\n};\n\n/**\n * Binds the desired blur behavior on a given terminal object.\n *\n * @static\n */\nTerminal.bindBlur = function (term) {\n on(term.textarea, 'blur', function (ev) {\n term.refresh(term.buffer.y, term.buffer.y);\n if (term.sendFocus) {\n term.send(C0.ESC + '[O');\n }\n term.element.classList.remove('focus');\n term.clearCursorBlinkingInterval.apply(term);\n Terminal.focus = null;\n term.emit('blur', {terminal: term});\n });\n};\n\n/**\n * Initialize default behavior\n */\nTerminal.prototype.initGlobal = function() {\n var term = this;\n\n Terminal.bindKeys(this);\n Terminal.bindFocus(this);\n Terminal.bindBlur(this);\n\n // Bind clipboard functionality\n on(this.element, 'copy', event => {\n // If mouse events are active it means the selection manager is disabled and\n // copy should be handled by the host program.\n if (!term.hasSelection()) {\n return;\n }\n copyHandler(event, term, this.selectionManager);\n });\n const pasteHandlerWrapper = event => pasteHandler(event, term);\n on(this.textarea, 'paste', pasteHandlerWrapper);\n on(this.element, 'paste', pasteHandlerWrapper);\n\n // Handle right click context menus\n if (term.browser.isFirefox) {\n // Firefox doesn't appear to fire the contextmenu event on right click\n on(this.element, 'mousedown', event => {\n if (event.button == 2) {\n rightClickHandler(event, this.textarea, this.selectionManager);\n }\n });\n } else {\n on(this.element, 'contextmenu', event => {\n rightClickHandler(event, this.textarea, this.selectionManager);\n });\n }\n\n // Move the textarea under the cursor when middle clicking on Linux to ensure\n // middle click to paste selection works. This only appears to work in Chrome\n // at the time is writing.\n if (term.browser.isLinux) {\n // Use auxclick event over mousedown the latter doesn't seem to work. Note\n // that the regular click event doesn't fire for the middle mouse button.\n on(this.element, 'auxclick', event => {\n if (event.button === 1) {\n moveTextAreaUnderMouseCursor(event, this.textarea, this.selectionManager);\n }\n });\n }\n};\n\n/**\n * Apply key handling to the terminal\n */\nTerminal.bindKeys = function(term) {\n on(term.element, 'keydown', function(ev) {\n if (document.activeElement != this) {\n return;\n }\n term.keyDown(ev);\n }, true);\n\n on(term.element, 'keypress', function(ev) {\n if (document.activeElement != this) {\n return;\n }\n term.keyPress(ev);\n }, true);\n\n on(term.element, 'keyup', function(ev) {\n if (!wasMondifierKeyOnlyEvent(ev)) {\n term.focus(term);\n }\n }, true);\n\n on(term.textarea, 'keydown', function(ev) {\n term.keyDown(ev);\n }, true);\n\n on(term.textarea, 'keypress', function(ev) {\n term.keyPress(ev);\n // Truncate the textarea's value, since it is not needed\n this.value = '';\n }, true);\n\n on(term.textarea, 'compositionstart', term.compositionHelper.compositionstart.bind(term.compositionHelper));\n on(term.textarea, 'compositionupdate', term.compositionHelper.compositionupdate.bind(term.compositionHelper));\n on(term.textarea, 'compositionend', term.compositionHelper.compositionend.bind(term.compositionHelper));\n term.on('refresh', term.compositionHelper.updateCompositionElements.bind(term.compositionHelper));\n term.on('refresh', function (data) {\n term.queueLinkification(data.start, data.end)\n });\n};\n\n\n/**\n * Insert the given row to the terminal or produce a new one\n * if no row argument is passed. Return the inserted row.\n * @param {HTMLElement} row (optional) The row to append to the terminal.\n */\nTerminal.prototype.insertRow = function (row) {\n if (typeof row != 'object') {\n row = document.createElement('div');\n }\n\n this.rowContainer.appendChild(row);\n this.children.push(row);\n\n return row;\n};\n\n/**\n * Opens the terminal within an element.\n *\n * @param {HTMLElement} parent The element to create the terminal within.\n * @param {boolean} focus Focus the terminal, after it gets instantiated in the DOM\n */\nTerminal.prototype.open = function(parent, focus) {\n var self=this, i=0, div;\n\n this.parent = parent || this.parent;\n\n if (!this.parent) {\n throw new Error('Terminal requires a parent element.');\n }\n\n // Grab global elements\n this.context = this.parent.ownerDocument.defaultView;\n this.document = this.parent.ownerDocument;\n this.body = this.document.getElementsByTagName('body')[0];\n\n //Create main element container\n this.element = this.document.createElement('div');\n this.element.classList.add('terminal');\n this.element.classList.add('xterm');\n this.element.classList.add('xterm-theme-' + this.theme);\n this.element.classList.add(`xterm-cursor-style-${this.options.cursorStyle}`);\n this.setCursorBlinking(this.options.cursorBlink);\n\n this.element.setAttribute('tabindex', 0);\n\n this.viewportElement = document.createElement('div');\n this.viewportElement.classList.add('xterm-viewport');\n this.element.appendChild(this.viewportElement);\n this.viewportScrollArea = document.createElement('div');\n this.viewportScrollArea.classList.add('xterm-scroll-area');\n this.viewportElement.appendChild(this.viewportScrollArea);\n\n // Create the selection container.\n this.selectionContainer = document.createElement('div');\n this.selectionContainer.classList.add('xterm-selection');\n this.element.appendChild(this.selectionContainer);\n\n // Create the container that will hold the lines of the terminal and then\n // produce the lines the lines.\n this.rowContainer = document.createElement('div');\n this.rowContainer.classList.add('xterm-rows');\n this.element.appendChild(this.rowContainer);\n this.children = [];\n this.linkifier.attachToDom(document, this.children);\n\n // Create the container that will hold helpers like the textarea for\n // capturing DOM Events. Then produce the helpers.\n this.helperContainer = document.createElement('div');\n this.helperContainer.classList.add('xterm-helpers');\n // TODO: This should probably be inserted once it's filled to prevent an additional layout\n this.element.appendChild(this.helperContainer);\n this.textarea = document.createElement('textarea');\n this.textarea.classList.add('xterm-helper-textarea');\n this.textarea.setAttribute('autocorrect', 'off');\n this.textarea.setAttribute('autocapitalize', 'off');\n this.textarea.setAttribute('spellcheck', 'false');\n this.textarea.tabIndex = 0;\n this.textarea.addEventListener('focus', function() {\n self.emit('focus', {terminal: self});\n });\n this.textarea.addEventListener('blur', function() {\n self.emit('blur', {terminal: self});\n });\n this.helperContainer.appendChild(this.textarea);\n\n this.compositionView = document.createElement('div');\n this.compositionView.classList.add('composition-view');\n this.compositionHelper = new CompositionHelper(this.textarea, this.compositionView, this);\n this.helperContainer.appendChild(this.compositionView);\n\n this.charSizeStyleElement = document.createElement('style');\n this.helperContainer.appendChild(this.charSizeStyleElement);\n\n for (; i < this.rows; i++) {\n this.insertRow();\n }\n this.parent.appendChild(this.element);\n\n this.charMeasure = new CharMeasure(document, this.helperContainer);\n this.charMeasure.on('charsizechanged', function () {\n self.updateCharSizeStyles();\n });\n this.charMeasure.measure();\n\n this.viewport = new Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasure);\n this.renderer = new Renderer(this);\n this.selectionManager = new SelectionManager(\n this, this.buffer.lines, this.rowContainer, this.charMeasure\n );\n this.selectionManager.on('refresh', data => {\n this.renderer.refreshSelection(data.start, data.end);\n });\n this.selectionManager.on('newselection', text => {\n // If there's a new selection, put it into the textarea, focus and select it\n // in order to register it as a selection on the OS. This event is fired\n // only on Linux to enable middle click to paste selection.\n this.textarea.value = text;\n this.textarea.focus();\n this.textarea.select();\n });\n this.on('scroll', () => this.selectionManager.refresh());\n this.viewportElement.addEventListener('scroll', () => this.selectionManager.refresh());\n\n // Setup loop that draws to screen\n this.refresh(0, this.rows - 1);\n\n // Initialize global actions that\n // need to be taken on the document.\n this.initGlobal();\n\n /**\n * Automatic focus functionality.\n * TODO: Default to `false` starting with xterm.js 3.0.\n */\n if (typeof focus == 'undefined') {\n let message = 'You did not pass the `focus` argument in `Terminal.prototype.open()`.\\n';\n\n message += 'The `focus` argument now defaults to `true` but starting with xterm.js 3.0 ';\n message += 'it will default to `false`.';\n\n console.warn(message);\n focus = true;\n }\n\n if (focus) {\n this.focus();\n }\n\n // Listen for mouse events and translate\n // them into terminal mouse protocols.\n this.bindMouse();\n\n /**\n * This event is emitted when terminal has completed opening.\n *\n * @event open\n */\n this.emit('open');\n};\n\n\n/**\n * Attempts to load an add-on using CommonJS or RequireJS (whichever is available).\n * @param {string} addon The name of the addon to load\n * @static\n */\nTerminal.loadAddon = function(addon, callback) {\n if (typeof exports === 'object' && typeof module === 'object') {\n // CommonJS\n return require('./addons/' + addon + '/' + addon);\n } else if (typeof define == 'function') {\n // RequireJS\n return require(['./addons/' + addon + '/' + addon], callback);\n } else {\n console.error('Cannot load a module without a CommonJS or RequireJS environment.');\n return false;\n }\n};\n\n/**\n * Updates the helper CSS class with any changes necessary after the terminal's\n * character width has been changed.\n */\nTerminal.prototype.updateCharSizeStyles = function() {\n this.charSizeStyleElement.textContent =\n `.xterm-wide-char{width:${this.charMeasure.width * 2}px;}` +\n `.xterm-normal-char{width:${this.charMeasure.width}px;}` +\n `.xterm-rows > div{height:${this.charMeasure.height}px;}`;\n}\n\n/**\n * XTerm mouse events\n * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking\n * To better understand these\n * the xterm code is very helpful:\n * Relevant files:\n * button.c, charproc.c, misc.c\n * Relevant functions in xterm/button.c:\n * BtnCode, EmitButtonCode, EditorButton, SendMousePosition\n */\nTerminal.prototype.bindMouse = function() {\n var el = this.element, self = this, pressed = 32;\n\n // mouseup, mousedown, wheel\n // left click: ^[[M 3<^[[M#3<\n // wheel up: ^[[M`3>\n function sendButton(ev) {\n var button\n , pos;\n\n // get the xterm-style button\n button = getButton(ev);\n\n // get mouse coordinates\n pos = getRawByteCoords(ev, self.rowContainer, self.charMeasure, self.cols, self.rows);\n if (!pos) return;\n\n sendEvent(button, pos);\n\n switch (ev.overrideType || ev.type) {\n case 'mousedown':\n pressed = button;\n break;\n case 'mouseup':\n // keep it at the left\n // button, just in case.\n pressed = 32;\n break;\n case 'wheel':\n // nothing. don't\n // interfere with\n // `pressed`.\n break;\n }\n }\n\n // motion example of a left click:\n // ^[[M 3<^[[M@4<^[[M@5<^[[M@6<^[[M@7<^[[M#7<\n function sendMove(ev) {\n var button = pressed\n , pos;\n\n pos = getRawByteCoords(ev, self.rowContainer, self.charMeasure, self.cols, self.rows);\n if (!pos) return;\n\n // buttons marked as motions\n // are incremented by 32\n button += 32;\n\n sendEvent(button, pos);\n }\n\n // encode button and\n // position to characters\n function encode(data, ch) {\n if (!self.utfMouse) {\n if (ch === 255) return data.push(0);\n if (ch > 127) ch = 127;\n data.push(ch);\n } else {\n if (ch === 2047) return data.push(0);\n if (ch < 127) {\n data.push(ch);\n } else {\n if (ch > 2047) ch = 2047;\n data.push(0xC0 | (ch >> 6));\n data.push(0x80 | (ch & 0x3F));\n }\n }\n }\n\n // send a mouse event:\n // regular/utf8: ^[[M Cb Cx Cy\n // urxvt: ^[[ Cb ; Cx ; Cy M\n // sgr: ^[[ Cb ; Cx ; Cy M/m\n // vt300: ^[[ 24(1/3/5)~ [ Cx , Cy ] \\r\n // locator: CSI P e ; P b ; P r ; P c ; P p & w\n function sendEvent(button, pos) {\n // self.emit('mouse', {\n // x: pos.x - 32,\n // y: pos.x - 32,\n // button: button\n // });\n\n if (self.vt300Mouse) {\n // NOTE: Unstable.\n // http://www.vt100.net/docs/vt3xx-gp/chapter15.html\n button &= 3;\n pos.x -= 32;\n pos.y -= 32;\n var data = C0.ESC + '[24';\n if (button === 0) data += '1';\n else if (button === 1) data += '3';\n else if (button === 2) data += '5';\n else if (button === 3) return;\n else data += '0';\n data += '~[' + pos.x + ',' + pos.y + ']\\r';\n self.send(data);\n return;\n }\n\n if (self.decLocator) {\n // NOTE: Unstable.\n button &= 3;\n pos.x -= 32;\n pos.y -= 32;\n if (button === 0) button = 2;\n else if (button === 1) button = 4;\n else if (button === 2) button = 6;\n else if (button === 3) button = 3;\n self.send(C0.ESC + '['\n + button\n + ';'\n + (button === 3 ? 4 : 0)\n + ';'\n + pos.y\n + ';'\n + pos.x\n + ';'\n + (pos.page || 0)\n + '&w');\n return;\n }\n\n if (self.urxvtMouse) {\n pos.x -= 32;\n pos.y -= 32;\n pos.x++;\n pos.y++;\n self.send(C0.ESC + '[' + button + ';' + pos.x + ';' + pos.y + 'M');\n return;\n }\n\n if (self.sgrMouse) {\n pos.x -= 32;\n pos.y -= 32;\n self.send(C0.ESC + '[<'\n + (((button & 3) === 3 ? button & ~3 : button) - 32)\n + ';'\n + pos.x\n + ';'\n + pos.y\n + ((button & 3) === 3 ? 'm' : 'M'));\n return;\n }\n\n var data = [];\n\n encode(data, button);\n encode(data, pos.x);\n encode(data, pos.y);\n\n self.send(C0.ESC + '[M' + String.fromCharCode.apply(String, data));\n }\n\n function getButton(ev) {\n var button\n , shift\n , meta\n , ctrl\n , mod;\n\n // two low bits:\n // 0 = left\n // 1 = middle\n // 2 = right\n // 3 = release\n // wheel up/down:\n // 1, and 2 - with 64 added\n switch (ev.overrideType || ev.type) {\n case 'mousedown':\n button = ev.button != null\n ? +ev.button\n : ev.which != null\n ? ev.which - 1\n : null;\n\n if (self.browser.isMSIE) {\n button = button === 1 ? 0 : button === 4 ? 1 : button;\n }\n break;\n case 'mouseup':\n button = 3;\n break;\n case 'DOMMouseScroll':\n button = ev.detail < 0\n ? 64\n : 65;\n break;\n case 'wheel':\n button = ev.wheelDeltaY > 0\n ? 64\n : 65;\n break;\n }\n\n // next three bits are the modifiers:\n // 4 = shift, 8 = meta, 16 = control\n shift = ev.shiftKey ? 4 : 0;\n meta = ev.metaKey ? 8 : 0;\n ctrl = ev.ctrlKey ? 16 : 0;\n mod = shift | meta | ctrl;\n\n // no mods\n if (self.vt200Mouse) {\n // ctrl only\n mod &= ctrl;\n } else if (!self.normalMouse) {\n mod = 0;\n }\n\n // increment to SP\n button = (32 + (mod << 2)) + button;\n\n return button;\n }\n\n on(el, 'mousedown', function(ev) {\n\n // Prevent the focus on the textarea from getting lost\n // and make sure we get focused on mousedown\n ev.preventDefault();\n self.focus();\n\n if (!self.mouseEvents) return;\n\n // send the button\n sendButton(ev);\n\n // fix for odd bug\n //if (self.vt200Mouse && !self.normalMouse) {\n if (self.vt200Mouse) {\n ev.overrideType = 'mouseup';\n sendButton(ev);\n return self.cancel(ev);\n }\n\n // bind events\n if (self.normalMouse) on(self.document, 'mousemove', sendMove);\n\n // x10 compatibility mode can't send button releases\n if (!self.x10Mouse) {\n on(self.document, 'mouseup', function up(ev) {\n sendButton(ev);\n if (self.normalMouse) off(self.document, 'mousemove', sendMove);\n off(self.document, 'mouseup', up);\n return self.cancel(ev);\n });\n }\n\n return self.cancel(ev);\n });\n\n //if (self.normalMouse) {\n // on(self.document, 'mousemove', sendMove);\n //}\n\n on(el, 'wheel', function(ev) {\n if (!self.mouseEvents) return;\n if (self.x10Mouse\n || self.vt300Mouse\n || self.decLocator) return;\n sendButton(ev);\n return self.cancel(ev);\n });\n\n // allow wheel scrolling in\n // the shell for example\n on(el, 'wheel', function(ev) {\n if (self.mouseEvents) return;\n self.viewport.onWheel(ev);\n return self.cancel(ev);\n });\n\n on(el, 'touchstart', function(ev) {\n if (self.mouseEvents) return;\n self.viewport.onTouchStart(ev);\n return self.cancel(ev);\n });\n\n on(el, 'touchmove', function(ev) {\n if (self.mouseEvents) return;\n self.viewport.onTouchMove(ev);\n return self.cancel(ev);\n });\n};\n\n/**\n * Destroys the terminal.\n */\nTerminal.prototype.destroy = function() {\n this.readable = false;\n this.writable = false;\n this._events = {};\n this.handler = function() {};\n this.write = function() {};\n if (this.element && this.element.parentNode) {\n this.element.parentNode.removeChild(this.element);\n }\n //this.emit('close');\n};\n\n/**\n * Tells the renderer to refresh terminal content between two rows (inclusive) at the next\n * opportunity.\n * @param {number} start The row to start from (between 0 and this.rows - 1).\n * @param {number} end The row to end at (between start and this.rows - 1).\n */\nTerminal.prototype.refresh = function(start, end) {\n if (this.renderer) {\n this.renderer.queueRefresh(start, end);\n }\n};\n\n/**\n * Queues linkification for the specified rows.\n * @param {number} start The row to start from (between 0 and this.rows - 1).\n * @param {number} end The row to end at (between start and this.rows - 1).\n */\nTerminal.prototype.queueLinkification = function(start, end) {\n if (this.linkifier) {\n for (let i = start; i <= end; i++) {\n this.linkifier.linkifyRow(i);\n }\n }\n};\n\n/**\n * Display the cursor element\n */\nTerminal.prototype.showCursor = function() {\n if (!this.cursorState) {\n this.cursorState = 1;\n this.refresh(this.buffer.y, this.buffer.y);\n }\n};\n\n/**\n * Scroll the terminal down 1 row, creating a blank line.\n * @param {boolean} isWrapped Whether the new line is wrapped from the previous\n * line.\n */\nTerminal.prototype.scroll = function(isWrapped) {\n var row;\n\n // Make room for the new row in lines\n if (this.buffer.lines.length === this.buffer.lines.maxLength) {\n this.buffer.lines.trimStart(1);\n this.buffer.ybase--;\n if (this.buffer.ydisp !== 0) {\n this.buffer.ydisp--;\n }\n }\n\n this.buffer.ybase++;\n\n // TODO: Why is this done twice?\n if (!this.userScrolling) {\n this.buffer.ydisp = this.buffer.ybase;\n }\n\n // last line\n row = this.buffer.ybase + this.rows - 1;\n\n // subtract the bottom scroll region\n row -= this.rows - 1 - this.buffer.scrollBottom;\n\n if (row === this.buffer.lines.length) {\n // Optimization: pushing is faster than splicing when they amount to the same behavior\n this.buffer.lines.push(this.blankLine(undefined, isWrapped));\n } else {\n // add our new line\n this.buffer.lines.splice(row, 0, this.blankLine(undefined, isWrapped));\n }\n\n if (this.buffer.scrollTop !== 0) {\n if (this.buffer.ybase !== 0) {\n this.buffer.ybase--;\n if (!this.userScrolling) {\n this.buffer.ydisp = this.buffer.ybase;\n }\n }\n this.buffer.lines.splice(this.buffer.ybase + this.buffer.scrollTop, 1);\n }\n\n // this.maxRange();\n this.updateRange(this.buffer.scrollTop);\n this.updateRange(this.buffer.scrollBottom);\n\n /**\n * This event is emitted whenever the terminal is scrolled.\n * The one parameter passed is the new y display position.\n *\n * @event scroll\n */\n this.emit('scroll', this.buffer.ydisp);\n};\n\n/**\n * Scroll the display of the terminal\n * @param {number} disp The number of lines to scroll down (negatives scroll up).\n * @param {boolean} suppressScrollEvent Don't emit the scroll event as scrollDisp. This is used\n * to avoid unwanted events being handled by the veiwport when the event was triggered from the\n * viewport originally.\n */\nTerminal.prototype.scrollDisp = function(disp, suppressScrollEvent) {\n if (disp < 0) {\n if (this.buffer.ydisp === 0) {\n return;\n }\n this.userScrolling = true;\n } else if (disp + this.buffer.ydisp >= this.buffer.ybase) {\n this.userScrolling = false;\n }\n\n const oldYdisp = this.buffer.ydisp;\n this.buffer.ydisp = Math.max(Math.min(this.buffer.ydisp + disp, this.buffer.ybase), 0);\n\n // No change occurred, don't trigger scroll/refresh\n if (oldYdisp === this.buffer.ydisp) {\n return;\n }\n\n if (!suppressScrollEvent) {\n this.emit('scroll', this.buffer.ydisp);\n }\n\n this.refresh(0, this.rows - 1);\n};\n\n/**\n * Scroll the display of the terminal by a number of pages.\n * @param {number} pageCount The number of pages to scroll (negative scrolls up).\n */\nTerminal.prototype.scrollPages = function(pageCount) {\n this.scrollDisp(pageCount * (this.rows - 1));\n};\n\n/**\n * Scrolls the display of the terminal to the top.\n */\nTerminal.prototype.scrollToTop = function() {\n this.scrollDisp(-this.buffer.ydisp);\n};\n\n/**\n * Scrolls the display of the terminal to the bottom.\n */\nTerminal.prototype.scrollToBottom = function() {\n this.scrollDisp(this.buffer.ybase - this.buffer.ydisp);\n};\n\n/**\n * Writes text to the terminal.\n * @param {string} data The text to write to the terminal.\n */\nTerminal.prototype.write = function(data) {\n this.writeBuffer.push(data);\n\n // Send XOFF to pause the pty process if the write buffer becomes too large so\n // xterm.js can catch up before more data is sent. This is necessary in order\n // to keep signals such as ^C responsive.\n if (this.options.useFlowControl && !this.xoffSentToCatchUp && this.writeBuffer.length >= WRITE_BUFFER_PAUSE_THRESHOLD) {\n // XOFF - stop pty pipe\n // XON will be triggered by emulator before processing data chunk\n this.send(C0.DC3);\n this.xoffSentToCatchUp = true;\n }\n\n if (!this.writeInProgress && this.writeBuffer.length > 0) {\n // Kick off a write which will write all data in sequence recursively\n this.writeInProgress = true;\n // Kick off an async innerWrite so more writes can come in while processing data\n var self = this;\n setTimeout(function () {\n self.innerWrite();\n });\n }\n};\n\nTerminal.prototype.innerWrite = function() {\n var writeBatch = this.writeBuffer.splice(0, WRITE_BATCH_SIZE);\n while (writeBatch.length > 0) {\n var data = writeBatch.shift();\n var l = data.length, i = 0, j, cs, ch, code, low, ch_width, row;\n\n // If XOFF was sent in order to catch up with the pty process, resume it if\n // the writeBuffer is empty to allow more data to come in.\n if (this.xoffSentToCatchUp && writeBatch.length === 0 && this.writeBuffer.length === 0) {\n this.send(C0.DC1);\n this.xoffSentToCatchUp = false;\n }\n\n this.refreshStart = this.buffer.y;\n this.refreshEnd = this.buffer.y;\n\n // HACK: Set the parser state based on it's state at the time of return.\n // This works around the bug #662 which saw the parser state reset in the\n // middle of parsing escape sequence in two chunks. For some reason the\n // state of the parser resets to 0 after exiting parser.parse. This change\n // just sets the state back based on the correct return statement.\n var state = this.parser.parse(data);\n this.parser.setState(state);\n\n this.updateRange(this.buffer.y);\n this.refresh(this.refreshStart, this.refreshEnd);\n }\n if (this.writeBuffer.length > 0) {\n // Allow renderer to catch up before processing the next batch\n var self = this;\n setTimeout(function () {\n self.innerWrite();\n }, 0);\n } else {\n this.writeInProgress = false;\n }\n};\n\n/**\n * Writes text to the terminal, followed by a break line character (\\n).\n * @param {string} data The text to write to the terminal.\n */\nTerminal.prototype.writeln = function(data) {\n this.write(data + '\\r\\n');\n};\n\n/**\n * DEPRECATED: only for backward compatibility. Please use attachCustomKeyEventHandler() instead.\n * @param {function} customKeydownHandler The custom KeyboardEvent handler to attach. This is a\n * function that takes a KeyboardEvent, allowing consumers to stop propogation and/or prevent\n * the default action. The function returns whether the event should be processed by xterm.js.\n */\nTerminal.prototype.attachCustomKeydownHandler = function(customKeydownHandler) {\n let message = 'attachCustomKeydownHandler() is DEPRECATED and will be removed soon. Please use attachCustomKeyEventHandler() instead.';\n console.warn(message);\n this.attachCustomKeyEventHandler(customKeydownHandler);\n};\n\n/**\n * Attaches a custom key event handler which is run before keys are processed, giving consumers of\n * xterm.js ultimate control as to what keys should be processed by the terminal and what keys\n * should not.\n * @param {function} customKeyEventHandler The custom KeyboardEvent handler to attach. This is a\n * function that takes a KeyboardEvent, allowing consumers to stop propogation and/or prevent\n * the default action. The function returns whether the event should be processed by xterm.js.\n */\nTerminal.prototype.attachCustomKeyEventHandler = function(customKeyEventHandler) {\n this.customKeyEventHandler = customKeyEventHandler;\n};\n\n/**\n * Attaches a http(s) link handler, forcing web links to behave differently to\n * regular tags. This will trigger a refresh as links potentially need to be\n * reconstructed. Calling this with null will remove the handler.\n * @param {LinkMatcherHandler} handler The handler callback function.\n */\nTerminal.prototype.setHypertextLinkHandler = function(handler) {\n if (!this.linkifier) {\n throw new Error('Cannot attach a hypertext link handler before Terminal.open is called');\n }\n this.linkifier.setHypertextLinkHandler(handler);\n // Refresh to force links to refresh\n this.refresh(0, this.rows - 1);\n};\n\n/**\n * Attaches a validation callback for hypertext links. This is useful to use\n * validation logic or to do something with the link's element and url.\n * @param {LinkMatcherValidationCallback} callback The callback to use, this can\n * be cleared with null.\n */\nTerminal.prototype.setHypertextValidationCallback = function(callback) {\n if (!this.linkifier) {\n throw new Error('Cannot attach a hypertext validation callback before Terminal.open is called');\n }\n this.linkifier.setHypertextValidationCallback(callback);\n // Refresh to force links to refresh\n this.refresh(0, this.rows - 1);\n};\n\n/**\n * Registers a link matcher, allowing custom link patterns to be matched and\n * handled.\n * @param {RegExp} regex The regular expression to search for, specifically\n * this searches the textContent of the rows. You will want to use \\s to match\n * a space ' ' character for example.\n * @param {LinkMatcherHandler} handler The callback when the link is called.\n * @param {LinkMatcherOptions} [options] Options for the link matcher.\n * @return {number} The ID of the new matcher, this can be used to deregister.\n */\nTerminal.prototype.registerLinkMatcher = function(regex, handler, options) {\n if (this.linkifier) {\n var matcherId = this.linkifier.registerLinkMatcher(regex, handler, options);\n this.refresh(0, this.rows - 1);\n return matcherId;\n }\n};\n\n/**\n * Deregisters a link matcher if it has been registered.\n * @param {number} matcherId The link matcher's ID (returned after register)\n */\nTerminal.prototype.deregisterLinkMatcher = function(matcherId) {\n if (this.linkifier) {\n if (this.linkifier.deregisterLinkMatcher(matcherId)) {\n this.refresh(0, this.rows - 1);\n }\n }\n};\n\n/**\n * Gets whether the terminal has an active selection.\n */\nTerminal.prototype.hasSelection = function() {\n return this.selectionManager ? this.selectionManager.hasSelection : false;\n};\n\n/**\n * Gets the terminal's current selection, this is useful for implementing copy\n * behavior outside of xterm.js.\n */\nTerminal.prototype.getSelection = function() {\n return this.selectionManager ? this.selectionManager.selectionText : '';\n};\n\n/**\n * Clears the current terminal selection.\n */\nTerminal.prototype.clearSelection = function() {\n if (this.selectionManager) {\n this.selectionManager.clearSelection();\n }\n};\n\n/**\n * Selects all text within the terminal.\n */\nTerminal.prototype.selectAll = function() {\n if (this.selectionManager) {\n this.selectionManager.selectAll();\n }\n};\n\n/**\n * Handle a keydown event\n * Key Resources:\n * - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent\n * @param {KeyboardEvent} ev The keydown event to be handled.\n */\nTerminal.prototype.keyDown = function(ev) {\n if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) {\n return false;\n }\n\n this.restartCursorBlinking();\n\n if (!this.compositionHelper.keydown.bind(this.compositionHelper)(ev)) {\n if (this.buffer.ybase !== this.buffer.ydisp) {\n this.scrollToBottom();\n }\n return false;\n }\n\n var self = this;\n var result = this.evaluateKeyEscapeSequence(ev);\n\n if (result.key === C0.DC3) { // XOFF\n this.writeStopped = true;\n } else if (result.key === C0.DC1) { // XON\n this.writeStopped = false;\n }\n\n if (result.scrollDisp) {\n this.scrollDisp(result.scrollDisp);\n return this.cancel(ev, true);\n }\n\n if (isThirdLevelShift(this, ev)) {\n return true;\n }\n\n if (result.cancel) {\n // The event is canceled at the end already, is this necessary?\n this.cancel(ev, true);\n }\n\n if (!result.key) {\n return true;\n }\n\n this.emit('keydown', ev);\n this.emit('key', result.key, ev);\n this.showCursor();\n this.handler(result.key);\n\n return this.cancel(ev, true);\n};\n\n/**\n * Returns an object that determines how a KeyboardEvent should be handled. The key of the\n * returned value is the new key code to pass to the PTY.\n *\n * Reference: http://invisible-island.net/xterm/ctlseqs/ctlseqs.html\n * @param {KeyboardEvent} ev The keyboard event to be translated to key escape sequence.\n */\nTerminal.prototype.evaluateKeyEscapeSequence = function(ev) {\n var result = {\n // Whether to cancel event propogation (NOTE: this may not be needed since the event is\n // canceled at the end of keyDown\n cancel: false,\n // The new key even to emit\n key: undefined,\n // The number of characters to scroll, if this is defined it will cancel the event\n scrollDisp: undefined\n };\n var modifiers = ev.shiftKey << 0 | ev.altKey << 1 | ev.ctrlKey << 2 | ev.metaKey << 3;\n switch (ev.keyCode) {\n case 8:\n // backspace\n if (ev.shiftKey) {\n result.key = C0.BS; // ^H\n break;\n }\n result.key = C0.DEL; // ^?\n break;\n case 9:\n // tab\n if (ev.shiftKey) {\n result.key = C0.ESC + '[Z';\n break;\n }\n result.key = C0.HT;\n result.cancel = true;\n break;\n case 13:\n // return/enter\n result.key = C0.CR;\n result.cancel = true;\n break;\n case 27:\n // escape\n result.key = C0.ESC;\n result.cancel = true;\n break;\n case 37:\n // left-arrow\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'D';\n // HACK: Make Alt + left-arrow behave like Ctrl + left-arrow: move one word backwards\n // http://unix.stackexchange.com/a/108106\n // macOS uses different escape sequences than linux\n if (result.key == C0.ESC + '[1;3D') {\n result.key = (this.browser.isMac) ? C0.ESC + 'b' : C0.ESC + '[1;5D';\n }\n } else if (this.applicationCursor) {\n result.key = C0.ESC + 'OD';\n } else {\n result.key = C0.ESC + '[D';\n }\n break;\n case 39:\n // right-arrow\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'C';\n // HACK: Make Alt + right-arrow behave like Ctrl + right-arrow: move one word forward\n // http://unix.stackexchange.com/a/108106\n // macOS uses different escape sequences than linux\n if (result.key == C0.ESC + '[1;3C') {\n result.key = (this.browser.isMac) ? C0.ESC + 'f' : C0.ESC + '[1;5C';\n }\n } else if (this.applicationCursor) {\n result.key = C0.ESC + 'OC';\n } else {\n result.key = C0.ESC + '[C';\n }\n break;\n case 38:\n // up-arrow\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'A';\n // HACK: Make Alt + up-arrow behave like Ctrl + up-arrow\n // http://unix.stackexchange.com/a/108106\n if (result.key == C0.ESC + '[1;3A') {\n result.key = C0.ESC + '[1;5A';\n }\n } else if (this.applicationCursor) {\n result.key = C0.ESC + 'OA';\n } else {\n result.key = C0.ESC + '[A';\n }\n break;\n case 40:\n // down-arrow\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'B';\n // HACK: Make Alt + down-arrow behave like Ctrl + down-arrow\n // http://unix.stackexchange.com/a/108106\n if (result.key == C0.ESC + '[1;3B') {\n result.key = C0.ESC + '[1;5B';\n }\n } else if (this.applicationCursor) {\n result.key = C0.ESC + 'OB';\n } else {\n result.key = C0.ESC + '[B';\n }\n break;\n case 45:\n // insert\n if (!ev.shiftKey && !ev.ctrlKey) {\n // or + are used to\n // copy-paste on some systems.\n result.key = C0.ESC + '[2~';\n }\n break;\n case 46:\n // delete\n if (modifiers) {\n result.key = C0.ESC + '[3;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[3~';\n }\n break;\n case 36:\n // home\n if (modifiers)\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'H';\n else if (this.applicationCursor)\n result.key = C0.ESC + 'OH';\n else\n result.key = C0.ESC + '[H';\n break;\n case 35:\n // end\n if (modifiers)\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'F';\n else if (this.applicationCursor)\n result.key = C0.ESC + 'OF';\n else\n result.key = C0.ESC + '[F';\n break;\n case 33:\n // page up\n if (ev.shiftKey) {\n result.scrollDisp = -(this.rows - 1);\n } else {\n result.key = C0.ESC + '[5~';\n }\n break;\n case 34:\n // page down\n if (ev.shiftKey) {\n result.scrollDisp = this.rows - 1;\n } else {\n result.key = C0.ESC + '[6~';\n }\n break;\n case 112:\n // F1-F12\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'P';\n } else {\n result.key = C0.ESC + 'OP';\n }\n break;\n case 113:\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'Q';\n } else {\n result.key = C0.ESC + 'OQ';\n }\n break;\n case 114:\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'R';\n } else {\n result.key = C0.ESC + 'OR';\n }\n break;\n case 115:\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'S';\n } else {\n result.key = C0.ESC + 'OS';\n }\n break;\n case 116:\n if (modifiers) {\n result.key = C0.ESC + '[15;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[15~';\n }\n break;\n case 117:\n if (modifiers) {\n result.key = C0.ESC + '[17;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[17~';\n }\n break;\n case 118:\n if (modifiers) {\n result.key = C0.ESC + '[18;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[18~';\n }\n break;\n case 119:\n if (modifiers) {\n result.key = C0.ESC + '[19;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[19~';\n }\n break;\n case 120:\n if (modifiers) {\n result.key = C0.ESC + '[20;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[20~';\n }\n break;\n case 121:\n if (modifiers) {\n result.key = C0.ESC + '[21;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[21~';\n }\n break;\n case 122:\n if (modifiers) {\n result.key = C0.ESC + '[23;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[23~';\n }\n break;\n case 123:\n if (modifiers) {\n result.key = C0.ESC + '[24;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[24~';\n }\n break;\n default:\n // a-z and space\n if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) {\n if (ev.keyCode >= 65 && ev.keyCode <= 90) {\n result.key = String.fromCharCode(ev.keyCode - 64);\n } else if (ev.keyCode === 32) {\n // NUL\n result.key = String.fromCharCode(0);\n } else if (ev.keyCode >= 51 && ev.keyCode <= 55) {\n // escape, file sep, group sep, record sep, unit sep\n result.key = String.fromCharCode(ev.keyCode - 51 + 27);\n } else if (ev.keyCode === 56) {\n // delete\n result.key = String.fromCharCode(127);\n } else if (ev.keyCode === 219) {\n // ^[ - Control Sequence Introducer (CSI)\n result.key = String.fromCharCode(27);\n } else if (ev.keyCode === 220) {\n // ^\\ - String Terminator (ST)\n result.key = String.fromCharCode(28);\n } else if (ev.keyCode === 221) {\n // ^] - Operating System Command (OSC)\n result.key = String.fromCharCode(29);\n }\n } else if (!this.browser.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) {\n // On Mac this is a third level shift. Use instead.\n if (ev.keyCode >= 65 && ev.keyCode <= 90) {\n result.key = C0.ESC + String.fromCharCode(ev.keyCode + 32);\n } else if (ev.keyCode === 192) {\n result.key = C0.ESC + '`';\n } else if (ev.keyCode >= 48 && ev.keyCode <= 57) {\n result.key = C0.ESC + (ev.keyCode - 48);\n }\n } else if (this.browser.isMac && !ev.altKey && !ev.ctrlKey && ev.metaKey) {\n if (ev.keyCode === 65) { // cmd + a\n this.selectAll();\n }\n }\n break;\n }\n\n return result;\n};\n\n/**\n * Set the G level of the terminal\n * @param g\n */\nTerminal.prototype.setgLevel = function(g) {\n this.glevel = g;\n this.charset = this.charsets[g];\n};\n\n/**\n * Set the charset for the given G level of the terminal\n * @param g\n * @param charset\n */\nTerminal.prototype.setgCharset = function(g, charset) {\n this.charsets[g] = charset;\n if (this.glevel === g) {\n this.charset = charset;\n }\n};\n\n/**\n * Handle a keypress event.\n * Key Resources:\n * - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent\n * @param {KeyboardEvent} ev The keypress event to be handled.\n */\nTerminal.prototype.keyPress = function(ev) {\n var key;\n\n if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) {\n return false;\n }\n\n this.cancel(ev);\n\n if (ev.charCode) {\n key = ev.charCode;\n } else if (ev.which == null) {\n key = ev.keyCode;\n } else if (ev.which !== 0 && ev.charCode !== 0) {\n key = ev.which;\n } else {\n return false;\n }\n\n if (!key || (\n (ev.altKey || ev.ctrlKey || ev.metaKey) && !isThirdLevelShift(this, ev)\n )) {\n return false;\n }\n\n key = String.fromCharCode(key);\n\n this.emit('keypress', key, ev);\n this.emit('key', key, ev);\n this.showCursor();\n this.handler(key);\n\n return true;\n};\n\n/**\n * Send data for handling to the terminal\n * @param {string} data\n */\nTerminal.prototype.send = function(data) {\n var self = this;\n\n if (!this.queue) {\n setTimeout(function() {\n self.handler(self.queue);\n self.queue = '';\n }, 1);\n }\n\n this.queue += data;\n};\n\n/**\n * Ring the bell.\n * Note: We could do sweet things with webaudio here\n */\nTerminal.prototype.bell = function() {\n if (!this.visualBell) return;\n var self = this;\n this.element.style.borderColor = 'white';\n setTimeout(function() {\n self.element.style.borderColor = '';\n }, 10);\n if (this.popOnBell) this.focus();\n};\n\n/**\n * Log the current state to the console.\n */\nTerminal.prototype.log = function() {\n if (!this.debug) return;\n if (!this.context.console || !this.context.console.log) return;\n var args = Array.prototype.slice.call(arguments);\n this.context.console.log.apply(this.context.console, args);\n};\n\n/**\n * Log the current state as error to the console.\n */\nTerminal.prototype.error = function() {\n if (!this.debug) return;\n if (!this.context.console || !this.context.console.error) return;\n var args = Array.prototype.slice.call(arguments);\n this.context.console.error.apply(this.context.console, args);\n};\n\n/**\n * Resizes the terminal.\n *\n * @param {number} x The number of columns to resize to.\n * @param {number} y The number of rows to resize to.\n */\nTerminal.prototype.resize = function(x, y) {\n if (isNaN(x) || isNaN(y)) {\n return;\n }\n\n if (y > this.getOption('scrollback')) {\n this.setOption('scrollback', y)\n }\n\n var line\n , el\n , i\n , j\n , ch\n , addToY;\n\n if (x === this.cols && y === this.rows) {\n // Check if we still need to measure the char size (fixes #785).\n if (!this.charMeasure.width || !this.charMeasure.height) {\n this.charMeasure.measure();\n }\n return;\n }\n\n if (x < 1) x = 1;\n if (y < 1) y = 1;\n\n this.buffers.resize(x, y);\n\n // Adjust rows in the DOM to accurately reflect the new dimensions\n while (this.children.length < y) {\n this.insertRow();\n }\n while (this.children.length > y) {\n el = this.children.shift();\n if (!el) continue;\n el.parentNode.removeChild(el);\n }\n\n this.cols = x;\n this.rows = y;\n this.setupStops(this.cols);\n\n this.charMeasure.measure();\n\n this.refresh(0, this.rows - 1);\n\n this.geometry = [this.cols, this.rows];\n this.emit('resize', {terminal: this, cols: x, rows: y});\n};\n\n/**\n * Updates the range of rows to refresh\n * @param {number} y The number of rows to refresh next.\n */\nTerminal.prototype.updateRange = function(y) {\n if (y < this.refreshStart) this.refreshStart = y;\n if (y > this.refreshEnd) this.refreshEnd = y;\n // if (y > this.refreshEnd) {\n // this.refreshEnd = y;\n // if (y > this.rows - 1) {\n // this.refreshEnd = this.rows - 1;\n // }\n // }\n};\n\n/**\n * Set the range of refreshing to the maximum value\n */\nTerminal.prototype.maxRange = function() {\n this.refreshStart = 0;\n this.refreshEnd = this.rows - 1;\n};\n\n\n\n/**\n * Setup the tab stops.\n * @param {number} i\n */\nTerminal.prototype.setupStops = function(i) {\n if (i != null) {\n if (!this.buffer.tabs[i]) {\n i = this.prevStop(i);\n }\n } else {\n this.buffer.tabs = {};\n i = 0;\n }\n\n for (; i < this.cols; i += this.getOption('tabStopWidth')) {\n this.buffer.tabs[i] = true;\n }\n};\n\n\n/**\n * Move the cursor to the previous tab stop from the given position (default is current).\n * @param {number} x The position to move the cursor to the previous tab stop.\n */\nTerminal.prototype.prevStop = function(x) {\n if (x == null) x = this.buffer.x;\n while (!this.buffer.tabs[--x] && x > 0);\n return x >= this.cols\n ? this.cols - 1\n : x < 0 ? 0 : x;\n};\n\n\n/**\n * Move the cursor one tab stop forward from the given position (default is current).\n * @param {number} x The position to move the cursor one tab stop forward.\n */\nTerminal.prototype.nextStop = function(x) {\n if (x == null) x = this.buffer.x;\n while (!this.buffer.tabs[++x] && x < this.cols);\n return x >= this.cols\n ? this.cols - 1\n : x < 0 ? 0 : x;\n};\n\n\n/**\n * Erase in the identified line everything from \"x\" to the end of the line (right).\n * @param {number} x The column from which to start erasing to the end of the line.\n * @param {number} y The line in which to operate.\n */\nTerminal.prototype.eraseRight = function(x, y) {\n var line = this.buffer.lines.get(this.buffer.ybase + y);\n if (!line) {\n return;\n }\n var ch = [this.eraseAttr(), ' ', 1]; // xterm\n for (; x < this.cols; x++) {\n line[x] = ch;\n }\n this.updateRange(y);\n};\n\n\n\n/**\n * Erase in the identified line everything from \"x\" to the start of the line (left).\n * @param {number} x The column from which to start erasing to the start of the line.\n * @param {number} y The line in which to operate.\n */\nTerminal.prototype.eraseLeft = function(x, y) {\n var line = this.buffer.lines.get(this.buffer.ybase + y);\n if (!line) {\n return;\n }\n var ch = [this.eraseAttr(), ' ', 1]; // xterm\n x++;\n while (x--) {\n line[x] = ch;\n }\n this.updateRange(y);\n};\n\n/**\n * Clears the entire buffer, making the prompt line the new first line.\n */\nTerminal.prototype.clear = function() {\n if (this.buffer.ybase === 0 && this.buffer.y === 0) {\n // Don't clear if it's already clear\n return;\n }\n this.buffer.lines.set(0, this.buffer.lines.get(this.buffer.ybase + this.buffer.y));\n this.buffer.lines.length = 1;\n this.buffer.ydisp = 0;\n this.buffer.ybase = 0;\n this.buffer.y = 0;\n for (var i = 1; i < this.rows; i++) {\n this.buffer.lines.push(this.blankLine());\n }\n this.refresh(0, this.rows - 1);\n this.emit('scroll', this.buffer.ydisp);\n};\n\n/**\n * Erase all content in the given line\n * @param {number} y The line to erase all of its contents.\n */\nTerminal.prototype.eraseLine = function(y) {\n this.eraseRight(0, y);\n};\n\n\n/**\n * Return the data array of a blank line\n * @param {number} cur First bunch of data for each \"blank\" character.\n * @param {boolean} isWrapped Whether the new line is wrapped from the previous line.\n */\nTerminal.prototype.blankLine = function(cur, isWrapped) {\n var attr = cur\n ? this.eraseAttr()\n : this.defAttr;\n\n var ch = [attr, ' ', 1] // width defaults to 1 halfwidth character\n , line = []\n , i = 0;\n\n // TODO: It is not ideal that this is a property on an array, a buffer line\n // class should be added that will hold this data and other useful functions.\n if (isWrapped) {\n line.isWrapped = isWrapped;\n }\n\n for (; i < this.cols; i++) {\n line[i] = ch;\n }\n\n return line;\n};\n\n\n/**\n * If cur return the back color xterm feature attribute. Else return defAttr.\n * @param {object} cur\n */\nTerminal.prototype.ch = function(cur) {\n return cur\n ? [this.eraseAttr(), ' ', 1]\n : [this.defAttr, ' ', 1];\n};\n\n\n/**\n * Evaluate if the current terminal is the given argument.\n * @param {object} term The terminal to evaluate\n */\nTerminal.prototype.is = function(term) {\n var name = this.termName;\n return (name + '').indexOf(term) === 0;\n};\n\n\n/**\n * Emit the 'data' event and populate the given data.\n * @param {string} data The data to populate in the event.\n */\nTerminal.prototype.handler = function(data) {\n // Prevents all events to pty process if stdin is disabled\n if (this.options.disableStdin) {\n return;\n }\n\n // Clear the selection if the selection manager is available and has an active selection\n if (this.selectionManager && this.selectionManager.hasSelection) {\n this.selectionManager.clearSelection();\n }\n\n // Input is being sent to the terminal, the terminal should focus the prompt.\n if (this.buffer.ybase !== this.buffer.ydisp) {\n this.scrollToBottom();\n }\n this.emit('data', data);\n};\n\n\n/**\n * Emit the 'title' event and populate the given title.\n * @param {string} title The title to populate in the event.\n */\nTerminal.prototype.handleTitle = function(title) {\n /**\n * This event is emitted when the title of the terminal is changed\n * from inside the terminal. The parameter is the new title.\n *\n * @event title\n */\n this.emit('title', title);\n};\n\n\n/**\n * ESC\n */\n\n/**\n * ESC D Index (IND is 0x84).\n */\nTerminal.prototype.index = function() {\n this.buffer.y++;\n if (this.buffer.y > this.buffer.scrollBottom) {\n this.buffer.y--;\n this.scroll();\n }\n // If the end of the line is hit, prevent this action from wrapping around to the next line.\n if (this.buffer.x >= this.cols) {\n this.buffer.x--;\n }\n};\n\n\n/**\n * ESC M Reverse Index (RI is 0x8d).\n *\n * Move the cursor up one row, inserting a new blank line if necessary.\n */\nTerminal.prototype.reverseIndex = function() {\n var j;\n if (this.buffer.y === this.buffer.scrollTop) {\n // possibly move the code below to term.reverseScroll();\n // test: echo -ne '\\e[1;1H\\e[44m\\eM\\e[0m'\n // blankLine(true) is xterm/linux behavior\n this.buffer.lines.shiftElements(this.buffer.y + this.buffer.ybase, this.rows - 1, 1);\n this.buffer.lines.set(this.buffer.y + this.buffer.ybase, this.blankLine(true));\n this.updateRange(this.buffer.scrollTop);\n this.updateRange(this.buffer.scrollBottom);\n } else {\n this.buffer.y--;\n }\n};\n\n\n/**\n * ESC c Full Reset (RIS).\n */\nTerminal.prototype.reset = function() {\n this.options.rows = this.rows;\n this.options.cols = this.cols;\n var customKeyEventHandler = this.customKeyEventHandler;\n var cursorBlinkInterval = this.cursorBlinkInterval;\n var inputHandler = this.inputHandler;\n Terminal.call(this, this.options);\n this.customKeyEventHandler = customKeyEventHandler;\n this.cursorBlinkInterval = cursorBlinkInterval;\n this.inputHandler = inputHandler;\n this.refresh(0, this.rows - 1);\n this.viewport.syncScrollArea();\n};\n\n\n/**\n * ESC H Tab Set (HTS is 0x88).\n */\nTerminal.prototype.tabSet = function() {\n this.buffer.tabs[this.buffer.x] = true;\n};\n\n/**\n * Helpers\n */\n\nfunction on(el, type, handler, capture) {\n if (!Array.isArray(el)) {\n el = [el];\n }\n el.forEach(function (element) {\n element.addEventListener(type, handler, capture || false);\n });\n}\n\nfunction off(el, type, handler, capture) {\n el.removeEventListener(type, handler, capture || false);\n}\n\nfunction cancel(ev, force) {\n if (!this.cancelEvents && !force) {\n return;\n }\n ev.preventDefault();\n ev.stopPropagation();\n return false;\n}\n\nfunction inherits(child, parent) {\n function f() {\n this.constructor = child;\n }\n f.prototype = parent.prototype;\n child.prototype = new f;\n}\n\nfunction indexOf(obj, el) {\n var i = obj.length;\n while (i--) {\n if (obj[i] === el) return i;\n }\n return -1;\n}\n\nfunction isThirdLevelShift(term, ev) {\n var thirdLevelKey =\n (term.browser.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) ||\n (term.browser.isMSWindows && ev.altKey && ev.ctrlKey && !ev.metaKey);\n\n if (ev.type == 'keypress') {\n return thirdLevelKey;\n }\n\n // Don't invoke for arrows, pageDown, home, backspace, etc. (on non-keypress events)\n return thirdLevelKey && (!ev.keyCode || ev.keyCode > 47);\n}\n\n// Expose to InputHandler (temporary)\nTerminal.prototype.matchColor = matchColor;\n\nfunction matchColor(r1, g1, b1) {\n var hash = (r1 << 16) | (g1 << 8) | b1;\n\n if (matchColor._cache[hash] != null) {\n return matchColor._cache[hash];\n }\n\n var ldiff = Infinity\n , li = -1\n , i = 0\n , c\n , r2\n , g2\n , b2\n , diff;\n\n for (; i < Terminal.vcolors.length; i++) {\n c = Terminal.vcolors[i];\n r2 = c[0];\n g2 = c[1];\n b2 = c[2];\n\n diff = matchColor.distance(r1, g1, b1, r2, g2, b2);\n\n if (diff === 0) {\n li = i;\n break;\n }\n\n if (diff < ldiff) {\n ldiff = diff;\n li = i;\n }\n }\n\n return matchColor._cache[hash] = li;\n}\n\nmatchColor._cache = {};\n\n// http://stackoverflow.com/questions/1633828\nmatchColor.distance = function(r1, g1, b1, r2, g2, b2) {\n return Math.pow(30 * (r1 - r2), 2)\n + Math.pow(59 * (g1 - g2), 2)\n + Math.pow(11 * (b1 - b2), 2);\n};\n\nfunction each(obj, iter, con) {\n if (obj.forEach) return obj.forEach(iter, con);\n for (var i = 0; i < obj.length; i++) {\n iter.call(con, obj[i], i, obj);\n }\n}\n\nfunction wasMondifierKeyOnlyEvent(ev) {\n return ev.keyCode === 16 || // Shift\n ev.keyCode === 17 || // Ctrl\n ev.keyCode === 18; // Alt\n}\n\nfunction keys(obj) {\n if (Object.keys) return Object.keys(obj);\n var key, keys = [];\n for (key in obj) {\n if (Object.prototype.hasOwnProperty.call(obj, key)) {\n keys.push(key);\n }\n }\n return keys;\n}\n\n/**\n * Expose\n */\n\nTerminal.translateBufferLineToString = translateBufferLineToString;\nTerminal.EventEmitter = EventEmitter;\nTerminal.inherits = inherits;\n\n/**\n * Adds an event listener to the terminal.\n *\n * @param {string} event The name of the event. TODO: Document all event types\n * @param {function} callback The function to call when the event is triggered.\n */\nTerminal.on = on;\nTerminal.off = off;\nTerminal.cancel = cancel;\n\nmodule.exports = Terminal;\n","/**\n * @license MIT\n */\n\nimport { CharMeasure } from './CharMeasure';\n\nexport function getCoordsRelativeToElement(event: MouseEvent, element: HTMLElement): [number, number] {\n // Ignore browsers that don't support MouseEvent.pageX\n if (event.pageX == null) {\n return null;\n }\n\n let x = event.pageX;\n let y = event.pageY;\n\n // Converts the coordinates from being relative to the document to being\n // relative to the terminal.\n while (element && element !== self.document.documentElement) {\n x -= element.offsetLeft;\n y -= element.offsetTop;\n element = 'offsetParent' in element ? element.offsetParent : element.parentElement;\n }\n return [x, y];\n}\n\n/**\n * Gets coordinates within the terminal for a particular mouse event. The result\n * is returned as an array in the form [x, y] instead of an object as it's a\n * little faster and this function is used in some low level code.\n * @param event The mouse event.\n * @param rowContainer The terminal's row container.\n * @param charMeasure The char measure object used to determine character sizes.\n * @param colCount The number of columns in the terminal.\n * @param rowCount The number of rows n the terminal.\n * @param isSelection Whether the request is for the selection or not. This will\n * apply an offset to the x value such that the left half of the cell will\n * select that cell and the right half will select the next cell.\n */\nexport function getCoords(event: MouseEvent, rowContainer: HTMLElement, charMeasure: CharMeasure, colCount: number, rowCount: number, isSelection?: boolean): [number, number] {\n // Coordinates cannot be measured if charMeasure has not been initialized\n if (!charMeasure.width || !charMeasure.height) {\n return null;\n }\n\n const coords = getCoordsRelativeToElement(event, rowContainer);\n if (!coords) {\n return null;\n }\n\n // Convert to cols/rows.\n coords[0] = Math.ceil((coords[0] + (isSelection ? charMeasure.width / 2 : 0)) / charMeasure.width);\n coords[1] = Math.ceil(coords[1] / charMeasure.height);\n\n // Ensure coordinates are within the terminal viewport.\n coords[0] = Math.min(Math.max(coords[0], 1), colCount + 1);\n coords[1] = Math.min(Math.max(coords[1], 1), rowCount + 1);\n\n return coords;\n}\n\n/**\n * Gets coordinates within the terminal for a particular mouse event, wrapping\n * them to the bounds of the terminal and adding 32 to both the x and y values\n * as expected by xterm.\n * @param event The mouse event.\n * @param rowContainer The terminal's row container.\n * @param charMeasure The char measure object used to determine character sizes.\n * @param colCount The number of columns in the terminal.\n * @param rowCount The number of rows in the terminal.\n */\nexport function getRawByteCoords(event: MouseEvent, rowContainer: HTMLElement, charMeasure: CharMeasure, colCount: number, rowCount: number): { x: number, y: number } {\n const coords = getCoords(event, rowContainer, charMeasure, colCount, rowCount);\n let x = coords[0];\n let y = coords[1];\n\n // xterm sends raw bytes and starts at 32 (SP) for each.\n x += 32;\n y += 32;\n\n return { x, y };\n}\n","/**\n * Generic utilities module with methods that can be helpful at different parts of the code base.\n * @module xterm/utils/Generic\n * @license MIT\n */\n\n/**\n * Return if the given array contains the given element\n * @param {Array} array The array to search for the given element.\n * @param {Object} el The element to look for into the array\n */\nexport function contains(arr: any[], el: any) {\n return arr.indexOf(el) >= 0;\n};\n","/**\n * @module xterm/utils/DomElementObjectPool\n * @license MIT\n */\n\n/**\n * An object pool that manages acquisition and releasing of DOM elements for\n * when reuse is desirable.\n */\nexport class DomElementObjectPool {\n private static readonly OBJECT_ID_ATTRIBUTE = 'data-obj-id';\n\n private static _objectCount = 0;\n\n private _type: string;\n private _pool: HTMLElement[];\n private _inUse: {[key: string]: HTMLElement};\n\n /**\n * @param type The DOM element type (div, span, etc.).\n */\n constructor(private type: string) {\n this._type = type;\n this._pool = [];\n this._inUse = {};\n }\n\n /**\n * Acquire an element from the pool, creating it if the pool is empty.\n */\n public acquire(): HTMLElement {\n let element: HTMLElement;\n if (this._pool.length === 0) {\n element = this._createNew();\n } else {\n element = this._pool.pop();\n }\n this._inUse[element.getAttribute(DomElementObjectPool.OBJECT_ID_ATTRIBUTE)] = element;\n return element;\n }\n\n /**\n * Release an element back into the pool. It's up to the caller of this\n * function to ensure that all external references to the element have been\n * removed.\n * @param element The element being released.\n */\n public release(element: HTMLElement): void {\n if (!this._inUse[element.getAttribute(DomElementObjectPool.OBJECT_ID_ATTRIBUTE)]) {\n throw new Error('Could not release an element not yet acquired');\n }\n delete this._inUse[element.getAttribute(DomElementObjectPool.OBJECT_ID_ATTRIBUTE)];\n this._cleanElement(element);\n this._pool.push(element);\n }\n\n /**\n * Creates a new element for the pool.\n */\n private _createNew(): HTMLElement {\n const element = document.createElement(this._type);\n const id = DomElementObjectPool._objectCount++;\n element.setAttribute(DomElementObjectPool.OBJECT_ID_ATTRIBUTE, id.toString(10));\n return element;\n }\n\n /**\n * Resets an element back to a \"clean state\".\n * @param element The element to be cleaned.\n */\n private _cleanElement(element: HTMLElement): void {\n element.className = '';\n element.innerHTML = '';\n }\n}\n","/**\n * Represents a circular list; a list with a maximum size that wraps around when push is called,\n * overriding values at the start of the list.\n * @module xterm/utils/CircularList\n * @license MIT\n */\nimport { EventEmitter } from '../EventEmitter';\nimport { ICircularList } from '../Interfaces';\n\nexport class CircularList extends EventEmitter implements ICircularList {\n private _array: T[];\n private _startIndex: number;\n private _length: number;\n\n constructor(maxLength: number) {\n super();\n this._array = new Array(maxLength);\n this._startIndex = 0;\n this._length = 0;\n }\n\n public get maxLength(): number {\n return this._array.length;\n }\n\n public set maxLength(newMaxLength: number) {\n // Reconstruct array, starting at index 0. Only transfer values from the\n // indexes 0 to length.\n let newArray = new Array(newMaxLength);\n for (let i = 0; i < Math.min(newMaxLength, this.length); i++) {\n newArray[i] = this._array[this._getCyclicIndex(i)];\n }\n this._array = newArray;\n this._startIndex = 0;\n }\n\n public get length(): number {\n return this._length;\n }\n\n public set length(newLength: number) {\n if (newLength > this._length) {\n for (let i = this._length; i < newLength; i++) {\n this._array[i] = undefined;\n }\n }\n this._length = newLength;\n }\n\n public get forEach(): (callbackfn: (value: T, index: number) => void) => void {\n return (callbackfn: (value: T, index: number) => void) => {\n let i = 0;\n let length = this.length;\n for (let i = 0; i < length; i++) {\n callbackfn(this.get(i), i);\n }\n };\n }\n\n /**\n * Gets the value at an index.\n *\n * Note that for performance reasons there is no bounds checking here, the index reference is\n * circular so this should always return a value and never throw.\n * @param index The index of the value to get.\n * @return The value corresponding to the index.\n */\n public get(index: number): T {\n return this._array[this._getCyclicIndex(index)];\n }\n\n /**\n * Sets the value at an index.\n *\n * Note that for performance reasons there is no bounds checking here, the index reference is\n * circular so this should always return a value and never throw.\n * @param index The index to set.\n * @param value The value to set.\n */\n public set(index: number, value: T): void {\n this._array[this._getCyclicIndex(index)] = value;\n }\n\n /**\n * Pushes a new value onto the list, wrapping around to the start of the array, overriding index 0\n * if the maximum length is reached.\n * @param value The value to push onto the list.\n */\n public push(value: T): void {\n this._array[this._getCyclicIndex(this._length)] = value;\n if (this._length === this.maxLength) {\n this._startIndex++;\n if (this._startIndex === this.maxLength) {\n this._startIndex = 0;\n }\n this.emit('trim', 1);\n } else {\n this._length++;\n }\n }\n\n /**\n * Removes and returns the last value on the list.\n * @return The popped value.\n */\n public pop(): T {\n return this._array[this._getCyclicIndex(this._length-- - 1)];\n }\n\n /**\n * Deletes and/or inserts items at a particular index (in that order). Unlike\n * Array.prototype.splice, this operation does not return the deleted items as a new array in\n * order to save creating a new array. Note that this operation may shift all values in the list\n * in the worst case.\n * @param start The index to delete and/or insert.\n * @param deleteCount The number of elements to delete.\n * @param items The items to insert.\n */\n public splice(start: number, deleteCount: number, ...items: T[]): void {\n // Delete items\n if (deleteCount) {\n for (let i = start; i < this._length - deleteCount; i++) {\n this._array[this._getCyclicIndex(i)] = this._array[this._getCyclicIndex(i + deleteCount)];\n }\n this._length -= deleteCount;\n }\n\n if (items && items.length) {\n // Add items\n for (let i = this._length - 1; i >= start; i--) {\n this._array[this._getCyclicIndex(i + items.length)] = this._array[this._getCyclicIndex(i)];\n }\n for (let i = 0; i < items.length; i++) {\n this._array[this._getCyclicIndex(start + i)] = items[i];\n }\n\n // Adjust length as needed\n if (this._length + items.length > this.maxLength) {\n const countToTrim = (this._length + items.length) - this.maxLength;\n this._startIndex += countToTrim;\n this._length = this.maxLength;\n this.emit('trim', countToTrim);\n } else {\n this._length += items.length;\n }\n }\n }\n\n /**\n * Trims a number of items from the start of the list.\n * @param count The number of items to remove.\n */\n public trimStart(count: number): void {\n if (count > this._length) {\n count = this._length;\n }\n this._startIndex += count;\n this._length -= count;\n this.emit('trim', count);\n }\n\n public shiftElements(start: number, count: number, offset: number): void {\n if (count <= 0) {\n return;\n }\n if (start < 0 || start >= this._length) {\n throw new Error('start argument out of range');\n }\n if (start + offset < 0) {\n throw new Error('Cannot shift elements in list beyond index 0');\n }\n\n if (offset > 0) {\n for (let i = count - 1; i >= 0; i--) {\n this.set(start + i + offset, this.get(start + i));\n }\n const expandListBy = (start + count + offset) - this._length;\n if (expandListBy > 0) {\n this._length += expandListBy;\n while (this._length > this.maxLength) {\n this._length--;\n this._startIndex++;\n this.emit('trim', 1);\n }\n }\n } else {\n for (let i = 0; i < count; i++) {\n this.set(start + i + offset, this.get(start + i));\n }\n }\n }\n\n /**\n * Gets the cyclic index for the specified regular index. The cyclic index can then be used on the\n * backing array to get the element associated with the regular index.\n * @param index The regular index.\n * @returns The cyclic index.\n */\n private _getCyclicIndex(index: number): number {\n return (this._startIndex + index) % this.maxLength;\n }\n}\n","/**\n * @module xterm/utils/CharMeasure\n * @license MIT\n */\n\nimport { EventEmitter } from '../EventEmitter.js';\n\n/**\n * Utility class that measures the size of a character.\n */\nexport class CharMeasure extends EventEmitter {\n private _document: Document;\n private _parentElement: HTMLElement;\n private _measureElement: HTMLElement;\n private _width: number;\n private _height: number;\n\n constructor(document: Document, parentElement: HTMLElement) {\n super();\n this._document = document;\n this._parentElement = parentElement;\n }\n\n public get width(): number {\n return this._width;\n }\n\n public get height(): number {\n return this._height;\n }\n\n public measure(): void {\n if (!this._measureElement) {\n this._measureElement = this._document.createElement('span');\n this._measureElement.style.position = 'absolute';\n this._measureElement.style.top = '0';\n this._measureElement.style.left = '-9999em';\n this._measureElement.textContent = 'W';\n this._measureElement.setAttribute('aria-hidden', 'true');\n this._parentElement.appendChild(this._measureElement);\n // Perform _doMeasure async if the element was just attached as sometimes\n // getBoundingClientRect does not return accurate values without this.\n setTimeout(() => this._doMeasure(), 0);\n } else {\n this._doMeasure();\n }\n }\n\n private _doMeasure(): void {\n const geometry = this._measureElement.getBoundingClientRect();\n // The element is likely currently display:none, we should retain the\n // previous value.\n if (geometry.width === 0 || geometry.height === 0) {\n return;\n }\n if (this._width !== geometry.width || this._height !== geometry.height) {\n this._width = geometry.width;\n this._height = geometry.height;\n this.emit('charsizechanged');\n }\n }\n}\n","/**\n * @license MIT\n */\n\n// TODO: This module should be merged into a buffer or buffer line class\n\nconst LINE_DATA_CHAR_INDEX = 1;\nconst LINE_DATA_WIDTH_INDEX = 2;\n\n/**\n * Translates a buffer line to a string, with optional start and end columns.\n * Wide characters will count as two columns in the resulting string. This\n * function is useful for getting the actual text underneath the raw selection\n * position.\n * @param line The line being translated.\n * @param trimRight Whether to trim whitespace to the right.\n * @param startCol The column to start at.\n * @param endCol The column to end at.\n */\nexport function translateBufferLineToString(line: any, trimRight: boolean, startCol: number = 0, endCol: number = null): string {\n // Get full line\n let lineString = '';\n let widthAdjustedStartCol = startCol;\n let widthAdjustedEndCol = endCol;\n for (let i = 0; i < line.length; i++) {\n const char = line[i];\n lineString += char[LINE_DATA_CHAR_INDEX];\n // Adjust start and end cols for wide characters if they affect their\n // column indexes\n if (char[LINE_DATA_WIDTH_INDEX] === 0) {\n if (startCol >= i) {\n widthAdjustedStartCol--;\n }\n if (endCol >= i) {\n widthAdjustedEndCol--;\n }\n }\n }\n\n // Calculate the final end col by trimming whitespace on the right of the\n // line if needed.\n let finalEndCol = widthAdjustedEndCol || line.length;\n if (trimRight) {\n const rightWhitespaceIndex = lineString.search(/\\s+$/);\n if (rightWhitespaceIndex !== -1) {\n finalEndCol = Math.min(finalEndCol, rightWhitespaceIndex);\n }\n // Return the empty string if only trimmed whitespace is selected\n if (finalEndCol <= widthAdjustedStartCol) {\n return '';\n }\n }\n\n return lineString.substring(widthAdjustedStartCol, finalEndCol);\n}\n","/**\n * Attributes and methods to help with identifying the current browser and platform.\n * @module xterm/utils/Browser\n * @license MIT\n */\n\nimport { contains } from './Generic';\n\nconst isNode = (typeof navigator === 'undefined') ? true : false;\nconst userAgent = (isNode) ? 'node' : navigator.userAgent;\nconst platform = (isNode) ? 'node' : navigator.platform;\n\nexport const isFirefox = !!~userAgent.indexOf('Firefox');\nexport const isMSIE = !!~userAgent.indexOf('MSIE') || !!~userAgent.indexOf('Trident');\n\n// Find the users platform. We use this to interpret the meta key\n// and ISO third level shifts.\n// http://stackoverflow.com/q/19877924/577598\nexport const isMac = contains(['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'], platform);\nexport const isIpad = platform === 'iPad';\nexport const isIphone = platform === 'iPhone';\nexport const isMSWindows = contains(['Windows', 'Win16', 'Win32', 'WinCE'], platform);\nexport const isLinux = platform.indexOf('Linux') >= 0;\n","/**\n * Clipboard handler module: exports methods for handling all clipboard-related events in the\n * terminal.\n * @module xterm/handlers/Clipboard\n * @license MIT\n */\n\nimport { ITerminal, ISelectionManager } from '../Interfaces';\n\ninterface IWindow extends Window {\n clipboardData?: {\n getData(format: string): string;\n setData(format: string, data: string);\n };\n}\n\ndeclare var window: IWindow;\n\n/**\n * Prepares text to be pasted into the terminal by normalizing the line endings\n * @param text The pasted text that needs processing before inserting into the terminal\n */\nexport function prepareTextForTerminal(text: string, isMSWindows: boolean): string {\n if (isMSWindows) {\n return text.replace(/\\r?\\n/g, '\\r');\n }\n return text;\n}\n\n/**\n * Binds copy functionality to the given terminal.\n * @param {ClipboardEvent} ev The original copy event to be handled\n */\nexport function copyHandler(ev: ClipboardEvent, term: ITerminal, selectionManager: ISelectionManager) {\n if (term.browser.isMSIE) {\n window.clipboardData.setData('Text', selectionManager.selectionText);\n } else {\n ev.clipboardData.setData('text/plain', selectionManager.selectionText);\n }\n\n // Prevent or the original text will be copied.\n ev.preventDefault();\n}\n\n/**\n * Redirect the clipboard's data to the terminal's input handler.\n * @param {ClipboardEvent} ev The original paste event to be handled\n * @param {Terminal} term The terminal on which to apply the handled paste event\n */\nexport function pasteHandler(ev: ClipboardEvent, term: ITerminal) {\n ev.stopPropagation();\n\n let text: string;\n\n let dispatchPaste = function(text) {\n text = prepareTextForTerminal(text, term.browser.isMSWindows);\n term.handler(text);\n term.textarea.value = '';\n term.emit('paste', text);\n\n return term.cancel(ev);\n };\n\n if (term.browser.isMSIE) {\n if (window.clipboardData) {\n text = window.clipboardData.getData('Text');\n dispatchPaste(text);\n }\n } else {\n if (ev.clipboardData) {\n text = ev.clipboardData.getData('text/plain');\n dispatchPaste(text);\n }\n }\n}\n\n/**\n * Moves the textarea under the mouse cursor and focuses it.\n * @param ev The original right click event to be handled.\n * @param textarea The terminal's textarea.\n */\nexport function moveTextAreaUnderMouseCursor(ev: MouseEvent, textarea: HTMLTextAreaElement) {\n // Bring textarea at the cursor position\n textarea.style.position = 'fixed';\n textarea.style.width = '20px';\n textarea.style.height = '20px';\n textarea.style.left = (ev.clientX - 10) + 'px';\n textarea.style.top = (ev.clientY - 10) + 'px';\n textarea.style.zIndex = '1000';\n\n textarea.focus();\n\n // Reset the terminal textarea's styling\n setTimeout(function () {\n textarea.style.position = null;\n textarea.style.width = null;\n textarea.style.height = null;\n textarea.style.left = null;\n textarea.style.top = null;\n textarea.style.zIndex = null;\n }, 4);\n}\n\n/**\n * Bind to right-click event and allow right-click copy and paste.\n * @param ev The original right click event to be handled.\n * @param textarea The terminal's textarea.\n * @param selectionManager The terminal's selection manager.\n */\nexport function rightClickHandler(ev: MouseEvent, textarea: HTMLTextAreaElement, selectionManager: ISelectionManager) {\n moveTextAreaUnderMouseCursor(ev, textarea);\n\n // Get textarea ready to copy from the context menu\n textarea.value = selectionManager.selectionText;\n textarea.select();\n}\n","/**\n * @license MIT\n */\n\nimport { ITerminal } from './Interfaces';\nimport { CharMeasure } from './utils/CharMeasure';\n\n/**\n * Represents the viewport of a terminal, the visible area within the larger buffer of output.\n * Logic for the virtual scroll bar is included in this object.\n */\nexport class Viewport {\n private currentRowHeight: number;\n private lastRecordedBufferLength: number;\n private lastRecordedViewportHeight: number;\n private lastTouchY: number;\n\n /**\n * Creates a new Viewport.\n * @param terminal The terminal this viewport belongs to.\n * @param viewportElement The DOM element acting as the viewport.\n * @param scrollArea The DOM element acting as the scroll area.\n * @param charMeasure A DOM element used to measure the character size of. the terminal.\n */\n constructor(\n private terminal: ITerminal,\n private viewportElement: HTMLElement,\n private scrollArea: HTMLElement,\n private charMeasure: CharMeasure\n ) {\n this.currentRowHeight = 0;\n this.lastRecordedBufferLength = 0;\n this.lastRecordedViewportHeight = 0;\n\n this.terminal.on('scroll', this.syncScrollArea.bind(this));\n this.terminal.on('resize', this.syncScrollArea.bind(this));\n this.viewportElement.addEventListener('scroll', this.onScroll.bind(this));\n\n // Perform this async to ensure the CharMeasure is ready.\n setTimeout(() => this.syncScrollArea(), 0);\n }\n\n /**\n * Refreshes row height, setting line-height, viewport height and scroll area height if\n * necessary.\n */\n private refresh(): void {\n if (this.charMeasure.height > 0) {\n const rowHeightChanged = this.charMeasure.height !== this.currentRowHeight;\n if (rowHeightChanged) {\n this.currentRowHeight = this.charMeasure.height;\n this.viewportElement.style.lineHeight = this.charMeasure.height + 'px';\n this.terminal.rowContainer.style.lineHeight = this.charMeasure.height + 'px';\n }\n const viewportHeightChanged = this.lastRecordedViewportHeight !== this.terminal.rows;\n if (rowHeightChanged || viewportHeightChanged) {\n this.lastRecordedViewportHeight = this.terminal.rows;\n this.viewportElement.style.height = this.charMeasure.height * this.terminal.rows + 'px';\n this.terminal.selectionContainer.style.height = this.viewportElement.style.height;\n }\n this.scrollArea.style.height = (this.charMeasure.height * this.lastRecordedBufferLength) + 'px';\n }\n }\n\n /**\n * Updates dimensions and synchronizes the scroll area if necessary.\n */\n public syncScrollArea(): void {\n if (this.lastRecordedBufferLength !== this.terminal.buffer.lines.length) {\n // If buffer height changed\n this.lastRecordedBufferLength = this.terminal.buffer.lines.length;\n this.refresh();\n } else if (this.lastRecordedViewportHeight !== this.terminal.rows) {\n // If viewport height changed\n this.refresh();\n } else {\n // If size has changed, refresh viewport\n if (this.charMeasure.height !== this.currentRowHeight) {\n this.refresh();\n }\n }\n\n // Sync scrollTop\n const scrollTop = this.terminal.buffer.ydisp * this.currentRowHeight;\n if (this.viewportElement.scrollTop !== scrollTop) {\n this.viewportElement.scrollTop = scrollTop;\n }\n }\n\n /**\n * Handles scroll events on the viewport, calculating the new viewport and requesting the\n * terminal to scroll to it.\n * @param ev The scroll event.\n */\n private onScroll(ev: Event) {\n const newRow = Math.round(this.viewportElement.scrollTop / this.currentRowHeight);\n const diff = newRow - this.terminal.buffer.ydisp;\n this.terminal.scrollDisp(diff, true);\n }\n\n /**\n * Handles mouse wheel events by adjusting the viewport's scrollTop and delegating the actual\n * scrolling to `onScroll`, this event needs to be attached manually by the consumer of\n * `Viewport`.\n * @param ev The mouse wheel event.\n */\n public onWheel(ev: WheelEvent) {\n if (ev.deltaY === 0) {\n // Do nothing if it's not a vertical scroll event\n return;\n }\n // Fallback to WheelEvent.DOM_DELTA_PIXEL\n let multiplier = 1;\n if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) {\n multiplier = this.currentRowHeight;\n } else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) {\n multiplier = this.currentRowHeight * this.terminal.rows;\n }\n this.viewportElement.scrollTop += ev.deltaY * multiplier;\n // Prevent the page from scrolling when the terminal scrolls\n ev.preventDefault();\n };\n\n /**\n * Handles the touchstart event, recording the touch occurred.\n * @param ev The touch event.\n */\n public onTouchStart(ev: TouchEvent) {\n this.lastTouchY = ev.touches[0].pageY;\n };\n\n /**\n * Handles the touchmove event, scrolling the viewport if the position shifted.\n * @param ev The touch event.\n */\n public onTouchMove(ev: TouchEvent) {\n let deltaY = this.lastTouchY - ev.touches[0].pageY;\n this.lastTouchY = ev.touches[0].pageY;\n if (deltaY === 0) {\n return;\n }\n this.viewportElement.scrollTop += deltaY;\n ev.preventDefault();\n };\n}\n","/**\n * @license MIT\n */\n\nimport { ITerminal } from './Interfaces';\n\n/**\n * Represents a selection within the buffer. This model only cares about column\n * and row coordinates, not wide characters.\n */\nexport class SelectionModel {\n /**\n * Whether select all is currently active.\n */\n public isSelectAllActive: boolean;\n\n /**\n * The [x, y] position the selection starts at.\n */\n public selectionStart: [number, number];\n\n /**\n * The minimal length of the selection from the start position. When double\n * clicking on a word, the word will be selected which makes the selection\n * start at the start of the word and makes this variable the length.\n */\n public selectionStartLength: number;\n\n /**\n * The [x, y] position the selection ends at.\n */\n public selectionEnd: [number, number];\n\n constructor(\n private _terminal: ITerminal\n ) {\n this.clearSelection();\n }\n\n /**\n * Clears the current selection.\n */\n public clearSelection(): void {\n this.selectionStart = null;\n this.selectionEnd = null;\n this.isSelectAllActive = false;\n this.selectionStartLength = 0;\n }\n\n /**\n * The final selection start, taking into consideration select all.\n */\n public get finalSelectionStart(): [number, number] {\n if (this.isSelectAllActive) {\n return [0, 0];\n }\n\n if (!this.selectionEnd || !this.selectionStart) {\n return this.selectionStart;\n }\n\n return this.areSelectionValuesReversed() ? this.selectionEnd : this.selectionStart;\n }\n\n /**\n * The final selection end, taking into consideration select all, double click\n * word selection and triple click line selection.\n */\n public get finalSelectionEnd(): [number, number] {\n if (this.isSelectAllActive) {\n return [this._terminal.cols, this._terminal.buffer.ybase + this._terminal.rows - 1];\n }\n\n if (!this.selectionStart) {\n return null;\n }\n\n // Use the selection start if the end doesn't exist or they're reversed\n if (!this.selectionEnd || this.areSelectionValuesReversed()) {\n return [this.selectionStart[0] + this.selectionStartLength, this.selectionStart[1]];\n }\n\n // Ensure the the word/line is selected after a double/triple click\n if (this.selectionStartLength) {\n // Select the larger of the two when start and end are on the same line\n if (this.selectionEnd[1] === this.selectionStart[1]) {\n return [Math.max(this.selectionStart[0] + this.selectionStartLength, this.selectionEnd[0]), this.selectionEnd[1]];\n }\n }\n return this.selectionEnd;\n }\n\n /**\n * Returns whether the selection start and end are reversed.\n */\n public areSelectionValuesReversed(): boolean {\n const start = this.selectionStart;\n const end = this.selectionEnd;\n return start[1] > end[1] || (start[1] === end[1] && start[0] > end[0]);\n }\n\n /**\n * Handle the buffer being trimmed, adjust the selection position.\n * @param amount The amount the buffer is being trimmed.\n * @return Whether a refresh is necessary.\n */\n public onTrim(amount: number): boolean {\n // Adjust the selection position based on the trimmed amount.\n if (this.selectionStart) {\n this.selectionStart[1] -= amount;\n }\n if (this.selectionEnd) {\n this.selectionEnd[1] -= amount;\n }\n\n // The selection has moved off the buffer, clear it.\n if (this.selectionEnd && this.selectionEnd[1] < 0) {\n this.clearSelection();\n return true;\n }\n\n // If the selection start is trimmed, ensure the start column is 0.\n if (this.selectionStart && this.selectionStart[1] < 0) {\n this.selectionStart[1] = 0;\n }\n return false;\n }\n}\n","/**\n * @license MIT\n */\n\nimport * as Mouse from './utils/Mouse';\nimport * as Browser from './utils/Browser';\nimport { CharMeasure } from './utils/CharMeasure';\nimport { CircularList } from './utils/CircularList';\nimport { EventEmitter } from './EventEmitter';\nimport { ITerminal, ICircularList } from './Interfaces';\nimport { SelectionModel } from './SelectionModel';\nimport { translateBufferLineToString } from './utils/BufferLine';\n\n/**\n * The number of pixels the mouse needs to be above or below the viewport in\n * order to scroll at the maximum speed.\n */\nconst DRAG_SCROLL_MAX_THRESHOLD = 50;\n\n/**\n * The maximum scrolling speed\n */\nconst DRAG_SCROLL_MAX_SPEED = 15;\n\n/**\n * The number of milliseconds between drag scroll updates.\n */\nconst DRAG_SCROLL_INTERVAL = 50;\n\n/**\n * A string containing all characters that are considered word separated by the\n * double click to select work logic.\n */\nconst WORD_SEPARATORS = ' ()[]{}\\'\"';\n\n// TODO: Move these constants elsewhere, they belong in a buffer or buffer\n// data/line class.\nconst LINE_DATA_CHAR_INDEX = 1;\nconst LINE_DATA_WIDTH_INDEX = 2;\n\nconst NON_BREAKING_SPACE_CHAR = String.fromCharCode(160);\nconst ALL_NON_BREAKING_SPACE_REGEX = new RegExp(NON_BREAKING_SPACE_CHAR, 'g');\n\n/**\n * Represents a position of a word on a line.\n */\ninterface IWordPosition {\n start: number;\n length: number;\n}\n\n/**\n * A selection mode, this drives how the selection behaves on mouse move.\n */\nenum SelectionMode {\n NORMAL,\n WORD,\n LINE\n}\n\n/**\n * A class that manages the selection of the terminal. With help from\n * SelectionModel, SelectionManager handles with all logic associated with\n * dealing with the selection, including handling mouse interaction, wide\n * characters and fetching the actual text within the selection. Rendering is\n * not handled by the SelectionManager but a 'refresh' event is fired when the\n * selection is ready to be redrawn.\n */\nexport class SelectionManager extends EventEmitter {\n protected _model: SelectionModel;\n\n /**\n * The amount to scroll every drag scroll update (depends on how far the mouse\n * drag is above or below the terminal).\n */\n private _dragScrollAmount: number;\n\n /**\n * The current selection mode.\n */\n private _activeSelectionMode: SelectionMode;\n\n /**\n * A setInterval timer that is active while the mouse is down whose callback\n * scrolls the viewport when necessary.\n */\n private _dragScrollIntervalTimer: NodeJS.Timer;\n\n /**\n * The animation frame ID used for refreshing the selection.\n */\n private _refreshAnimationFrame: number;\n\n /**\n * Whether selection is enabled.\n */\n private _enabled = true;\n\n private _mouseMoveListener: EventListener;\n private _mouseUpListener: EventListener;\n\n constructor(\n private _terminal: ITerminal,\n private _buffer: ICircularList<[number, string, number][]>,\n private _rowContainer: HTMLElement,\n private _charMeasure: CharMeasure\n ) {\n super();\n this._initListeners();\n this.enable();\n\n this._model = new SelectionModel(_terminal);\n this._activeSelectionMode = SelectionMode.NORMAL;\n }\n\n /**\n * Initializes listener variables.\n */\n private _initListeners() {\n this._mouseMoveListener = event => this._onMouseMove(event);\n this._mouseUpListener = event => this._onMouseUp(event);\n\n this._rowContainer.addEventListener('mousedown', event => this._onMouseDown(event));\n\n // Only adjust the selection on trim, shiftElements is rarely used (only in\n // reverseIndex) and delete in a splice is only ever used when the same\n // number of elements was just added. Given this is could actually be\n // beneficial to leave the selection as is for these cases.\n this._buffer.on('trim', (amount: number) => this._onTrim(amount));\n }\n\n /**\n * Disables the selection manager. This is useful for when terminal mouse\n * are enabled.\n */\n public disable() {\n this.clearSelection();\n this._enabled = false;\n }\n\n /**\n * Enable the selection manager.\n */\n public enable() {\n this._enabled = true;\n }\n\n /**\n * Sets the active buffer, this should be called when the alt buffer is\n * switched in or out.\n * @param buffer The active buffer.\n */\n public setBuffer(buffer: ICircularList<[number, string, number][]>): void {\n this._buffer = buffer;\n this.clearSelection();\n }\n\n public get selectionStart(): [number, number] { return this._model.finalSelectionStart; }\n public get selectionEnd(): [number, number] { return this._model.finalSelectionEnd; }\n\n /**\n * Gets whether there is an active text selection.\n */\n public get hasSelection(): boolean {\n const start = this._model.finalSelectionStart;\n const end = this._model.finalSelectionEnd;\n if (!start || !end) {\n return false;\n }\n return start[0] !== end[0] || start[1] !== end[1];\n }\n\n /**\n * Gets the text currently selected.\n */\n public get selectionText(): string {\n const start = this._model.finalSelectionStart;\n const end = this._model.finalSelectionEnd;\n if (!start || !end) {\n return '';\n }\n\n // Get first row\n const startRowEndCol = start[1] === end[1] ? end[0] : null;\n let result: string[] = [];\n result.push(translateBufferLineToString(this._buffer.get(start[1]), true, start[0], startRowEndCol));\n\n // Get middle rows\n for (let i = start[1] + 1; i <= end[1] - 1; i++) {\n const bufferLine = this._buffer.get(i);\n const lineText = translateBufferLineToString(bufferLine, true);\n if ((bufferLine).isWrapped) {\n result[result.length - 1] += lineText;\n } else {\n result.push(lineText);\n }\n }\n\n // Get final row\n if (start[1] !== end[1]) {\n const bufferLine = this._buffer.get(end[1]);\n const lineText = translateBufferLineToString(bufferLine, true, 0, end[0]);\n if ((bufferLine).isWrapped) {\n result[result.length - 1] += lineText;\n } else {\n result.push(lineText);\n }\n }\n\n // Format string by replacing non-breaking space chars with regular spaces\n // and joining the array into a multi-line string.\n const formattedResult = result.map(line => {\n return line.replace(ALL_NON_BREAKING_SPACE_REGEX, ' ');\n }).join(Browser.isMSWindows ? '\\r\\n' : '\\n');\n\n return formattedResult;\n }\n\n /**\n * Clears the current terminal selection.\n */\n public clearSelection(): void {\n this._model.clearSelection();\n this._removeMouseDownListeners();\n this.refresh();\n }\n\n /**\n * Queues a refresh, redrawing the selection on the next opportunity.\n * @param isNewSelection Whether the selection should be registered as a new\n * selection on Linux.\n */\n public refresh(isNewSelection?: boolean): void {\n // Queue the refresh for the renderer\n if (!this._refreshAnimationFrame) {\n this._refreshAnimationFrame = window.requestAnimationFrame(() => this._refresh());\n }\n\n // If the platform is Linux and the refresh call comes from a mouse event,\n // we need to update the selection for middle click to paste selection.\n if (Browser.isLinux && isNewSelection) {\n const selectionText = this.selectionText;\n if (selectionText.length) {\n this.emit('newselection', this.selectionText);\n }\n }\n }\n\n /**\n * Fires the refresh event, causing consumers to pick it up and redraw the\n * selection state.\n */\n private _refresh(): void {\n this._refreshAnimationFrame = null;\n this.emit('refresh', { start: this._model.finalSelectionStart, end: this._model.finalSelectionEnd });\n }\n\n /**\n * Selects all text within the terminal.\n */\n public selectAll(): void {\n this._model.isSelectAllActive = true;\n this.refresh();\n }\n\n /**\n * Handle the buffer being trimmed, adjust the selection position.\n * @param amount The amount the buffer is being trimmed.\n */\n private _onTrim(amount: number) {\n const needsRefresh = this._model.onTrim(amount);\n if (needsRefresh) {\n this.refresh();\n }\n }\n\n /**\n * Gets the 0-based [x, y] buffer coordinates of the current mouse event.\n * @param event The mouse event.\n */\n private _getMouseBufferCoords(event: MouseEvent): [number, number] {\n const coords = Mouse.getCoords(event, this._rowContainer, this._charMeasure, this._terminal.cols, this._terminal.rows, true);\n if (!coords) {\n return null;\n }\n\n // Convert to 0-based\n coords[0]--;\n coords[1]--;\n // Convert viewport coords to buffer coords\n coords[1] += this._terminal.buffer.ydisp;\n return coords;\n }\n\n /**\n * Gets the amount the viewport should be scrolled based on how far out of the\n * terminal the mouse is.\n * @param event The mouse event.\n */\n private _getMouseEventScrollAmount(event: MouseEvent): number {\n let offset = Mouse.getCoordsRelativeToElement(event, this._rowContainer)[1];\n const terminalHeight = this._terminal.rows * this._charMeasure.height;\n if (offset >= 0 && offset <= terminalHeight) {\n return 0;\n }\n if (offset > terminalHeight) {\n offset -= terminalHeight;\n }\n\n offset = Math.min(Math.max(offset, -DRAG_SCROLL_MAX_THRESHOLD), DRAG_SCROLL_MAX_THRESHOLD);\n offset /= DRAG_SCROLL_MAX_THRESHOLD;\n return (offset / Math.abs(offset)) + Math.round(offset * (DRAG_SCROLL_MAX_SPEED - 1));\n }\n\n /**\n * Handles te mousedown event, setting up for a new selection.\n * @param event The mousedown event.\n */\n private _onMouseDown(event: MouseEvent) {\n // If we have selection, we want the context menu on right click even if the\n // terminal is in mouse mode.\n if (event.button === 2 && this.hasSelection) {\n event.stopPropagation();\n return;\n }\n\n // Only action the primary button\n if (event.button !== 0) {\n return;\n }\n\n // Allow selection when using a specific modifier key, even when disabled\n if (!this._enabled) {\n const shouldForceSelection = Browser.isMac && event.altKey;\n\n if (!shouldForceSelection) {\n return;\n }\n\n // Don't send the mouse down event to the current process, we want to select\n event.stopPropagation();\n }\n\n // Tell the browser not to start a regular selection\n event.preventDefault();\n\n // Reset drag scroll state\n this._dragScrollAmount = 0;\n\n if (this._enabled && event.shiftKey) {\n this._onIncrementalClick(event);\n } else {\n if (event.detail === 1) {\n this._onSingleClick(event);\n } else if (event.detail === 2) {\n this._onDoubleClick(event);\n } else if (event.detail === 3) {\n this._onTripleClick(event);\n }\n }\n\n this._addMouseDownListeners();\n this.refresh(true);\n }\n\n /**\n * Adds listeners when mousedown is triggered.\n */\n private _addMouseDownListeners(): void {\n // Listen on the document so that dragging outside of viewport works\n this._rowContainer.ownerDocument.addEventListener('mousemove', this._mouseMoveListener);\n this._rowContainer.ownerDocument.addEventListener('mouseup', this._mouseUpListener);\n this._dragScrollIntervalTimer = setInterval(() => this._dragScroll(), DRAG_SCROLL_INTERVAL);\n }\n\n /**\n * Removes the listeners that are registered when mousedown is triggered.\n */\n private _removeMouseDownListeners(): void {\n this._rowContainer.ownerDocument.removeEventListener('mousemove', this._mouseMoveListener);\n this._rowContainer.ownerDocument.removeEventListener('mouseup', this._mouseUpListener);\n clearInterval(this._dragScrollIntervalTimer);\n this._dragScrollIntervalTimer = null;\n }\n\n /**\n * Performs an incremental click, setting the selection end position to the mouse\n * position.\n * @param event The mouse event.\n */\n private _onIncrementalClick(event: MouseEvent): void {\n if (this._model.selectionStart) {\n this._model.selectionEnd = this._getMouseBufferCoords(event);\n }\n }\n\n /**\n * Performs a single click, resetting relevant state and setting the selection\n * start position.\n * @param event The mouse event.\n */\n private _onSingleClick(event: MouseEvent): void {\n this._model.selectionStartLength = 0;\n this._model.isSelectAllActive = false;\n this._activeSelectionMode = SelectionMode.NORMAL;\n\n // Initialize the new selection\n this._model.selectionStart = this._getMouseBufferCoords(event);\n if (!this._model.selectionStart) {\n return;\n }\n this._model.selectionEnd = null;\n\n // Ensure the line exists\n const line = this._buffer.get(this._model.selectionStart[1]);\n if (!line) {\n return;\n }\n\n // If the mouse is over the second half of a wide character, adjust the\n // selection to cover the whole character\n const char = line[this._model.selectionStart[0]];\n if (char[LINE_DATA_WIDTH_INDEX] === 0) {\n this._model.selectionStart[0]++;\n }\n }\n\n /**\n * Performs a double click, selecting the current work.\n * @param event The mouse event.\n */\n private _onDoubleClick(event: MouseEvent): void {\n const coords = this._getMouseBufferCoords(event);\n if (coords) {\n this._activeSelectionMode = SelectionMode.WORD;\n this._selectWordAt(coords);\n }\n }\n\n /**\n * Performs a triple click, selecting the current line and activating line\n * select mode.\n * @param event The mouse event.\n */\n private _onTripleClick(event: MouseEvent): void {\n const coords = this._getMouseBufferCoords(event);\n if (coords) {\n this._activeSelectionMode = SelectionMode.LINE;\n this._selectLineAt(coords[1]);\n }\n }\n\n /**\n * Handles the mousemove event when the mouse button is down, recording the\n * end of the selection and refreshing the selection.\n * @param event The mousemove event.\n */\n private _onMouseMove(event: MouseEvent) {\n // Record the previous position so we know whether to redraw the selection\n // at the end.\n const previousSelectionEnd = this._model.selectionEnd ? [this._model.selectionEnd[0], this._model.selectionEnd[1]] : null;\n\n // Set the initial selection end based on the mouse coordinates\n this._model.selectionEnd = this._getMouseBufferCoords(event);\n if (!this._model.selectionEnd) {\n this.refresh(true);\n return;\n }\n\n // Select the entire line if line select mode is active.\n if (this._activeSelectionMode === SelectionMode.LINE) {\n if (this._model.selectionEnd[1] < this._model.selectionStart[1]) {\n this._model.selectionEnd[0] = 0;\n } else {\n this._model.selectionEnd[0] = this._terminal.cols;\n }\n } else if (this._activeSelectionMode === SelectionMode.WORD) {\n this._selectToWordAt(this._model.selectionEnd);\n }\n\n // Determine the amount of scrolling that will happen.\n this._dragScrollAmount = this._getMouseEventScrollAmount(event);\n\n // If the cursor was above or below the viewport, make sure it's at the\n // start or end of the viewport respectively.\n if (this._dragScrollAmount > 0) {\n this._model.selectionEnd[0] = this._terminal.cols - 1;\n } else if (this._dragScrollAmount < 0) {\n this._model.selectionEnd[0] = 0;\n }\n\n // If the character is a wide character include the cell to the right in the\n // selection. Note that selections at the very end of the line will never\n // have a character.\n if (this._model.selectionEnd[1] < this._buffer.length) {\n const char = this._buffer.get(this._model.selectionEnd[1])[this._model.selectionEnd[0]];\n if (char && char[2] === 0) {\n this._model.selectionEnd[0]++;\n }\n }\n\n // Only draw here if the selection changes.\n if (!previousSelectionEnd ||\n previousSelectionEnd[0] !== this._model.selectionEnd[0] ||\n previousSelectionEnd[1] !== this._model.selectionEnd[1]) {\n this.refresh(true);\n }\n }\n\n /**\n * The callback that occurs every DRAG_SCROLL_INTERVAL ms that does the\n * scrolling of the viewport.\n */\n private _dragScroll() {\n if (this._dragScrollAmount) {\n this._terminal.scrollDisp(this._dragScrollAmount, false);\n // Re-evaluate selection\n if (this._dragScrollAmount > 0) {\n this._model.selectionEnd = [this._terminal.cols - 1, this._terminal.buffer.ydisp + this._terminal.rows];\n } else {\n this._model.selectionEnd = [0, this._terminal.buffer.ydisp];\n }\n this.refresh();\n }\n }\n\n /**\n * Handles the mouseup event, removing the mousedown listeners.\n * @param event The mouseup event.\n */\n private _onMouseUp(event: MouseEvent) {\n this._removeMouseDownListeners();\n }\n\n /**\n * Converts a viewport column to the character index on the buffer line, the\n * latter takes into account wide characters.\n * @param coords The coordinates to find the 2 index for.\n */\n private _convertViewportColToCharacterIndex(bufferLine: any, coords: [number, number]): number {\n let charIndex = coords[0];\n for (let i = 0; coords[0] >= i; i++) {\n const char = bufferLine[i];\n if (char[LINE_DATA_WIDTH_INDEX] === 0) {\n charIndex--;\n }\n }\n return charIndex;\n }\n\n public setSelection(col: number, row: number, length: number): void {\n this._model.clearSelection();\n this._removeMouseDownListeners();\n this._model.selectionStart = [col, row];\n this._model.selectionStartLength = length;\n this.refresh();\n }\n\n /**\n * Gets positional information for the word at the coordinated specified.\n * @param coords The coordinates to get the word at.\n */\n private _getWordAt(coords: [number, number]): IWordPosition {\n const bufferLine = this._buffer.get(coords[1]);\n if (!bufferLine) {\n return null;\n }\n\n const line = translateBufferLineToString(bufferLine, false);\n\n // Get actual index, taking into consideration wide characters\n let endIndex = this._convertViewportColToCharacterIndex(bufferLine, coords);\n let startIndex = endIndex;\n\n // Record offset to be used later\n const charOffset = coords[0] - startIndex;\n let leftWideCharCount = 0;\n let rightWideCharCount = 0;\n\n if (line.charAt(startIndex) === ' ') {\n // Expand until non-whitespace is hit\n while (startIndex > 0 && line.charAt(startIndex - 1) === ' ') {\n startIndex--;\n }\n while (endIndex < line.length && line.charAt(endIndex + 1) === ' ') {\n endIndex++;\n }\n } else {\n // Expand until whitespace is hit. This algorithm works by scanning left\n // and right from the starting position, keeping both the index format\n // (line) and the column format (bufferLine) in sync. When a wide\n // character is hit, it is recorded and the column index is adjusted.\n let startCol = coords[0];\n let endCol = coords[0];\n // Consider the initial position, skip it and increment the wide char\n // variable\n if (bufferLine[startCol][LINE_DATA_WIDTH_INDEX] === 0) {\n leftWideCharCount++;\n startCol--;\n }\n if (bufferLine[endCol][LINE_DATA_WIDTH_INDEX] === 2) {\n rightWideCharCount++;\n endCol++;\n }\n // Expand the string in both directions until a space is hit\n while (startIndex > 0 && !this._isCharWordSeparator(line.charAt(startIndex - 1))) {\n if (bufferLine[startCol - 1][LINE_DATA_WIDTH_INDEX] === 0) {\n // If the next character is a wide char, record it and skip the column\n leftWideCharCount++;\n startCol--;\n }\n startIndex--;\n startCol--;\n }\n while (endIndex + 1 < line.length && !this._isCharWordSeparator(line.charAt(endIndex + 1))) {\n if (bufferLine[endCol + 1][LINE_DATA_WIDTH_INDEX] === 2) {\n // If the next character is a wide char, record it and skip the column\n rightWideCharCount++;\n endCol++;\n }\n endIndex++;\n endCol++;\n }\n }\n\n const start = startIndex + charOffset - leftWideCharCount;\n const length = Math.min(endIndex - startIndex + leftWideCharCount + rightWideCharCount + 1/*include endIndex char*/, this._terminal.cols);\n return { start, length };\n }\n\n /**\n * Selects the word at the coordinates specified.\n * @param coords The coordinates to get the word at.\n */\n protected _selectWordAt(coords: [number, number]): void {\n const wordPosition = this._getWordAt(coords);\n if (wordPosition) {\n this._model.selectionStart = [wordPosition.start, coords[1]];\n this._model.selectionStartLength = wordPosition.length;\n }\n }\n\n /**\n * Sets the selection end to the word at the coordinated specified.\n * @param coords The coordinates to get the word at.\n */\n private _selectToWordAt(coords: [number, number]): void {\n const wordPosition = this._getWordAt(coords);\n if (wordPosition) {\n this._model.selectionEnd = [this._model.areSelectionValuesReversed() ? wordPosition.start : (wordPosition.start + wordPosition.length), coords[1]];\n }\n }\n\n /**\n * Gets whether the character is considered a word separator by the select\n * word logic.\n * @param char The character to check.\n */\n private _isCharWordSeparator(char: string): boolean {\n return WORD_SEPARATORS.indexOf(char) >= 0;\n }\n\n /**\n * Selects the line specified.\n * @param line The line index.\n */\n protected _selectLineAt(line: number): void {\n this._model.selectionStart = [0, line];\n this._model.selectionStartLength = this._terminal.cols;\n }\n}\n","/**\n * @license MIT\n */\n\nimport { ITerminal } from './Interfaces';\nimport { DomElementObjectPool } from './utils/DomElementObjectPool';\n\n/**\n * The maximum number of refresh frames to skip when the write buffer is non-\n * empty. Note that these frames may be intermingled with frames that are\n * skipped via requestAnimationFrame's mechanism.\n */\nconst MAX_REFRESH_FRAME_SKIP = 5;\n\n/**\n * Flags used to render terminal text properly.\n */\nenum FLAGS {\n BOLD = 1,\n UNDERLINE = 2,\n BLINK = 4,\n INVERSE = 8,\n INVISIBLE = 16\n};\n\nlet brokenBold: boolean = null;\n\nexport class Renderer {\n /** A queue of the rows to be refreshed */\n private _refreshRowsQueue: {start: number, end: number}[] = [];\n private _refreshFramesSkipped = 0;\n private _refreshAnimationFrame = null;\n\n private _spanElementObjectPool = new DomElementObjectPool('span');\n\n constructor(private _terminal: ITerminal) {\n // Figure out whether boldness affects\n // the character width of monospace fonts.\n if (brokenBold === null) {\n brokenBold = checkBoldBroken((this._terminal).element);\n }\n this._spanElementObjectPool = new DomElementObjectPool('span');\n\n // TODO: Pull more DOM interactions into Renderer.constructor, element for\n // example should be owned by Renderer (and also exposed by Terminal due to\n // to established public API).\n }\n\n /**\n * Queues a refresh between two rows (inclusive), to be done on next animation\n * frame.\n * @param {number} start The start row.\n * @param {number} end The end row.\n */\n public queueRefresh(start: number, end: number): void {\n this._refreshRowsQueue.push({ start: start, end: end });\n if (!this._refreshAnimationFrame) {\n this._refreshAnimationFrame = window.requestAnimationFrame(this._refreshLoop.bind(this));\n }\n }\n\n /**\n * Performs the refresh loop callback, calling refresh only if a refresh is\n * necessary before queueing up the next one.\n */\n private _refreshLoop(): void {\n // Skip MAX_REFRESH_FRAME_SKIP frames if the writeBuffer is non-empty as it\n // will need to be immediately refreshed anyway. This saves a lot of\n // rendering time as the viewport DOM does not need to be refreshed, no\n // scroll events, no layouts, etc.\n const skipFrame = this._terminal.writeBuffer.length > 0 && this._refreshFramesSkipped++ <= MAX_REFRESH_FRAME_SKIP;\n if (skipFrame) {\n this._refreshAnimationFrame = window.requestAnimationFrame(this._refreshLoop.bind(this));\n return;\n }\n\n this._refreshFramesSkipped = 0;\n let start;\n let end;\n if (this._refreshRowsQueue.length > 4) {\n // Just do a full refresh when 5+ refreshes are queued\n start = 0;\n end = this._terminal.rows - 1;\n } else {\n // Get start and end rows that need refreshing\n start = this._refreshRowsQueue[0].start;\n end = this._refreshRowsQueue[0].end;\n for (let i = 1; i < this._refreshRowsQueue.length; i++) {\n if (this._refreshRowsQueue[i].start < start) {\n start = this._refreshRowsQueue[i].start;\n }\n if (this._refreshRowsQueue[i].end > end) {\n end = this._refreshRowsQueue[i].end;\n }\n }\n }\n this._refreshRowsQueue = [];\n this._refreshAnimationFrame = null;\n this._refresh(start, end);\n }\n\n /**\n * Refreshes (re-renders) terminal content within two rows (inclusive)\n *\n * Rendering Engine:\n *\n * In the screen buffer, each character is stored as a an array with a character\n * and a 32-bit integer:\n * - First value: a utf-16 character.\n * - Second value:\n * - Next 9 bits: background color (0-511).\n * - Next 9 bits: foreground color (0-511).\n * - Next 14 bits: a mask for misc. flags:\n * - 1=bold\n * - 2=underline\n * - 4=blink\n * - 8=inverse\n * - 16=invisible\n *\n * @param {number} start The row to start from (between 0 and terminal's height terminal - 1)\n * @param {number} end The row to end at (between fromRow and terminal's height terminal - 1)\n */\n private _refresh(start: number, end: number): void {\n // If this is a big refresh, remove the terminal rows from the DOM for faster calculations\n let parent;\n if (end - start >= this._terminal.rows / 2) {\n parent = this._terminal.element.parentNode;\n if (parent) {\n this._terminal.element.removeChild(this._terminal.rowContainer);\n }\n }\n\n let width = this._terminal.cols;\n let y = start;\n\n if (end >= this._terminal.rows) {\n this._terminal.log('`end` is too large. Most likely a bad CSR.');\n end = this._terminal.rows - 1;\n }\n\n for (; y <= end; y++) {\n let row = y + this._terminal.buffer.ydisp;\n\n let line = this._terminal.buffer.lines.get(row);\n\n let x;\n if (this._terminal.buffer.y === y - (this._terminal.buffer.ybase - this._terminal.buffer.ydisp) &&\n this._terminal.cursorState &&\n !this._terminal.cursorHidden) {\n x = this._terminal.buffer.x;\n } else {\n x = -1;\n }\n\n let attr = this._terminal.defAttr;\n\n const documentFragment = document.createDocumentFragment();\n let innerHTML = '';\n let currentElement;\n\n // Return the row's spans to the pool\n while (this._terminal.children[y].children.length) {\n const child = this._terminal.children[y].children[0];\n this._terminal.children[y].removeChild(child);\n this._spanElementObjectPool.release(child);\n }\n\n for (let i = 0; i < width; i++) {\n // TODO: Could data be a more specific type?\n let data: any = line[i][0];\n const ch = line[i][1];\n const ch_width: any = line[i][2];\n const isCursor: boolean = i === x;\n if (!ch_width) {\n continue;\n }\n\n if (data !== attr || isCursor) {\n if (attr !== this._terminal.defAttr && !isCursor) {\n if (innerHTML) {\n currentElement.innerHTML = innerHTML;\n innerHTML = '';\n }\n documentFragment.appendChild(currentElement);\n currentElement = null;\n }\n if (data !== this._terminal.defAttr || isCursor) {\n if (innerHTML && !currentElement) {\n currentElement = this._spanElementObjectPool.acquire();\n }\n if (currentElement) {\n if (innerHTML) {\n currentElement.innerHTML = innerHTML;\n innerHTML = '';\n }\n documentFragment.appendChild(currentElement);\n }\n currentElement = this._spanElementObjectPool.acquire();\n\n let bg = data & 0x1ff;\n let fg = (data >> 9) & 0x1ff;\n let flags = data >> 18;\n\n if (isCursor) {\n currentElement.classList.add('reverse-video');\n currentElement.classList.add('terminal-cursor');\n }\n\n if (flags & FLAGS.BOLD) {\n if (!brokenBold) {\n currentElement.classList.add('xterm-bold');\n }\n // See: XTerm*boldColors\n if (fg < 8) {\n fg += 8;\n }\n }\n\n if (flags & FLAGS.UNDERLINE) {\n currentElement.classList.add('xterm-underline');\n }\n\n if (flags & FLAGS.BLINK) {\n currentElement.classList.add('xterm-blink');\n }\n\n // If inverse flag is on, then swap the foreground and background variables.\n if (flags & FLAGS.INVERSE) {\n let temp = bg;\n bg = fg;\n fg = temp;\n // Should inverse just be before the above boldColors effect instead?\n if ((flags & 1) && fg < 8) {\n fg += 8;\n }\n }\n\n if (flags & FLAGS.INVISIBLE && !isCursor) {\n currentElement.classList.add('xterm-hidden');\n }\n\n /**\n * Weird situation: Invert flag used black foreground and white background results\n * in invalid background color, positioned at the 256 index of the 256 terminal\n * color map. Pin the colors manually in such a case.\n *\n * Source: https://github.com/sourcelair/xterm.js/issues/57\n */\n if (flags & FLAGS.INVERSE) {\n if (bg === 257) {\n bg = 15;\n }\n if (fg === 256) {\n fg = 0;\n }\n }\n\n if (bg < 256) {\n currentElement.classList.add(`xterm-bg-color-${bg}`);\n }\n\n if (fg < 256) {\n currentElement.classList.add(`xterm-color-${fg}`);\n }\n\n }\n }\n\n if (ch_width === 2) {\n // Wrap wide characters so they're sized correctly. It's more difficult to release these\n // from the object pool so just create new ones via innerHTML.\n innerHTML += `${ch}`;\n } else if (ch.charCodeAt(0) > 255) {\n // Wrap any non-wide unicode character as some fonts size them badly\n innerHTML += `${ch}`;\n } else {\n switch (ch) {\n case '&':\n innerHTML += '&';\n break;\n case '<':\n innerHTML += '<';\n break;\n case '>':\n innerHTML += '>';\n break;\n default:\n if (ch <= ' ') {\n innerHTML += ' ';\n } else {\n innerHTML += ch;\n }\n break;\n }\n }\n\n // The cursor needs its own element, therefore we set attr to -1\n // which will cause the next character to be rendered in a new element\n attr = isCursor ? -1 : data;\n\n }\n\n if (innerHTML && !currentElement) {\n currentElement = this._spanElementObjectPool.acquire();\n }\n if (currentElement) {\n if (innerHTML) {\n currentElement.innerHTML = innerHTML;\n innerHTML = '';\n }\n documentFragment.appendChild(currentElement);\n currentElement = null;\n }\n\n this._terminal.children[y].appendChild(documentFragment);\n }\n\n if (parent) {\n this._terminal.element.appendChild(this._terminal.rowContainer);\n }\n\n this._terminal.emit('refresh', {element: this._terminal.element, start: start, end: end});\n };\n\n /**\n * Refreshes the selection in the DOM.\n * @param start The selection start.\n * @param end The selection end.\n */\n public refreshSelection(start: [number, number], end: [number, number]) {\n // Remove all selections\n while (this._terminal.selectionContainer.children.length) {\n this._terminal.selectionContainer.removeChild(this._terminal.selectionContainer.children[0]);\n }\n\n // Selection does not exist\n if (!start || !end) {\n return;\n }\n\n // Translate from buffer position to viewport position\n const viewportStartRow = start[1] - this._terminal.buffer.ydisp;\n const viewportEndRow = end[1] - this._terminal.buffer.ydisp;\n const viewportCappedStartRow = Math.max(viewportStartRow, 0);\n const viewportCappedEndRow = Math.min(viewportEndRow, this._terminal.rows - 1);\n\n // No need to draw the selection\n if (viewportCappedStartRow >= this._terminal.rows || viewportCappedEndRow < 0) {\n return;\n }\n\n // Create the selections\n const documentFragment = document.createDocumentFragment();\n // Draw first row\n const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0;\n const endCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : this._terminal.cols;\n documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow, startCol, endCol));\n // Draw middle rows\n const middleRowsCount = viewportCappedEndRow - viewportCappedStartRow - 1;\n documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow + 1, 0, this._terminal.cols, middleRowsCount));\n // Draw final row\n if (viewportCappedStartRow !== viewportCappedEndRow) {\n // Only draw viewportEndRow if it's not the same as viewporttartRow\n const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : this._terminal.cols;\n documentFragment.appendChild(this._createSelectionElement(viewportCappedEndRow, 0, endCol));\n }\n this._terminal.selectionContainer.appendChild(documentFragment);\n }\n\n /**\n * Creates a selection element at the specified position.\n * @param row The row of the selection.\n * @param colStart The start column.\n * @param colEnd The end columns.\n */\n private _createSelectionElement(row: number, colStart: number, colEnd: number, rowCount: number = 1): HTMLElement {\n const element = document.createElement('div');\n element.style.height = `${rowCount * this._terminal.charMeasure.height}px`;\n element.style.top = `${row * this._terminal.charMeasure.height}px`;\n element.style.left = `${colStart * this._terminal.charMeasure.width}px`;\n element.style.width = `${this._terminal.charMeasure.width * (colEnd - colStart)}px`;\n return element;\n }\n}\n\n\n// If bold is broken, we can't use it in the terminal.\nfunction checkBoldBroken(terminal) {\n const document = terminal.ownerDocument;\n const el = document.createElement('span');\n el.innerHTML = 'hello world';\n terminal.appendChild(el);\n const w1 = el.offsetWidth;\n const h1 = el.offsetHeight;\n el.style.fontWeight = 'bold';\n const w2 = el.offsetWidth;\n const h2 = el.offsetHeight;\n terminal.removeChild(el);\n return w1 !== w2 || h1 !== h2;\n}\n","/**\n * @license MIT\n */\n\nimport { C0 } from './EscapeSequences';\nimport { IInputHandler } from './Interfaces';\nimport { CHARSETS, DEFAULT_CHARSET } from './Charsets';\n\nconst normalStateHandler: {[key: string]: (parser: Parser, handler: IInputHandler) => void} = {};\nnormalStateHandler[C0.BEL] = (parser, handler) => handler.bell();\nnormalStateHandler[C0.LF] = (parser, handler) => handler.lineFeed();\nnormalStateHandler[C0.VT] = normalStateHandler[C0.LF];\nnormalStateHandler[C0.FF] = normalStateHandler[C0.LF];\nnormalStateHandler[C0.CR] = (parser, handler) => handler.carriageReturn();\nnormalStateHandler[C0.BS] = (parser, handler) => handler.backspace();\nnormalStateHandler[C0.HT] = (parser, handler) => handler.tab();\nnormalStateHandler[C0.SO] = (parser, handler) => handler.shiftOut();\nnormalStateHandler[C0.SI] = (parser, handler) => handler.shiftIn();\nnormalStateHandler[C0.ESC] = (parser, handler) => parser.setState(ParserState.ESCAPED);\n\n// TODO: Remove terminal when parser owns params and currentParam\nconst escapedStateHandler: {[key: string]: (parser: Parser, terminal: any) => void} = {};\nescapedStateHandler['['] = (parser, terminal) => {\n // ESC [ Control Sequence Introducer (CSI is 0x9b)\n terminal.params = [];\n terminal.currentParam = 0;\n parser.setState(ParserState.CSI_PARAM);\n};\nescapedStateHandler[']'] = (parser, terminal) => {\n // ESC ] Operating System Command (OSC is 0x9d)\n terminal.params = [];\n terminal.currentParam = 0;\n parser.setState(ParserState.OSC);\n};\nescapedStateHandler['P'] = (parser, terminal) => {\n // ESC P Device Control String (DCS is 0x90)\n terminal.params = [];\n terminal.currentParam = 0;\n parser.setState(ParserState.DCS);\n};\nescapedStateHandler['_'] = (parser, terminal) => {\n // ESC _ Application Program Command ( APC is 0x9f).\n parser.setState(ParserState.IGNORE);\n};\nescapedStateHandler['^'] = (parser, terminal) => {\n // ESC ^ Privacy Message ( PM is 0x9e).\n parser.setState(ParserState.IGNORE);\n};\nescapedStateHandler['c'] = (parser, terminal) => {\n // ESC c Full Reset (RIS).\n terminal.reset();\n};\nescapedStateHandler['E'] = (parser, terminal) => {\n // ESC E Next Line ( NEL is 0x85).\n terminal.buffer.x = 0;\n terminal.index();\n parser.setState(ParserState.NORMAL);\n};\nescapedStateHandler['D'] = (parser, terminal) => {\n // ESC D Index ( IND is 0x84).\n terminal.index();\n parser.setState(ParserState.NORMAL);\n};\nescapedStateHandler['M'] = (parser, terminal) => {\n // ESC M Reverse Index ( RI is 0x8d).\n terminal.reverseIndex();\n parser.setState(ParserState.NORMAL);\n};\nescapedStateHandler['%'] = (parser, terminal) => {\n // ESC % Select default/utf-8 character set.\n // @ = default, G = utf-8\n terminal.setgLevel(0);\n terminal.setgCharset(0, DEFAULT_CHARSET); // US (default)\n parser.setState(ParserState.NORMAL);\n parser.skipNextChar();\n};\nescapedStateHandler[C0.CAN] = (parser) => parser.setState(ParserState.NORMAL);\n\nconst csiParamStateHandler: {[key: string]: (parser: Parser) => void} = {};\ncsiParamStateHandler['?'] = (parser) => parser.setPrefix('?');\ncsiParamStateHandler['>'] = (parser) => parser.setPrefix('>');\ncsiParamStateHandler['!'] = (parser) => parser.setPrefix('!');\ncsiParamStateHandler['0'] = (parser) => parser.setParam(parser.getParam() * 10);\ncsiParamStateHandler['1'] = (parser) => parser.setParam(parser.getParam() * 10 + 1);\ncsiParamStateHandler['2'] = (parser) => parser.setParam(parser.getParam() * 10 + 2);\ncsiParamStateHandler['3'] = (parser) => parser.setParam(parser.getParam() * 10 + 3);\ncsiParamStateHandler['4'] = (parser) => parser.setParam(parser.getParam() * 10 + 4);\ncsiParamStateHandler['5'] = (parser) => parser.setParam(parser.getParam() * 10 + 5);\ncsiParamStateHandler['6'] = (parser) => parser.setParam(parser.getParam() * 10 + 6);\ncsiParamStateHandler['7'] = (parser) => parser.setParam(parser.getParam() * 10 + 7);\ncsiParamStateHandler['8'] = (parser) => parser.setParam(parser.getParam() * 10 + 8);\ncsiParamStateHandler['9'] = (parser) => parser.setParam(parser.getParam() * 10 + 9);\ncsiParamStateHandler['$'] = (parser) => parser.setPostfix('$');\ncsiParamStateHandler['\"'] = (parser) => parser.setPostfix('\"');\ncsiParamStateHandler[' '] = (parser) => parser.setPostfix(' ');\ncsiParamStateHandler['\\''] = (parser) => parser.setPostfix('\\'');\ncsiParamStateHandler[';'] = (parser) => parser.finalizeParam();\ncsiParamStateHandler[C0.CAN] = (parser) => parser.setState(ParserState.NORMAL);\n\nconst csiStateHandler: {[key: string]: (handler: IInputHandler, params: number[], prefix: string, postfix: string, parser: Parser) => void} = {};\ncsiStateHandler['@'] = (handler, params, prefix) => handler.insertChars(params);\ncsiStateHandler['A'] = (handler, params, prefix) => handler.cursorUp(params);\ncsiStateHandler['B'] = (handler, params, prefix) => handler.cursorDown(params);\ncsiStateHandler['C'] = (handler, params, prefix) => handler.cursorForward(params);\ncsiStateHandler['D'] = (handler, params, prefix) => handler.cursorBackward(params);\ncsiStateHandler['E'] = (handler, params, prefix) => handler.cursorNextLine(params);\ncsiStateHandler['F'] = (handler, params, prefix) => handler.cursorPrecedingLine(params);\ncsiStateHandler['G'] = (handler, params, prefix) => handler.cursorCharAbsolute(params);\ncsiStateHandler['H'] = (handler, params, prefix) => handler.cursorPosition(params);\ncsiStateHandler['I'] = (handler, params, prefix) => handler.cursorForwardTab(params);\ncsiStateHandler['J'] = (handler, params, prefix) => handler.eraseInDisplay(params);\ncsiStateHandler['K'] = (handler, params, prefix) => handler.eraseInLine(params);\ncsiStateHandler['L'] = (handler, params, prefix) => handler.insertLines(params);\ncsiStateHandler['M'] = (handler, params, prefix) => handler.deleteLines(params);\ncsiStateHandler['P'] = (handler, params, prefix) => handler.deleteChars(params);\ncsiStateHandler['S'] = (handler, params, prefix) => handler.scrollUp(params);\ncsiStateHandler['T'] = (handler, params, prefix) => {\n if (params.length < 2 && !prefix) {\n handler.scrollDown(params);\n }\n};\ncsiStateHandler['X'] = (handler, params, prefix) => handler.eraseChars(params);\ncsiStateHandler['Z'] = (handler, params, prefix) => handler.cursorBackwardTab(params);\ncsiStateHandler['`'] = (handler, params, prefix) => handler.charPosAbsolute(params);\ncsiStateHandler['a'] = (handler, params, prefix) => handler.HPositionRelative(params);\ncsiStateHandler['b'] = (handler, params, prefix) => handler.repeatPrecedingCharacter(params);\ncsiStateHandler['c'] = (handler, params, prefix) => handler.sendDeviceAttributes(params);\ncsiStateHandler['d'] = (handler, params, prefix) => handler.linePosAbsolute(params);\ncsiStateHandler['e'] = (handler, params, prefix) => handler.VPositionRelative(params);\ncsiStateHandler['f'] = (handler, params, prefix) => handler.HVPosition(params);\ncsiStateHandler['g'] = (handler, params, prefix) => handler.tabClear(params);\ncsiStateHandler['h'] = (handler, params, prefix) => handler.setMode(params);\ncsiStateHandler['l'] = (handler, params, prefix) => handler.resetMode(params);\ncsiStateHandler['m'] = (handler, params, prefix) => handler.charAttributes(params);\ncsiStateHandler['n'] = (handler, params, prefix) => handler.deviceStatus(params);\ncsiStateHandler['p'] = (handler, params, prefix) => {\n switch (prefix) {\n case '!': handler.softReset(params); break;\n }\n};\ncsiStateHandler['q'] = (handler, params, prefix, postfix) => {\n if (postfix === ' ') {\n handler.setCursorStyle(params);\n }\n};\ncsiStateHandler['r'] = (handler, params) => handler.setScrollRegion(params);\ncsiStateHandler['s'] = (handler, params) => handler.saveCursor(params);\ncsiStateHandler['u'] = (handler, params) => handler.restoreCursor(params);\ncsiStateHandler[C0.CAN] = (handler, params, prefix, postfix, parser) => parser.setState(ParserState.NORMAL);\n\nenum ParserState {\n NORMAL = 0,\n ESCAPED = 1,\n CSI_PARAM = 2,\n CSI = 3,\n OSC = 4,\n CHARSET = 5,\n DCS = 6,\n IGNORE = 7\n}\n\n/**\n * The terminal's parser, all input into the terminal goes through the parser\n * which parses and defers the actual input handling the the IInputHandler\n * specified in the constructor.\n */\nexport class Parser {\n private _state: ParserState;\n private _position: number;\n\n // TODO: Remove terminal when handler can do everything\n constructor(\n private _inputHandler: IInputHandler,\n private _terminal: any\n ) {\n this._state = ParserState.NORMAL;\n }\n\n /**\n * Parse and handle data.\n *\n * @param data The data to parse.\n */\n public parse(data: string): ParserState {\n let l = data.length, j, cs, ch, code, low;\n\n if (this._terminal.debug) {\n this._terminal.log('data: ' + data);\n }\n\n this._position = 0;\n // apply leftover surrogate high from last write\n if (this._terminal.surrogate_high) {\n data = this._terminal.surrogate_high + data;\n this._terminal.surrogate_high = '';\n }\n\n for (; this._position < l; this._position++) {\n ch = data[this._position];\n\n // FIXME: higher chars than 0xa0 are not allowed in escape sequences\n // --> maybe move to default\n code = data.charCodeAt(this._position);\n if (0xD800 <= code && code <= 0xDBFF) {\n // we got a surrogate high\n // get surrogate low (next 2 bytes)\n low = data.charCodeAt(this._position + 1);\n if (isNaN(low)) {\n // end of data stream, save surrogate high\n this._terminal.surrogate_high = ch;\n continue;\n }\n code = ((code - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000;\n ch += data.charAt(this._position + 1);\n }\n // surrogate low - already handled above\n if (0xDC00 <= code && code <= 0xDFFF)\n continue;\n\n switch (this._state) {\n case ParserState.NORMAL:\n if (ch in normalStateHandler) {\n normalStateHandler[ch](this, this._inputHandler);\n } else {\n this._inputHandler.addChar(ch, code);\n }\n break;\n case ParserState.ESCAPED:\n if (ch in escapedStateHandler) {\n escapedStateHandler[ch](this, this._terminal);\n // Skip switch as it was just handled\n break;\n }\n switch (ch) {\n\n // ESC (,),*,+,-,. Designate G0-G2 Character Set.\n case '(': // <-- this seems to get all the attention\n case ')':\n case '*':\n case '+':\n case '-':\n case '.':\n switch (ch) {\n case '(':\n this._terminal.gcharset = 0;\n break;\n case ')':\n this._terminal.gcharset = 1;\n break;\n case '*':\n this._terminal.gcharset = 2;\n break;\n case '+':\n this._terminal.gcharset = 3;\n break;\n case '-':\n this._terminal.gcharset = 1;\n break;\n case '.':\n this._terminal.gcharset = 2;\n break;\n }\n this._state = ParserState.CHARSET;\n break;\n\n // Designate G3 Character Set (VT300).\n // A = ISO Latin-1 Supplemental.\n // Not implemented.\n case '/':\n this._terminal.gcharset = 3;\n this._state = ParserState.CHARSET;\n this._position--;\n break;\n\n // ESC N\n // Single Shift Select of G2 Character Set\n // ( SS2 is 0x8e). This affects next character only.\n case 'N':\n break;\n // ESC O\n // Single Shift Select of G3 Character Set\n // ( SS3 is 0x8f). This affects next character only.\n case 'O':\n break;\n // ESC n\n // Invoke the G2 Character Set as GL (LS2).\n case 'n':\n this._terminal.setgLevel(2);\n break;\n // ESC o\n // Invoke the G3 Character Set as GL (LS3).\n case 'o':\n this._terminal.setgLevel(3);\n break;\n // ESC |\n // Invoke the G3 Character Set as GR (LS3R).\n case '|':\n this._terminal.setgLevel(3);\n break;\n // ESC }\n // Invoke the G2 Character Set as GR (LS2R).\n case '}':\n this._terminal.setgLevel(2);\n break;\n // ESC ~\n // Invoke the G1 Character Set as GR (LS1R).\n case '~':\n this._terminal.setgLevel(1);\n break;\n\n // ESC 7 Save Cursor (DECSC).\n case '7':\n this._inputHandler.saveCursor();\n this._state = ParserState.NORMAL;\n break;\n\n // ESC 8 Restore Cursor (DECRC).\n case '8':\n this._inputHandler.restoreCursor();\n this._state = ParserState.NORMAL;\n break;\n\n // ESC # 3 DEC line height/width\n case '#':\n this._state = ParserState.NORMAL;\n this._position++;\n break;\n\n // ESC H Tab Set (HTS is 0x88).\n case 'H':\n this._terminal.tabSet();\n this._state = ParserState.NORMAL;\n break;\n\n // ESC = Application Keypad (DECKPAM).\n case '=':\n this._terminal.log('Serial port requested application keypad.');\n this._terminal.applicationKeypad = true;\n this._terminal.viewport.syncScrollArea();\n this._state = ParserState.NORMAL;\n break;\n\n // ESC > Normal Keypad (DECKPNM).\n case '>':\n this._terminal.log('Switching back to normal keypad.');\n this._terminal.applicationKeypad = false;\n this._terminal.viewport.syncScrollArea();\n this._state = ParserState.NORMAL;\n break;\n\n default:\n this._state = ParserState.NORMAL;\n this._terminal.error('Unknown ESC control: %s.', ch);\n break;\n }\n break;\n\n case ParserState.CHARSET:\n if (ch in CHARSETS) {\n cs = CHARSETS[ch];\n if (ch === '/') { // ISOLatin is actually /A\n this.skipNextChar();\n }\n } else {\n cs = DEFAULT_CHARSET;\n }\n this._terminal.setgCharset(this._terminal.gcharset, cs);\n this._terminal.gcharset = null;\n this._state = ParserState.NORMAL;\n break;\n\n case ParserState.OSC:\n // OSC Ps ; Pt ST\n // OSC Ps ; Pt BEL\n // Set Text Parameters.\n if (ch === C0.ESC || ch === C0.BEL) {\n if (ch === C0.ESC) this._position++;\n\n this._terminal.params.push(this._terminal.currentParam);\n\n switch (this._terminal.params[0]) {\n case 0:\n case 1:\n case 2:\n if (this._terminal.params[1]) {\n this._terminal.title = this._terminal.params[1];\n this._terminal.handleTitle(this._terminal.title);\n }\n break;\n case 3:\n // set X property\n break;\n case 4:\n case 5:\n // change dynamic colors\n break;\n case 10:\n case 11:\n case 12:\n case 13:\n case 14:\n case 15:\n case 16:\n case 17:\n case 18:\n case 19:\n // change dynamic ui colors\n break;\n case 46:\n // change log file\n break;\n case 50:\n // dynamic font\n break;\n case 51:\n // emacs shell\n break;\n case 52:\n // manipulate selection data\n break;\n case 104:\n case 105:\n case 110:\n case 111:\n case 112:\n case 113:\n case 114:\n case 115:\n case 116:\n case 117:\n case 118:\n // reset colors\n break;\n }\n\n this._terminal.params = [];\n this._terminal.currentParam = 0;\n this._state = ParserState.NORMAL;\n } else {\n if (!this._terminal.params.length) {\n if (ch >= '0' && ch <= '9') {\n this._terminal.currentParam =\n this._terminal.currentParam * 10 + ch.charCodeAt(0) - 48;\n } else if (ch === ';') {\n this._terminal.params.push(this._terminal.currentParam);\n this._terminal.currentParam = '';\n }\n } else {\n this._terminal.currentParam += ch;\n }\n }\n break;\n\n case ParserState.CSI_PARAM:\n if (ch in csiParamStateHandler) {\n csiParamStateHandler[ch](this);\n break;\n }\n this.finalizeParam();\n // Fall through the CSI as this character should be the CSI code.\n this._state = ParserState.CSI;\n\n case ParserState.CSI:\n if (ch in csiStateHandler) {\n if (this._terminal.debug) {\n this._terminal.log(`CSI ${this._terminal.prefix ? this._terminal.prefix : ''} ${this._terminal.params ? this._terminal.params.join(';') : ''} ${this._terminal.postfix ? this._terminal.postfix : ''} ${ch}`);\n }\n csiStateHandler[ch](this._inputHandler, this._terminal.params, this._terminal.prefix, this._terminal.postfix, this);\n } else {\n this._terminal.error('Unknown CSI code: %s.', ch);\n }\n\n this._state = ParserState.NORMAL;\n this._terminal.prefix = '';\n this._terminal.postfix = '';\n break;\n\n case ParserState.DCS:\n if (ch === C0.ESC || ch === C0.BEL) {\n if (ch === C0.ESC) this._position++;\n let pt;\n let valid: boolean;\n\n switch (this._terminal.prefix) {\n // User-Defined Keys (DECUDK).\n case '':\n break;\n\n // Request Status String (DECRQSS).\n // test: echo -e '\\eP$q\"p\\e\\\\'\n case '$q':\n pt = this._terminal.currentParam;\n valid = false;\n\n switch (pt) {\n // DECSCA\n case '\"q':\n pt = '0\"q';\n break;\n\n // DECSCL\n case '\"p':\n pt = '61\"p';\n break;\n\n // DECSTBM\n case 'r':\n pt = ''\n + (this._terminal.buffer.scrollTop + 1)\n + ';'\n + (this._terminal.buffer.scrollBottom + 1)\n + 'r';\n break;\n\n // SGR\n case 'm':\n pt = '0m';\n break;\n\n default:\n this._terminal.error('Unknown DCS Pt: %s.', pt);\n pt = '';\n break;\n }\n\n this._terminal.send(C0.ESC + 'P' + +valid + '$r' + pt + C0.ESC + '\\\\');\n break;\n\n // Set Termcap/Terminfo Data (xterm, experimental).\n case '+p':\n break;\n\n // Request Termcap/Terminfo String (xterm, experimental)\n // Regular xterm does not even respond to this sequence.\n // This can cause a small glitch in vim.\n // test: echo -ne '\\eP+q6b64\\e\\\\'\n case '+q':\n pt = this._terminal.currentParam;\n valid = false;\n\n this._terminal.send(C0.ESC + 'P' + +valid + '+r' + pt + C0.ESC + '\\\\');\n break;\n\n default:\n this._terminal.error('Unknown DCS prefix: %s.', this._terminal.prefix);\n break;\n }\n\n this._terminal.currentParam = 0;\n this._terminal.prefix = '';\n this._state = ParserState.NORMAL;\n } else if (!this._terminal.currentParam) {\n if (!this._terminal.prefix && ch !== '$' && ch !== '+') {\n this._terminal.currentParam = ch;\n } else if (this._terminal.prefix.length === 2) {\n this._terminal.currentParam = ch;\n } else {\n this._terminal.prefix += ch;\n }\n } else {\n this._terminal.currentParam += ch;\n }\n break;\n\n case ParserState.IGNORE:\n // For PM and APC.\n if (ch === C0.ESC || ch === C0.BEL) {\n if (ch === C0.ESC) this._position++;\n this._state = ParserState.NORMAL;\n }\n break;\n }\n }\n return this._state;\n }\n\n /**\n * Set the parser's current parsing state.\n *\n * @param state The new state.\n */\n public setState(state: ParserState): void {\n this._state = state;\n }\n\n /**\n * Sets the parsier's current prefix. CSI codes can have prefixes of '?', '>'\n * or '!'.\n *\n * @param prefix The prefix.\n */\n public setPrefix(prefix: string): void {\n this._terminal.prefix = prefix;\n }\n\n /**\n * Sets the parsier's current prefix. CSI codes can have postfixes of '$',\n * '\"', ' ', '\\''.\n *\n * @param postfix The postfix.\n */\n public setPostfix(postfix: string): void {\n this._terminal.postfix = postfix;\n }\n\n /**\n * Sets the parser's current parameter.\n *\n * @param param the parameter.\n */\n public setParam(param: number) {\n this._terminal.currentParam = param;\n }\n\n /**\n * Gets the parser's current parameter.\n */\n public getParam(): number {\n return this._terminal.currentParam;\n }\n\n /**\n * Finalizes the parser's current parameter, adding it to the list of\n * parameters and setting the new current parameter to 0.\n */\n public finalizeParam(): void {\n this._terminal.params.push(this._terminal.currentParam);\n this._terminal.currentParam = 0;\n }\n\n /**\n * Tell the parser to skip the next character.\n */\n public skipNextChar(): void {\n this._position++;\n }\n\n /**\n * Tell the parser to repeat parsing the current character (for example if it\n * needs parsing using a different state.\n */\n // public repeatChar(): void {\n // this._position--;\n // }\n}\n","/**\n * @license MIT\n */\n\nimport { LinkMatcherOptions } from './Interfaces';\nimport { LinkMatcher, LinkMatcherHandler, LinkMatcherValidationCallback } from './Types';\n\nconst INVALID_LINK_CLASS = 'xterm-invalid-link';\n\nconst protocolClause = '(https?:\\\\/\\\\/)';\nconst domainCharacterSet = '[\\\\da-z\\\\.-]+';\nconst negatedDomainCharacterSet = '[^\\\\da-z\\\\.-]+';\nconst domainBodyClause = '(' + domainCharacterSet + ')';\nconst tldClause = '([a-z\\\\.]{2,6})';\nconst ipClause = '((\\\\d{1,3}\\\\.){3}\\\\d{1,3})';\nconst localHostClause = '(localhost)';\nconst portClause = '(:\\\\d{1,5})';\nconst hostClause = '((' + domainBodyClause + '\\\\.' + tldClause + ')|' + ipClause + '|' + localHostClause + ')' + portClause + '?';\nconst pathClause = '(\\\\/[\\\\/\\\\w\\\\.\\\\-%~]*)*';\nconst queryStringHashFragmentCharacterSet = '[0-9\\\\w\\\\[\\\\]\\\\(\\\\)\\\\/\\\\?\\\\!#@$%&\\'*+,:;~\\\\=\\\\.\\\\-]*';\nconst queryStringClause = '(\\\\?' + queryStringHashFragmentCharacterSet + ')?';\nconst hashFragmentClause = '(#' + queryStringHashFragmentCharacterSet + ')?';\nconst negatedPathCharacterSet = '[^\\\\/\\\\w\\\\.\\\\-%]+';\nconst bodyClause = hostClause + pathClause + queryStringClause + hashFragmentClause;\nconst start = '(?:^|' + negatedDomainCharacterSet + ')(';\nconst end = ')($|' + negatedPathCharacterSet + ')';\nconst strictUrlRegex = new RegExp(start + protocolClause + bodyClause + end);\n\n/**\n * The ID of the built in http(s) link matcher.\n */\nconst HYPERTEXT_LINK_MATCHER_ID = 0;\n\n/**\n * The Linkifier applies links to rows shortly after they have been refreshed.\n */\nexport class Linkifier {\n /**\n * The time to wait after a row is changed before it is linkified. This prevents\n * the costly operation of searching every row multiple times, potentially a\n * huge amount of times.\n */\n protected static TIME_BEFORE_LINKIFY = 200;\n\n protected _linkMatchers: LinkMatcher[];\n\n private _document: Document;\n private _rows: HTMLElement[];\n private _rowTimeoutIds: number[];\n private _nextLinkMatcherId = HYPERTEXT_LINK_MATCHER_ID;\n\n constructor() {\n this._rowTimeoutIds = [];\n this._linkMatchers = [];\n this.registerLinkMatcher(strictUrlRegex, null, { matchIndex: 1 });\n }\n\n /**\n * Attaches the linkifier to the DOM, enabling linkification.\n * @param document The document object.\n * @param rows The array of rows to apply links to.\n */\n public attachToDom(document: Document, rows: HTMLElement[]) {\n this._document = document;\n this._rows = rows;\n }\n\n /**\n * Queues a row for linkification.\n * @param {number} rowIndex The index of the row to linkify.\n */\n public linkifyRow(rowIndex: number): void {\n // Don't attempt linkify if not yet attached to DOM\n if (!this._document) {\n return;\n }\n\n const timeoutId = this._rowTimeoutIds[rowIndex];\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n this._rowTimeoutIds[rowIndex] = setTimeout(this._linkifyRow.bind(this, rowIndex), Linkifier.TIME_BEFORE_LINKIFY);\n }\n\n /**\n * Attaches a handler for hypertext links, overriding default behavior\n * for standard http(s) links.\n * @param {LinkHandler} handler The handler to use, this can be cleared with\n * null.\n */\n public setHypertextLinkHandler(handler: LinkMatcherHandler): void {\n this._linkMatchers[HYPERTEXT_LINK_MATCHER_ID].handler = handler;\n }\n\n /**\n * Attaches a validation callback for hypertext links.\n * @param {LinkMatcherValidationCallback} callback The callback to use, this\n * can be cleared with null.\n */\n public setHypertextValidationCallback(callback: LinkMatcherValidationCallback): void {\n this._linkMatchers[HYPERTEXT_LINK_MATCHER_ID].validationCallback = callback;\n }\n\n /**\n * Registers a link matcher, allowing custom link patterns to be matched and\n * handled.\n * @param {RegExp} regex The regular expression to search for, specifically\n * this searches the textContent of the rows. You will want to use \\s to match\n * a space ' ' character for example.\n * @param {LinkHandler} handler The callback when the link is called.\n * @param {LinkMatcherOptions} [options] Options for the link matcher.\n * @return {number} The ID of the new matcher, this can be used to deregister.\n */\n public registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options: LinkMatcherOptions = {}): number {\n if (this._nextLinkMatcherId !== HYPERTEXT_LINK_MATCHER_ID && !handler) {\n throw new Error('handler must be defined');\n }\n const matcher: LinkMatcher = {\n id: this._nextLinkMatcherId++,\n regex,\n handler,\n matchIndex: options.matchIndex,\n validationCallback: options.validationCallback,\n priority: options.priority || 0\n };\n this._addLinkMatcherToList(matcher);\n return matcher.id;\n }\n\n /**\n * Inserts a link matcher to the list in the correct position based on the\n * priority of each link matcher. New link matchers of equal priority are\n * considered after older link matchers.\n * @param matcher The link matcher to be added.\n */\n private _addLinkMatcherToList(matcher: LinkMatcher): void {\n if (this._linkMatchers.length === 0) {\n this._linkMatchers.push(matcher);\n return;\n }\n\n for (let i = this._linkMatchers.length - 1; i >= 0; i--) {\n if (matcher.priority <= this._linkMatchers[i].priority) {\n this._linkMatchers.splice(i + 1, 0, matcher);\n return;\n }\n }\n\n this._linkMatchers.splice(0, 0, matcher);\n }\n\n /**\n * Deregisters a link matcher if it has been registered.\n * @param {number} matcherId The link matcher's ID (returned after register)\n * @return {boolean} Whether a link matcher was found and deregistered.\n */\n public deregisterLinkMatcher(matcherId: number): boolean {\n // ID 0 is the hypertext link matcher which cannot be deregistered\n for (let i = 1; i < this._linkMatchers.length; i++) {\n if (this._linkMatchers[i].id === matcherId) {\n this._linkMatchers.splice(i, 1);\n return true;\n }\n }\n return false;\n }\n\n /**\n * Linkifies a row.\n * @param {number} rowIndex The index of the row to linkify.\n */\n private _linkifyRow(rowIndex: number): void {\n const row = this._rows[rowIndex];\n if (!row) {\n return;\n }\n const text = row.textContent;\n for (let i = 0; i < this._linkMatchers.length; i++) {\n const matcher = this._linkMatchers[i];\n const linkElements = this._doLinkifyRow(row, matcher);\n if (linkElements.length > 0) {\n // Fire validation callback\n if (matcher.validationCallback) {\n for (let j = 0; j < linkElements.length; j++) {\n const element = linkElements[j];\n matcher.validationCallback(element.textContent, element, isValid => {\n if (!isValid) {\n element.classList.add(INVALID_LINK_CLASS);\n }\n });\n }\n }\n // Only allow a single LinkMatcher to trigger on any given row.\n return;\n }\n }\n }\n\n /**\n * Linkifies a row given a specific handler.\n * @param {HTMLElement} row The row to linkify.\n * @param {LinkMatcher} matcher The link matcher for this line.\n * @return The link element(s) that were added.\n */\n private _doLinkifyRow(row: HTMLElement, matcher: LinkMatcher): HTMLElement[] {\n // Iterate over nodes as we want to consider text nodes\n let result = [];\n const isHttpLinkMatcher = matcher.id === HYPERTEXT_LINK_MATCHER_ID;\n const nodes = row.childNodes;\n\n // Find the first match\n let match = row.textContent.match(matcher.regex);\n if (!match || match.length === 0) {\n return result;\n }\n let uri = match[typeof matcher.matchIndex !== 'number' ? 0 : matcher.matchIndex];\n // Set the next searches start index\n let rowStartIndex = match.index + uri.length;\n\n for (let i = 0; i < nodes.length; i++) {\n const node = nodes[i];\n const searchIndex = node.textContent.indexOf(uri);\n if (searchIndex >= 0) {\n const linkElement = this._createAnchorElement(uri, matcher.handler, isHttpLinkMatcher);\n if (node.textContent.length === uri.length) {\n // Matches entire string\n if (node.nodeType === 3 /*Node.TEXT_NODE*/) {\n this._replaceNode(node, linkElement);\n } else {\n const element = (node);\n if (element.nodeName === 'A') {\n // This row has already been linkified\n return result;\n }\n element.innerHTML = '';\n element.appendChild(linkElement);\n }\n } else if (node.childNodes.length > 1) {\n // Matches part of string in an element with multiple child nodes\n for (let j = 0; j < node.childNodes.length; j++) {\n const childNode = node.childNodes[j];\n const childSearchIndex = childNode.textContent.indexOf(uri);\n if (childSearchIndex !== -1) {\n // Match found in currentNode\n this._replaceNodeSubstringWithNode(childNode, linkElement, uri, childSearchIndex);\n // Don't need to count nodesAdded by replacing the node as this\n // is a child node, not a top-level node.\n break;\n }\n }\n } else {\n // Matches part of string in a single text node\n const nodesAdded = this._replaceNodeSubstringWithNode(node, linkElement, uri, searchIndex);\n // No need to consider the new nodes\n i += nodesAdded;\n }\n result.push(linkElement);\n\n // Find the next match\n match = row.textContent.substring(rowStartIndex).match(matcher.regex);\n if (!match || match.length === 0) {\n return result;\n }\n uri = match[typeof matcher.matchIndex !== 'number' ? 0 : matcher.matchIndex];\n rowStartIndex += match.index + uri.length;\n }\n }\n return result;\n }\n\n /**\n * Creates a link anchor element.\n * @param {string} uri The uri of the link.\n * @return {HTMLAnchorElement} The link.\n */\n private _createAnchorElement(uri: string, handler: LinkMatcherHandler, isHypertextLinkHandler: boolean): HTMLAnchorElement {\n const element = this._document.createElement('a');\n element.textContent = uri;\n element.draggable = false;\n if (isHypertextLinkHandler) {\n element.href = uri;\n // Force link on another tab so work is not lost\n element.target = '_blank';\n element.addEventListener('click', (event: MouseEvent) => {\n if (handler) {\n return handler(event, uri);\n }\n });\n } else {\n element.addEventListener('click', (event: MouseEvent) => {\n // Don't execute the handler if the link is flagged as invalid\n if (element.classList.contains(INVALID_LINK_CLASS)) {\n return;\n }\n return handler(event, uri);\n });\n }\n return element;\n }\n\n /**\n * Replace a node with 1 or more other nodes.\n * @param {Node} oldNode The node to replace.\n * @param {Node[]} newNodes The new nodes to insert in order.\n */\n private _replaceNode(oldNode: Node, ...newNodes: Node[]): void {\n const parent = oldNode.parentNode;\n for (let i = 0; i < newNodes.length; i++) {\n parent.insertBefore(newNodes[i], oldNode);\n }\n parent.removeChild(oldNode);\n }\n\n /**\n * Replace a substring within a node with a new node.\n * @param {Node} targetNode The target node; either a text node or a \n * containing a single text node.\n * @param {Node} newNode The new node to insert.\n * @param {string} substring The substring to replace.\n * @param {number} substringIndex The index of the substring within the string.\n * @return The number of nodes to skip when searching for the next uri.\n */\n private _replaceNodeSubstringWithNode(targetNode: Node, newNode: Node, substring: string, substringIndex: number): number {\n // If the targetNode is a non-text node with a single child, make the child\n // the new targetNode.\n if (targetNode.childNodes.length === 1) {\n targetNode = targetNode.childNodes[0];\n }\n\n // The targetNode will be either a text node or a . The text node\n // (targetNode or its only-child) needs to be replaced with newNode plus new\n // text nodes potentially on either side.\n if (targetNode.nodeType !== 3/*Node.TEXT_NODE*/) {\n throw new Error('targetNode must be a text node or only contain a single text node');\n }\n\n const fullText = targetNode.textContent;\n\n if (substringIndex === 0) {\n // Replace with \n const rightText = fullText.substring(substring.length);\n const rightTextNode = this._document.createTextNode(rightText);\n this._replaceNode(targetNode, newNode, rightTextNode);\n return 0;\n }\n\n if (substringIndex === targetNode.textContent.length - substring.length) {\n // Replace with \n const leftText = fullText.substring(0, substringIndex);\n const leftTextNode = this._document.createTextNode(leftText);\n this._replaceNode(targetNode, leftTextNode, newNode);\n return 0;\n }\n\n // Replace with \n const leftText = fullText.substring(0, substringIndex);\n const leftTextNode = this._document.createTextNode(leftText);\n const rightText = fullText.substring(substringIndex + substring.length);\n const rightTextNode = this._document.createTextNode(rightText);\n this._replaceNode(targetNode, leftTextNode, newNode, rightTextNode);\n return 1;\n }\n}\n","/**\n * @license MIT\n */\n\nimport { IInputHandler, ITerminal } from './Interfaces';\nimport { C0 } from './EscapeSequences';\nimport { DEFAULT_CHARSET } from './Charsets';\n\n/**\n * The terminal's standard implementation of IInputHandler, this handles all\n * input from the Parser.\n *\n * Refer to http://invisible-island.net/xterm/ctlseqs/ctlseqs.html to understand\n * each function's header comment.\n */\nexport class InputHandler implements IInputHandler {\n // TODO: We want to type _terminal when it's pulled into TS\n constructor(private _terminal: any) { }\n\n public addChar(char: string, code: number): void {\n if (char >= ' ') {\n // calculate print space\n // expensive call, therefore we save width in line buffer\n const ch_width = wcwidth(code);\n\n if (this._terminal.charset && this._terminal.charset[char]) {\n char = this._terminal.charset[char];\n }\n\n let row = this._terminal.buffer.y + this._terminal.buffer.ybase;\n\n // insert combining char in last cell\n // FIXME: needs handling after cursor jumps\n if (!ch_width && this._terminal.buffer.x) {\n // dont overflow left\n if (this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1]) {\n if (!this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][2]) {\n\n // found empty cell after fullwidth, need to go 2 cells back\n if (this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 2])\n this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 2][1] += char;\n\n } else {\n this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][1] += char;\n }\n this._terminal.updateRange(this._terminal.buffer.y);\n }\n return;\n }\n\n // goto next line if ch would overflow\n // TODO: needs a global min terminal width of 2\n if (this._terminal.buffer.x + ch_width - 1 >= this._terminal.cols) {\n // autowrap - DECAWM\n if (this._terminal.wraparoundMode) {\n this._terminal.buffer.x = 0;\n this._terminal.buffer.y++;\n if (this._terminal.buffer.y > this._terminal.buffer.scrollBottom) {\n this._terminal.buffer.y--;\n this._terminal.scroll(true);\n } else {\n // The line already exists (eg. the initial viewport), mark it as a\n // wrapped line\n this._terminal.buffer.lines.get(this._terminal.buffer.y).isWrapped = true;\n }\n } else {\n if (ch_width === 2) // FIXME: check for xterm behavior\n return;\n }\n }\n row = this._terminal.buffer.y + this._terminal.buffer.ybase;\n\n // insert mode: move characters to right\n if (this._terminal.insertMode) {\n // do this twice for a fullwidth char\n for (let moves = 0; moves < ch_width; ++moves) {\n // remove last cell, if it's width is 0\n // we have to adjust the second last cell as well\n const removed = this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).pop();\n if (removed[2] === 0\n && this._terminal.buffer.lines.get(row)[this._terminal.cols - 2]\n && this._terminal.buffer.lines.get(row)[this._terminal.cols - 2][2] === 2) {\n this._terminal.buffer.lines.get(row)[this._terminal.cols - 2] = [this._terminal.curAttr, ' ', 1];\n }\n\n // insert empty cell at cursor\n this._terminal.buffer.lines.get(row).splice(this._terminal.buffer.x, 0, [this._terminal.curAttr, ' ', 1]);\n }\n }\n\n this._terminal.buffer.lines.get(row)[this._terminal.buffer.x] = [this._terminal.curAttr, char, ch_width];\n this._terminal.buffer.x++;\n this._terminal.updateRange(this._terminal.buffer.y);\n\n // fullwidth char - set next cell width to zero and advance cursor\n if (ch_width === 2) {\n this._terminal.buffer.lines.get(row)[this._terminal.buffer.x] = [this._terminal.curAttr, '', 0];\n this._terminal.buffer.x++;\n }\n }\n }\n\n /**\n * BEL\n * Bell (Ctrl-G).\n */\n public bell(): void {\n if (!this._terminal.visualBell) {\n return;\n }\n this._terminal.element.style.borderColor = 'white';\n setTimeout(() => this._terminal.element.style.borderColor = '', 10);\n if (this._terminal.popOnBell) {\n this._terminal.focus();\n }\n }\n\n /**\n * LF\n * Line Feed or New Line (NL). (LF is Ctrl-J).\n */\n public lineFeed(): void {\n if (this._terminal.convertEol) {\n this._terminal.buffer.x = 0;\n }\n this._terminal.buffer.y++;\n if (this._terminal.buffer.y > this._terminal.buffer.scrollBottom) {\n this._terminal.buffer.y--;\n this._terminal.scroll();\n }\n // If the end of the line is hit, prevent this action from wrapping around to the next line.\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x--;\n }\n /**\n * This event is emitted whenever the terminal outputs a LF or NL.\n *\n * @event lineFeed\n */\n this._terminal.emit('lineFeed');\n }\n\n /**\n * CR\n * Carriage Return (Ctrl-M).\n */\n public carriageReturn(): void {\n this._terminal.buffer.x = 0;\n }\n\n /**\n * BS\n * Backspace (Ctrl-H).\n */\n public backspace(): void {\n if (this._terminal.buffer.x > 0) {\n this._terminal.buffer.x--;\n }\n }\n\n /**\n * TAB\n * Horizontal Tab (HT) (Ctrl-I).\n */\n public tab(): void {\n this._terminal.buffer.x = this._terminal.nextStop();\n }\n\n /**\n * SO\n * Shift Out (Ctrl-N) -> Switch to Alternate Character Set. This invokes the\n * G1 character set.\n */\n public shiftOut(): void {\n this._terminal.setgLevel(1);\n }\n\n /**\n * SI\n * Shift In (Ctrl-O) -> Switch to Standard Character Set. This invokes the G0\n * character set (the default).\n */\n public shiftIn(): void {\n this._terminal.setgLevel(0);\n }\n\n /**\n * CSI Ps @\n * Insert Ps (Blank) Character(s) (default = 1) (ICH).\n */\n public insertChars(params: number[]): void {\n let param, row, j, ch;\n\n param = params[0];\n if (param < 1) param = 1;\n\n row = this._terminal.buffer.y + this._terminal.buffer.ybase;\n j = this._terminal.buffer.x;\n ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm\n\n while (param-- && j < this._terminal.cols) {\n this._terminal.buffer.lines.get(row).splice(j++, 0, ch);\n this._terminal.buffer.lines.get(row).pop();\n }\n }\n\n /**\n * CSI Ps A\n * Cursor Up Ps Times (default = 1) (CUU).\n */\n public cursorUp(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.y -= param;\n if (this._terminal.buffer.y < 0) {\n this._terminal.buffer.y = 0;\n }\n }\n\n /**\n * CSI Ps B\n * Cursor Down Ps Times (default = 1) (CUD).\n */\n public cursorDown(params: number[]) {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.y += param;\n if (this._terminal.buffer.y >= this._terminal.rows) {\n this._terminal.buffer.y = this._terminal.rows - 1;\n }\n // If the end of the line is hit, prevent this action from wrapping around to the next line.\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x--;\n }\n }\n\n /**\n * CSI Ps C\n * Cursor Forward Ps Times (default = 1) (CUF).\n */\n public cursorForward(params: number[]) {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.x += param;\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x = this._terminal.cols - 1;\n }\n }\n\n /**\n * CSI Ps D\n * Cursor Backward Ps Times (default = 1) (CUB).\n */\n public cursorBackward(params: number[]) {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n // If the end of the line is hit, prevent this action from wrapping around to the next line.\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x--;\n }\n this._terminal.buffer.x -= param;\n if (this._terminal.buffer.x < 0) {\n this._terminal.buffer.x = 0;\n }\n }\n\n /**\n * CSI Ps E\n * Cursor Next Line Ps Times (default = 1) (CNL).\n * same as CSI Ps B ?\n */\n public cursorNextLine(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.y += param;\n if (this._terminal.buffer.y >= this._terminal.rows) {\n this._terminal.buffer.y = this._terminal.rows - 1;\n }\n this._terminal.buffer.x = 0;\n }\n\n\n /**\n * CSI Ps F\n * Cursor Preceding Line Ps Times (default = 1) (CNL).\n * reuse CSI Ps A ?\n */\n public cursorPrecedingLine(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.y -= param;\n if (this._terminal.buffer.y < 0) {\n this._terminal.buffer.y = 0;\n }\n this._terminal.buffer.x = 0;\n }\n\n\n /**\n * CSI Ps G\n * Cursor Character Absolute [column] (default = [row,1]) (CHA).\n */\n public cursorCharAbsolute(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.x = param - 1;\n }\n\n /**\n * CSI Ps ; Ps H\n * Cursor Position [row;column] (default = [1,1]) (CUP).\n */\n public cursorPosition(params: number[]): void {\n let row, col;\n\n row = params[0] - 1;\n\n if (params.length >= 2) {\n col = params[1] - 1;\n } else {\n col = 0;\n }\n\n if (row < 0) {\n row = 0;\n } else if (row >= this._terminal.rows) {\n row = this._terminal.rows - 1;\n }\n\n if (col < 0) {\n col = 0;\n } else if (col >= this._terminal.cols) {\n col = this._terminal.cols - 1;\n }\n\n this._terminal.buffer.x = col;\n this._terminal.buffer.y = row;\n }\n\n /**\n * CSI Ps I\n * Cursor Forward Tabulation Ps tab stops (default = 1) (CHT).\n */\n public cursorForwardTab(params: number[]): void {\n let param = params[0] || 1;\n while (param--) {\n this._terminal.buffer.x = this._terminal.nextStop();\n }\n }\n\n /**\n * CSI Ps J Erase in Display (ED).\n * Ps = 0 -> Erase Below (default).\n * Ps = 1 -> Erase Above.\n * Ps = 2 -> Erase All.\n * Ps = 3 -> Erase Saved Lines (xterm).\n * CSI ? Ps J\n * Erase in Display (DECSED).\n * Ps = 0 -> Selective Erase Below (default).\n * Ps = 1 -> Selective Erase Above.\n * Ps = 2 -> Selective Erase All.\n */\n public eraseInDisplay(params: number[]): void {\n let j;\n switch (params[0]) {\n case 0:\n this._terminal.eraseRight(this._terminal.buffer.x, this._terminal.buffer.y);\n j = this._terminal.buffer.y + 1;\n for (; j < this._terminal.rows; j++) {\n this._terminal.eraseLine(j);\n }\n break;\n case 1:\n this._terminal.eraseLeft(this._terminal.buffer.x, this._terminal.buffer.y);\n j = this._terminal.buffer.y;\n while (j--) {\n this._terminal.eraseLine(j);\n }\n break;\n case 2:\n j = this._terminal.rows;\n while (j--) this._terminal.eraseLine(j);\n break;\n case 3:\n // Clear scrollback (everything not in viewport)\n const scrollBackSize = this._terminal.buffer.lines.length - this._terminal.rows;\n if (scrollBackSize > 0) {\n this._terminal.buffer.lines.trimStart(scrollBackSize);\n this._terminal.buffer.ybase = Math.max(this._terminal.buffer.ybase - scrollBackSize, 0);\n this._terminal.buffer.ydisp = Math.max(this._terminal.buffer.ydisp - scrollBackSize, 0);\n // Force a scroll event to refresh viewport\n this._terminal.emit('scroll', 0);\n }\n break;\n }\n }\n\n /**\n * CSI Ps K Erase in Line (EL).\n * Ps = 0 -> Erase to Right (default).\n * Ps = 1 -> Erase to Left.\n * Ps = 2 -> Erase All.\n * CSI ? Ps K\n * Erase in Line (DECSEL).\n * Ps = 0 -> Selective Erase to Right (default).\n * Ps = 1 -> Selective Erase to Left.\n * Ps = 2 -> Selective Erase All.\n */\n public eraseInLine(params: number[]): void {\n switch (params[0]) {\n case 0:\n this._terminal.eraseRight(this._terminal.buffer.x, this._terminal.buffer.y);\n break;\n case 1:\n this._terminal.eraseLeft(this._terminal.buffer.x, this._terminal.buffer.y);\n break;\n case 2:\n this._terminal.eraseLine(this._terminal.buffer.y);\n break;\n }\n }\n\n /**\n * CSI Ps L\n * Insert Ps Line(s) (default = 1) (IL).\n */\n public insertLines(params: number[]): void {\n let param, row, j;\n\n param = params[0];\n if (param < 1) {\n param = 1;\n }\n row = this._terminal.buffer.y + this._terminal.buffer.ybase;\n\n j = this._terminal.rows - 1 - this._terminal.buffer.scrollBottom;\n j = this._terminal.rows - 1 + this._terminal.buffer.ybase - j + 1;\n\n while (param--) {\n if (this._terminal.buffer.lines.length === this._terminal.buffer.lines.maxLength) {\n // Trim the start of lines to make room for the new line\n this._terminal.buffer.lines.trimStart(1);\n this._terminal.buffer.ybase--;\n this._terminal.buffer.ydisp--;\n row--;\n j--;\n }\n // test: echo -e '\\e[44m\\e[1L\\e[0m'\n // blankLine(true) - xterm/linux behavior\n this._terminal.buffer.lines.splice(row, 0, this._terminal.blankLine(true));\n this._terminal.buffer.lines.splice(j, 1);\n }\n\n // this.maxRange();\n this._terminal.updateRange(this._terminal.buffer.y);\n this._terminal.updateRange(this._terminal.buffer.scrollBottom);\n }\n\n /**\n * CSI Ps M\n * Delete Ps Line(s) (default = 1) (DL).\n */\n public deleteLines(params: number[]): void {\n let param, row, j;\n\n param = params[0];\n if (param < 1) {\n param = 1;\n }\n row = this._terminal.buffer.y + this._terminal.buffer.ybase;\n\n j = this._terminal.rows - 1 - this._terminal.buffer.scrollBottom;\n j = this._terminal.rows - 1 + this._terminal.buffer.ybase - j;\n\n while (param--) {\n if (this._terminal.buffer.lines.length === this._terminal.buffer.lines.maxLength) {\n // Trim the start of lines to make room for the new line\n this._terminal.buffer.lines.trimStart(1);\n this._terminal.buffer.ybase -= 1;\n this._terminal.buffer.ydisp -= 1;\n }\n // test: echo -e '\\e[44m\\e[1M\\e[0m'\n // blankLine(true) - xterm/linux behavior\n this._terminal.buffer.lines.splice(j + 1, 0, this._terminal.blankLine(true));\n this._terminal.buffer.lines.splice(row, 1);\n }\n\n // this.maxRange();\n this._terminal.updateRange(this._terminal.buffer.y);\n this._terminal.updateRange(this._terminal.buffer.scrollBottom);\n }\n\n /**\n * CSI Ps P\n * Delete Ps Character(s) (default = 1) (DCH).\n */\n public deleteChars(params: number[]): void {\n let param, row, ch;\n\n param = params[0];\n if (param < 1) {\n param = 1;\n }\n\n row = this._terminal.buffer.y + this._terminal.buffer.ybase;\n ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm\n\n while (param--) {\n this._terminal.buffer.lines.get(row).splice(this._terminal.buffer.x, 1);\n this._terminal.buffer.lines.get(row).push(ch);\n }\n }\n\n /**\n * CSI Ps S Scroll up Ps lines (default = 1) (SU).\n */\n public scrollUp(params: number[]): void {\n let param = params[0] || 1;\n while (param--) {\n this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollTop, 1);\n this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollBottom, 0, this._terminal.blankLine());\n }\n // this.maxRange();\n this._terminal.updateRange(this._terminal.buffer.scrollTop);\n this._terminal.updateRange(this._terminal.buffer.scrollBottom);\n }\n\n /**\n * CSI Ps T Scroll down Ps lines (default = 1) (SD).\n */\n public scrollDown(params: number[]): void {\n let param = params[0] || 1;\n while (param--) {\n this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollBottom, 1);\n this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollTop, 0, this._terminal.blankLine());\n }\n // this.maxRange();\n this._terminal.updateRange(this._terminal.buffer.scrollTop);\n this._terminal.updateRange(this._terminal.buffer.scrollBottom);\n }\n\n /**\n * CSI Ps X\n * Erase Ps Character(s) (default = 1) (ECH).\n */\n public eraseChars(params: number[]): void {\n let param, row, j, ch;\n\n param = params[0];\n if (param < 1) {\n param = 1;\n }\n\n row = this._terminal.buffer.y + this._terminal.buffer.ybase;\n j = this._terminal.buffer.x;\n ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm\n\n while (param-- && j < this._terminal.cols) {\n this._terminal.buffer.lines.get(row)[j++] = ch;\n }\n }\n\n /**\n * CSI Ps Z Cursor Backward Tabulation Ps tab stops (default = 1) (CBT).\n */\n public cursorBackwardTab(params: number[]): void {\n let param = params[0] || 1;\n while (param--) {\n this._terminal.buffer.x = this._terminal.prevStop();\n }\n }\n\n /**\n * CSI Pm ` Character Position Absolute\n * [column] (default = [row,1]) (HPA).\n */\n public charPosAbsolute(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.x = param - 1;\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x = this._terminal.cols - 1;\n }\n }\n\n /**\n * CSI Pm a Character Position Relative\n * [columns] (default = [row,col+1]) (HPR)\n * reuse CSI Ps C ?\n */\n public HPositionRelative(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.x += param;\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x = this._terminal.cols - 1;\n }\n }\n\n /**\n * CSI Ps b Repeat the preceding graphic character Ps times (REP).\n */\n public repeatPrecedingCharacter(params: number[]): void {\n let param = params[0] || 1\n , line = this._terminal.buffer.lines.get(this._terminal.buffer.ybase + this._terminal.buffer.y)\n , ch = line[this._terminal.buffer.x - 1] || [this._terminal.defAttr, ' ', 1];\n\n while (param--) {\n line[this._terminal.buffer.x++] = ch;\n }\n }\n\n /**\n * CSI Ps c Send Device Attributes (Primary DA).\n * Ps = 0 or omitted -> request attributes from terminal. The\n * response depends on the decTerminalID resource setting.\n * -> CSI ? 1 ; 2 c (``VT100 with Advanced Video Option'')\n * -> CSI ? 1 ; 0 c (``VT101 with No Options'')\n * -> CSI ? 6 c (``VT102'')\n * -> CSI ? 6 0 ; 1 ; 2 ; 6 ; 8 ; 9 ; 1 5 ; c (``VT220'')\n * The VT100-style response parameters do not mean anything by\n * themselves. VT220 parameters do, telling the host what fea-\n * tures the terminal supports:\n * Ps = 1 -> 132-columns.\n * Ps = 2 -> Printer.\n * Ps = 6 -> Selective erase.\n * Ps = 8 -> User-defined keys.\n * Ps = 9 -> National replacement character sets.\n * Ps = 1 5 -> Technical characters.\n * Ps = 2 2 -> ANSI color, e.g., VT525.\n * Ps = 2 9 -> ANSI text locator (i.e., DEC Locator mode).\n * CSI > Ps c\n * Send Device Attributes (Secondary DA).\n * Ps = 0 or omitted -> request the terminal's identification\n * code. The response depends on the decTerminalID resource set-\n * ting. It should apply only to VT220 and up, but xterm extends\n * this to VT100.\n * -> CSI > Pp ; Pv ; Pc c\n * where Pp denotes the terminal type\n * Pp = 0 -> ``VT100''.\n * Pp = 1 -> ``VT220''.\n * and Pv is the firmware version (for xterm, this was originally\n * the XFree86 patch number, starting with 95). In a DEC termi-\n * nal, Pc indicates the ROM cartridge registration number and is\n * always zero.\n * More information:\n * xterm/charproc.c - line 2012, for more information.\n * vim responds with ^[[?0c or ^[[?1c after the terminal's response (?)\n */\n public sendDeviceAttributes(params: number[]): void {\n if (params[0] > 0) {\n return;\n }\n\n if (!this._terminal.prefix) {\n if (this._terminal.is('xterm') || this._terminal.is('rxvt-unicode') || this._terminal.is('screen')) {\n this._terminal.send(C0.ESC + '[?1;2c');\n } else if (this._terminal.is('linux')) {\n this._terminal.send(C0.ESC + '[?6c');\n }\n } else if (this._terminal.prefix === '>') {\n // xterm and urxvt\n // seem to spit this\n // out around ~370 times (?).\n if (this._terminal.is('xterm')) {\n this._terminal.send(C0.ESC + '[>0;276;0c');\n } else if (this._terminal.is('rxvt-unicode')) {\n this._terminal.send(C0.ESC + '[>85;95;0c');\n } else if (this._terminal.is('linux')) {\n // not supported by linux console.\n // linux console echoes parameters.\n this._terminal.send(params[0] + 'c');\n } else if (this._terminal.is('screen')) {\n this._terminal.send(C0.ESC + '[>83;40003;0c');\n }\n }\n }\n\n /**\n * CSI Pm d Vertical Position Absolute (VPA)\n * [row] (default = [1,column])\n */\n public linePosAbsolute(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.y = param - 1;\n if (this._terminal.buffer.y >= this._terminal.rows) {\n this._terminal.buffer.y = this._terminal.rows - 1;\n }\n }\n\n /**\n * CSI Pm e Vertical Position Relative (VPR)\n * [rows] (default = [row+1,column])\n * reuse CSI Ps B ?\n */\n public VPositionRelative(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.y += param;\n if (this._terminal.buffer.y >= this._terminal.rows) {\n this._terminal.buffer.y = this._terminal.rows - 1;\n }\n // If the end of the line is hit, prevent this action from wrapping around to the next line.\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x--;\n }\n }\n\n /**\n * CSI Ps ; Ps f\n * Horizontal and Vertical Position [row;column] (default =\n * [1,1]) (HVP).\n */\n public HVPosition(params: number[]): void {\n if (params[0] < 1) params[0] = 1;\n if (params[1] < 1) params[1] = 1;\n\n this._terminal.buffer.y = params[0] - 1;\n if (this._terminal.buffer.y >= this._terminal.rows) {\n this._terminal.buffer.y = this._terminal.rows - 1;\n }\n\n this._terminal.buffer.x = params[1] - 1;\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x = this._terminal.cols - 1;\n }\n }\n\n /**\n * CSI Ps g Tab Clear (TBC).\n * Ps = 0 -> Clear Current Column (default).\n * Ps = 3 -> Clear All.\n * Potentially:\n * Ps = 2 -> Clear Stops on Line.\n * http://vt100.net/annarbor/aaa-ug/section6.html\n */\n public tabClear(params: number[]): void {\n let param = params[0];\n if (param <= 0) {\n delete this._terminal.buffer.tabs[this._terminal.buffer.x];\n } else if (param === 3) {\n this._terminal.buffer.tabs = {};\n }\n }\n\n /**\n * CSI Pm h Set Mode (SM).\n * Ps = 2 -> Keyboard Action Mode (AM).\n * Ps = 4 -> Insert Mode (IRM).\n * Ps = 1 2 -> Send/receive (SRM).\n * Ps = 2 0 -> Automatic Newline (LNM).\n * CSI ? Pm h\n * DEC Private Mode Set (DECSET).\n * Ps = 1 -> Application Cursor Keys (DECCKM).\n * Ps = 2 -> Designate USASCII for character sets G0-G3\n * (DECANM), and set VT100 mode.\n * Ps = 3 -> 132 Column Mode (DECCOLM).\n * Ps = 4 -> Smooth (Slow) Scroll (DECSCLM).\n * Ps = 5 -> Reverse Video (DECSCNM).\n * Ps = 6 -> Origin Mode (DECOM).\n * Ps = 7 -> Wraparound Mode (DECAWM).\n * Ps = 8 -> Auto-repeat Keys (DECARM).\n * Ps = 9 -> Send Mouse X & Y on button press. See the sec-\n * tion Mouse Tracking.\n * Ps = 1 0 -> Show toolbar (rxvt).\n * Ps = 1 2 -> Start Blinking Cursor (att610).\n * Ps = 1 8 -> Print form feed (DECPFF).\n * Ps = 1 9 -> Set print extent to full screen (DECPEX).\n * Ps = 2 5 -> Show Cursor (DECTCEM).\n * Ps = 3 0 -> Show scrollbar (rxvt).\n * Ps = 3 5 -> Enable font-shifting functions (rxvt).\n * Ps = 3 8 -> Enter Tektronix Mode (DECTEK).\n * Ps = 4 0 -> Allow 80 -> 132 Mode.\n * Ps = 4 1 -> more(1) fix (see curses resource).\n * Ps = 4 2 -> Enable Nation Replacement Character sets (DECN-\n * RCM).\n * Ps = 4 4 -> Turn On Margin Bell.\n * Ps = 4 5 -> Reverse-wraparound Mode.\n * Ps = 4 6 -> Start Logging. This is normally disabled by a\n * compile-time option.\n * Ps = 4 7 -> Use Alternate Screen Buffer. (This may be dis-\n * abled by the titeInhibit resource).\n * Ps = 6 6 -> Application keypad (DECNKM).\n * Ps = 6 7 -> Backarrow key sends backspace (DECBKM).\n * Ps = 1 0 0 0 -> Send Mouse X & Y on button press and\n * release. See the section Mouse Tracking.\n * Ps = 1 0 0 1 -> Use Hilite Mouse Tracking.\n * Ps = 1 0 0 2 -> Use Cell Motion Mouse Tracking.\n * Ps = 1 0 0 3 -> Use All Motion Mouse Tracking.\n * Ps = 1 0 0 4 -> Send FocusIn/FocusOut events.\n * Ps = 1 0 0 5 -> Enable Extended Mouse Mode.\n * Ps = 1 0 1 0 -> Scroll to bottom on tty output (rxvt).\n * Ps = 1 0 1 1 -> Scroll to bottom on key press (rxvt).\n * Ps = 1 0 3 4 -> Interpret \"meta\" key, sets eighth bit.\n * (enables the eightBitInput resource).\n * Ps = 1 0 3 5 -> Enable special modifiers for Alt and Num-\n * Lock keys. (This enables the numLock resource).\n * Ps = 1 0 3 6 -> Send ESC when Meta modifies a key. (This\n * enables the metaSendsEscape resource).\n * Ps = 1 0 3 7 -> Send DEL from the editing-keypad Delete\n * key.\n * Ps = 1 0 3 9 -> Send ESC when Alt modifies a key. (This\n * enables the altSendsEscape resource).\n * Ps = 1 0 4 0 -> Keep selection even if not highlighted.\n * (This enables the keepSelection resource).\n * Ps = 1 0 4 1 -> Use the CLIPBOARD selection. (This enables\n * the selectToClipboard resource).\n * Ps = 1 0 4 2 -> Enable Urgency window manager hint when\n * Control-G is received. (This enables the bellIsUrgent\n * resource).\n * Ps = 1 0 4 3 -> Enable raising of the window when Control-G\n * is received. (enables the popOnBell resource).\n * Ps = 1 0 4 7 -> Use Alternate Screen Buffer. (This may be\n * disabled by the titeInhibit resource).\n * Ps = 1 0 4 8 -> Save cursor as in DECSC. (This may be dis-\n * abled by the titeInhibit resource).\n * Ps = 1 0 4 9 -> Save cursor as in DECSC and use Alternate\n * Screen Buffer, clearing it first. (This may be disabled by\n * the titeInhibit resource). This combines the effects of the 1\n * 0 4 7 and 1 0 4 8 modes. Use this with terminfo-based\n * applications rather than the 4 7 mode.\n * Ps = 1 0 5 0 -> Set terminfo/termcap function-key mode.\n * Ps = 1 0 5 1 -> Set Sun function-key mode.\n * Ps = 1 0 5 2 -> Set HP function-key mode.\n * Ps = 1 0 5 3 -> Set SCO function-key mode.\n * Ps = 1 0 6 0 -> Set legacy keyboard emulation (X11R6).\n * Ps = 1 0 6 1 -> Set VT220 keyboard emulation.\n * Ps = 2 0 0 4 -> Set bracketed paste mode.\n * Modes:\n * http: *vt100.net/docs/vt220-rm/chapter4.html\n */\n public setMode(params: number[]): void {\n if (params.length > 1) {\n for (let i = 0; i < params.length; i++) {\n this.setMode([params[i]]);\n }\n\n return;\n }\n\n if (!this._terminal.prefix) {\n switch (params[0]) {\n case 4:\n this._terminal.insertMode = true;\n break;\n case 20:\n // this._terminal.convertEol = true;\n break;\n }\n } else if (this._terminal.prefix === '?') {\n switch (params[0]) {\n case 1:\n this._terminal.applicationCursor = true;\n break;\n case 2:\n this._terminal.setgCharset(0, DEFAULT_CHARSET);\n this._terminal.setgCharset(1, DEFAULT_CHARSET);\n this._terminal.setgCharset(2, DEFAULT_CHARSET);\n this._terminal.setgCharset(3, DEFAULT_CHARSET);\n // set VT100 mode here\n break;\n case 3: // 132 col mode\n this._terminal.savedCols = this._terminal.cols;\n this._terminal.resize(132, this._terminal.rows);\n break;\n case 6:\n this._terminal.originMode = true;\n break;\n case 7:\n this._terminal.wraparoundMode = true;\n break;\n case 12:\n // this.cursorBlink = true;\n break;\n case 66:\n this._terminal.log('Serial port requested application keypad.');\n this._terminal.applicationKeypad = true;\n this._terminal.viewport.syncScrollArea();\n break;\n case 9: // X10 Mouse\n // no release, no motion, no wheel, no modifiers.\n case 1000: // vt200 mouse\n // no motion.\n // no modifiers, except control on the wheel.\n case 1002: // button event mouse\n case 1003: // any event mouse\n // any event - sends motion events,\n // even if there is no button held down.\n\n // TODO: Why are params[0] compares nested within a switch for params[0]?\n\n this._terminal.x10Mouse = params[0] === 9;\n this._terminal.vt200Mouse = params[0] === 1000;\n this._terminal.normalMouse = params[0] > 1000;\n this._terminal.mouseEvents = true;\n this._terminal.element.classList.add('enable-mouse-events');\n this._terminal.selectionManager.disable();\n this._terminal.log('Binding to mouse events.');\n break;\n case 1004: // send focusin/focusout events\n // focusin: ^[[I\n // focusout: ^[[O\n this._terminal.sendFocus = true;\n break;\n case 1005: // utf8 ext mode mouse\n this._terminal.utfMouse = true;\n // for wide terminals\n // simply encodes large values as utf8 characters\n break;\n case 1006: // sgr ext mode mouse\n this._terminal.sgrMouse = true;\n // for wide terminals\n // does not add 32 to fields\n // press: ^[[ Keyboard Action Mode (AM).\n * Ps = 4 -> Replace Mode (IRM).\n * Ps = 1 2 -> Send/receive (SRM).\n * Ps = 2 0 -> Normal Linefeed (LNM).\n * CSI ? Pm l\n * DEC Private Mode Reset (DECRST).\n * Ps = 1 -> Normal Cursor Keys (DECCKM).\n * Ps = 2 -> Designate VT52 mode (DECANM).\n * Ps = 3 -> 80 Column Mode (DECCOLM).\n * Ps = 4 -> Jump (Fast) Scroll (DECSCLM).\n * Ps = 5 -> Normal Video (DECSCNM).\n * Ps = 6 -> Normal Cursor Mode (DECOM).\n * Ps = 7 -> No Wraparound Mode (DECAWM).\n * Ps = 8 -> No Auto-repeat Keys (DECARM).\n * Ps = 9 -> Don't send Mouse X & Y on button press.\n * Ps = 1 0 -> Hide toolbar (rxvt).\n * Ps = 1 2 -> Stop Blinking Cursor (att610).\n * Ps = 1 8 -> Don't print form feed (DECPFF).\n * Ps = 1 9 -> Limit print to scrolling region (DECPEX).\n * Ps = 2 5 -> Hide Cursor (DECTCEM).\n * Ps = 3 0 -> Don't show scrollbar (rxvt).\n * Ps = 3 5 -> Disable font-shifting functions (rxvt).\n * Ps = 4 0 -> Disallow 80 -> 132 Mode.\n * Ps = 4 1 -> No more(1) fix (see curses resource).\n * Ps = 4 2 -> Disable Nation Replacement Character sets (DEC-\n * NRCM).\n * Ps = 4 4 -> Turn Off Margin Bell.\n * Ps = 4 5 -> No Reverse-wraparound Mode.\n * Ps = 4 6 -> Stop Logging. (This is normally disabled by a\n * compile-time option).\n * Ps = 4 7 -> Use Normal Screen Buffer.\n * Ps = 6 6 -> Numeric keypad (DECNKM).\n * Ps = 6 7 -> Backarrow key sends delete (DECBKM).\n * Ps = 1 0 0 0 -> Don't send Mouse X & Y on button press and\n * release. See the section Mouse Tracking.\n * Ps = 1 0 0 1 -> Don't use Hilite Mouse Tracking.\n * Ps = 1 0 0 2 -> Don't use Cell Motion Mouse Tracking.\n * Ps = 1 0 0 3 -> Don't use All Motion Mouse Tracking.\n * Ps = 1 0 0 4 -> Don't send FocusIn/FocusOut events.\n * Ps = 1 0 0 5 -> Disable Extended Mouse Mode.\n * Ps = 1 0 1 0 -> Don't scroll to bottom on tty output\n * (rxvt).\n * Ps = 1 0 1 1 -> Don't scroll to bottom on key press (rxvt).\n * Ps = 1 0 3 4 -> Don't interpret \"meta\" key. (This disables\n * the eightBitInput resource).\n * Ps = 1 0 3 5 -> Disable special modifiers for Alt and Num-\n * Lock keys. (This disables the numLock resource).\n * Ps = 1 0 3 6 -> Don't send ESC when Meta modifies a key.\n * (This disables the metaSendsEscape resource).\n * Ps = 1 0 3 7 -> Send VT220 Remove from the editing-keypad\n * Delete key.\n * Ps = 1 0 3 9 -> Don't send ESC when Alt modifies a key.\n * (This disables the altSendsEscape resource).\n * Ps = 1 0 4 0 -> Do not keep selection when not highlighted.\n * (This disables the keepSelection resource).\n * Ps = 1 0 4 1 -> Use the PRIMARY selection. (This disables\n * the selectToClipboard resource).\n * Ps = 1 0 4 2 -> Disable Urgency window manager hint when\n * Control-G is received. (This disables the bellIsUrgent\n * resource).\n * Ps = 1 0 4 3 -> Disable raising of the window when Control-\n * G is received. (This disables the popOnBell resource).\n * Ps = 1 0 4 7 -> Use Normal Screen Buffer, clearing screen\n * first if in the Alternate Screen. (This may be disabled by\n * the titeInhibit resource).\n * Ps = 1 0 4 8 -> Restore cursor as in DECRC. (This may be\n * disabled by the titeInhibit resource).\n * Ps = 1 0 4 9 -> Use Normal Screen Buffer and restore cursor\n * as in DECRC. (This may be disabled by the titeInhibit\n * resource). This combines the effects of the 1 0 4 7 and 1 0\n * 4 8 modes. Use this with terminfo-based applications rather\n * than the 4 7 mode.\n * Ps = 1 0 5 0 -> Reset terminfo/termcap function-key mode.\n * Ps = 1 0 5 1 -> Reset Sun function-key mode.\n * Ps = 1 0 5 2 -> Reset HP function-key mode.\n * Ps = 1 0 5 3 -> Reset SCO function-key mode.\n * Ps = 1 0 6 0 -> Reset legacy keyboard emulation (X11R6).\n * Ps = 1 0 6 1 -> Reset keyboard emulation to Sun/PC style.\n * Ps = 2 0 0 4 -> Reset bracketed paste mode.\n */\n public resetMode(params: number[]): void {\n if (params.length > 1) {\n for (let i = 0; i < params.length; i++) {\n this.resetMode([params[i]]);\n }\n\n return;\n }\n\n if (!this._terminal.prefix) {\n switch (params[0]) {\n case 4:\n this._terminal.insertMode = false;\n break;\n case 20:\n // this._terminal.convertEol = false;\n break;\n }\n } else if (this._terminal.prefix === '?') {\n switch (params[0]) {\n case 1:\n this._terminal.applicationCursor = false;\n break;\n case 3:\n if (this._terminal.cols === 132 && this._terminal.savedCols) {\n this._terminal.resize(this._terminal.savedCols, this._terminal.rows);\n }\n delete this._terminal.savedCols;\n break;\n case 6:\n this._terminal.originMode = false;\n break;\n case 7:\n this._terminal.wraparoundMode = false;\n break;\n case 12:\n // this.cursorBlink = false;\n break;\n case 66:\n this._terminal.log('Switching back to normal keypad.');\n this._terminal.applicationKeypad = false;\n this._terminal.viewport.syncScrollArea();\n break;\n case 9: // X10 Mouse\n case 1000: // vt200 mouse\n case 1002: // button event mouse\n case 1003: // any event mouse\n this._terminal.x10Mouse = false;\n this._terminal.vt200Mouse = false;\n this._terminal.normalMouse = false;\n this._terminal.mouseEvents = false;\n this._terminal.element.classList.remove('enable-mouse-events');\n this._terminal.selectionManager.enable();\n break;\n case 1004: // send focusin/focusout events\n this._terminal.sendFocus = false;\n break;\n case 1005: // utf8 ext mode mouse\n this._terminal.utfMouse = false;\n break;\n case 1006: // sgr ext mode mouse\n this._terminal.sgrMouse = false;\n break;\n case 1015: // urxvt ext mode mouse\n this._terminal.urxvtMouse = false;\n break;\n case 25: // hide cursor\n this._terminal.cursorHidden = true;\n break;\n case 1049: // alt screen buffer cursor\n // FALL-THROUGH\n case 47: // normal screen buffer\n case 1047: // normal screen buffer - clearing it first\n // Ensure the selection manager has the correct buffer\n this._terminal.buffers.activateNormalBuffer();\n // TODO: Not sure if we need to save/restore after switching the buffer\n // if (params[0] === 1049) {\n // this.restoreCursor(params);\n // }\n this._terminal.selectionManager.setBuffer(this._terminal.buffer.lines);\n this._terminal.refresh(0, this._terminal.rows - 1);\n this._terminal.viewport.syncScrollArea();\n this._terminal.showCursor();\n break;\n }\n }\n }\n\n /**\n * CSI Pm m Character Attributes (SGR).\n * Ps = 0 -> Normal (default).\n * Ps = 1 -> Bold.\n * Ps = 4 -> Underlined.\n * Ps = 5 -> Blink (appears as Bold).\n * Ps = 7 -> Inverse.\n * Ps = 8 -> Invisible, i.e., hidden (VT300).\n * Ps = 2 2 -> Normal (neither bold nor faint).\n * Ps = 2 4 -> Not underlined.\n * Ps = 2 5 -> Steady (not blinking).\n * Ps = 2 7 -> Positive (not inverse).\n * Ps = 2 8 -> Visible, i.e., not hidden (VT300).\n * Ps = 3 0 -> Set foreground color to Black.\n * Ps = 3 1 -> Set foreground color to Red.\n * Ps = 3 2 -> Set foreground color to Green.\n * Ps = 3 3 -> Set foreground color to Yellow.\n * Ps = 3 4 -> Set foreground color to Blue.\n * Ps = 3 5 -> Set foreground color to Magenta.\n * Ps = 3 6 -> Set foreground color to Cyan.\n * Ps = 3 7 -> Set foreground color to White.\n * Ps = 3 9 -> Set foreground color to default (original).\n * Ps = 4 0 -> Set background color to Black.\n * Ps = 4 1 -> Set background color to Red.\n * Ps = 4 2 -> Set background color to Green.\n * Ps = 4 3 -> Set background color to Yellow.\n * Ps = 4 4 -> Set background color to Blue.\n * Ps = 4 5 -> Set background color to Magenta.\n * Ps = 4 6 -> Set background color to Cyan.\n * Ps = 4 7 -> Set background color to White.\n * Ps = 4 9 -> Set background color to default (original).\n *\n * If 16-color support is compiled, the following apply. Assume\n * that xterm's resources are set so that the ISO color codes are\n * the first 8 of a set of 16. Then the aixterm colors are the\n * bright versions of the ISO colors:\n * Ps = 9 0 -> Set foreground color to Black.\n * Ps = 9 1 -> Set foreground color to Red.\n * Ps = 9 2 -> Set foreground color to Green.\n * Ps = 9 3 -> Set foreground color to Yellow.\n * Ps = 9 4 -> Set foreground color to Blue.\n * Ps = 9 5 -> Set foreground color to Magenta.\n * Ps = 9 6 -> Set foreground color to Cyan.\n * Ps = 9 7 -> Set foreground color to White.\n * Ps = 1 0 0 -> Set background color to Black.\n * Ps = 1 0 1 -> Set background color to Red.\n * Ps = 1 0 2 -> Set background color to Green.\n * Ps = 1 0 3 -> Set background color to Yellow.\n * Ps = 1 0 4 -> Set background color to Blue.\n * Ps = 1 0 5 -> Set background color to Magenta.\n * Ps = 1 0 6 -> Set background color to Cyan.\n * Ps = 1 0 7 -> Set background color to White.\n *\n * If xterm is compiled with the 16-color support disabled, it\n * supports the following, from rxvt:\n * Ps = 1 0 0 -> Set foreground and background color to\n * default.\n *\n * If 88- or 256-color support is compiled, the following apply.\n * Ps = 3 8 ; 5 ; Ps -> Set foreground color to the second\n * Ps.\n * Ps = 4 8 ; 5 ; Ps -> Set background color to the second\n * Ps.\n */\n public charAttributes(params: number[]): void {\n // Optimize a single SGR0.\n if (params.length === 1 && params[0] === 0) {\n this._terminal.curAttr = this._terminal.defAttr;\n return;\n }\n\n let l = params.length\n , i = 0\n , flags = this._terminal.curAttr >> 18\n , fg = (this._terminal.curAttr >> 9) & 0x1ff\n , bg = this._terminal.curAttr & 0x1ff\n , p;\n\n for (; i < l; i++) {\n p = params[i];\n if (p >= 30 && p <= 37) {\n // fg color 8\n fg = p - 30;\n } else if (p >= 40 && p <= 47) {\n // bg color 8\n bg = p - 40;\n } else if (p >= 90 && p <= 97) {\n // fg color 16\n p += 8;\n fg = p - 90;\n } else if (p >= 100 && p <= 107) {\n // bg color 16\n p += 8;\n bg = p - 100;\n } else if (p === 0) {\n // default\n flags = this._terminal.defAttr >> 18;\n fg = (this._terminal.defAttr >> 9) & 0x1ff;\n bg = this._terminal.defAttr & 0x1ff;\n // flags = 0;\n // fg = 0x1ff;\n // bg = 0x1ff;\n } else if (p === 1) {\n // bold text\n flags |= 1;\n } else if (p === 4) {\n // underlined text\n flags |= 2;\n } else if (p === 5) {\n // blink\n flags |= 4;\n } else if (p === 7) {\n // inverse and positive\n // test with: echo -e '\\e[31m\\e[42mhello\\e[7mworld\\e[27mhi\\e[m'\n flags |= 8;\n } else if (p === 8) {\n // invisible\n flags |= 16;\n } else if (p === 22) {\n // not bold\n flags &= ~1;\n } else if (p === 24) {\n // not underlined\n flags &= ~2;\n } else if (p === 25) {\n // not blink\n flags &= ~4;\n } else if (p === 27) {\n // not inverse\n flags &= ~8;\n } else if (p === 28) {\n // not invisible\n flags &= ~16;\n } else if (p === 39) {\n // reset fg\n fg = (this._terminal.defAttr >> 9) & 0x1ff;\n } else if (p === 49) {\n // reset bg\n bg = this._terminal.defAttr & 0x1ff;\n } else if (p === 38) {\n // fg color 256\n if (params[i + 1] === 2) {\n i += 2;\n fg = this._terminal.matchColor(\n params[i] & 0xff,\n params[i + 1] & 0xff,\n params[i + 2] & 0xff);\n if (fg === -1) fg = 0x1ff;\n i += 2;\n } else if (params[i + 1] === 5) {\n i += 2;\n p = params[i] & 0xff;\n fg = p;\n }\n } else if (p === 48) {\n // bg color 256\n if (params[i + 1] === 2) {\n i += 2;\n bg = this._terminal.matchColor(\n params[i] & 0xff,\n params[i + 1] & 0xff,\n params[i + 2] & 0xff);\n if (bg === -1) bg = 0x1ff;\n i += 2;\n } else if (params[i + 1] === 5) {\n i += 2;\n p = params[i] & 0xff;\n bg = p;\n }\n } else if (p === 100) {\n // reset fg/bg\n fg = (this._terminal.defAttr >> 9) & 0x1ff;\n bg = this._terminal.defAttr & 0x1ff;\n } else {\n this._terminal.error('Unknown SGR attribute: %d.', p);\n }\n }\n\n this._terminal.curAttr = (flags << 18) | (fg << 9) | bg;\n }\n\n /**\n * CSI Ps n Device Status Report (DSR).\n * Ps = 5 -> Status Report. Result (``OK'') is\n * CSI 0 n\n * Ps = 6 -> Report Cursor Position (CPR) [row;column].\n * Result is\n * CSI r ; c R\n * CSI ? Ps n\n * Device Status Report (DSR, DEC-specific).\n * Ps = 6 -> Report Cursor Position (CPR) [row;column] as CSI\n * ? r ; c R (assumes page is zero).\n * Ps = 1 5 -> Report Printer status as CSI ? 1 0 n (ready).\n * or CSI ? 1 1 n (not ready).\n * Ps = 2 5 -> Report UDK status as CSI ? 2 0 n (unlocked)\n * or CSI ? 2 1 n (locked).\n * Ps = 2 6 -> Report Keyboard status as\n * CSI ? 2 7 ; 1 ; 0 ; 0 n (North American).\n * The last two parameters apply to VT400 & up, and denote key-\n * board ready and LK01 respectively.\n * Ps = 5 3 -> Report Locator status as\n * CSI ? 5 3 n Locator available, if compiled-in, or\n * CSI ? 5 0 n No Locator, if not.\n */\n public deviceStatus(params: number[]): void {\n if (!this._terminal.prefix) {\n switch (params[0]) {\n case 5:\n // status report\n this._terminal.send(C0.ESC + '[0n');\n break;\n case 6:\n // cursor position\n this._terminal.send(C0.ESC + '['\n + (this._terminal.buffer.y + 1)\n + ';'\n + (this._terminal.buffer.x + 1)\n + 'R');\n break;\n }\n } else if (this._terminal.prefix === '?') {\n // modern xterm doesnt seem to\n // respond to any of these except ?6, 6, and 5\n switch (params[0]) {\n case 6:\n // cursor position\n this._terminal.send(C0.ESC + '[?'\n + (this._terminal.buffer.y + 1)\n + ';'\n + (this._terminal.buffer.x + 1)\n + 'R');\n break;\n case 15:\n // no printer\n // this.send(C0.ESC + '[?11n');\n break;\n case 25:\n // dont support user defined keys\n // this.send(C0.ESC + '[?21n');\n break;\n case 26:\n // north american keyboard\n // this.send(C0.ESC + '[?27;1;0;0n');\n break;\n case 53:\n // no dec locator/mouse\n // this.send(C0.ESC + '[?50n');\n break;\n }\n }\n }\n\n /**\n * CSI ! p Soft terminal reset (DECSTR).\n * http://vt100.net/docs/vt220-rm/table4-10.html\n */\n public softReset(params: number[]): void {\n this._terminal.cursorHidden = false;\n this._terminal.insertMode = false;\n this._terminal.originMode = false;\n this._terminal.wraparoundMode = true; // defaults: xterm - true, vt100 - false\n this._terminal.applicationKeypad = false; // ?\n this._terminal.viewport.syncScrollArea();\n this._terminal.applicationCursor = false;\n this._terminal.buffer.scrollTop = 0;\n this._terminal.buffer.scrollBottom = this._terminal.rows - 1;\n this._terminal.curAttr = this._terminal.defAttr;\n this._terminal.buffer.x = this._terminal.buffer.y = 0; // ?\n this._terminal.charset = null;\n this._terminal.glevel = 0; // ??\n this._terminal.charsets = [null]; // ??\n }\n\n /**\n * CSI Ps SP q Set cursor style (DECSCUSR, VT520).\n * Ps = 0 -> blinking block.\n * Ps = 1 -> blinking block (default).\n * Ps = 2 -> steady block.\n * Ps = 3 -> blinking underline.\n * Ps = 4 -> steady underline.\n * Ps = 5 -> blinking bar (xterm).\n * Ps = 6 -> steady bar (xterm).\n */\n public setCursorStyle(params?: number[]): void {\n const param = params[0] < 1 ? 1 : params[0];\n switch (param) {\n case 1:\n case 2:\n this._terminal.setOption('cursorStyle', 'block');\n break;\n case 3:\n case 4:\n this._terminal.setOption('cursorStyle', 'underline');\n break;\n case 5:\n case 6:\n this._terminal.setOption('cursorStyle', 'bar');\n break;\n }\n const isBlinking = param % 2 === 1;\n this._terminal.setOption('cursorBlink', isBlinking);\n }\n\n /**\n * CSI Ps ; Ps r\n * Set Scrolling Region [top;bottom] (default = full size of win-\n * dow) (DECSTBM).\n * CSI ? Pm r\n */\n public setScrollRegion(params: number[]): void {\n if (this._terminal.prefix) return;\n this._terminal.buffer.scrollTop = (params[0] || 1) - 1;\n this._terminal.buffer.scrollBottom = (params[1] && params[1] <= this._terminal.rows ? params[1] : this._terminal.rows) - 1;\n this._terminal.buffer.x = 0;\n this._terminal.buffer.y = 0;\n }\n\n\n /**\n * CSI s\n * Save cursor (ANSI.SYS).\n */\n public saveCursor(params: number[]): void {\n this._terminal.buffer.savedX = this._terminal.buffer.x;\n this._terminal.buffer.savedY = this._terminal.buffer.y;\n }\n\n\n /**\n * CSI u\n * Restore cursor (ANSI.SYS).\n */\n public restoreCursor(params: number[]): void {\n this._terminal.buffer.x = this._terminal.buffer.savedX || 0;\n this._terminal.buffer.y = this._terminal.buffer.savedY || 0;\n }\n}\n\nexport const wcwidth = (function(opts) {\n // extracted from https://www.cl.cam.ac.uk/%7Emgk25/ucs/wcwidth.c\n // combining characters\n const COMBINING_BMP = [\n [0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489],\n [0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2],\n [0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603],\n [0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670],\n [0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED],\n [0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A],\n [0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902],\n [0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D],\n [0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981],\n [0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD],\n [0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C],\n [0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D],\n [0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC],\n [0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD],\n [0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C],\n [0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D],\n [0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0],\n [0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48],\n [0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC],\n [0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD],\n [0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D],\n [0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6],\n [0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E],\n [0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC],\n [0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35],\n [0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E],\n [0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97],\n [0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030],\n [0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039],\n [0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F],\n [0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753],\n [0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD],\n [0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD],\n [0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922],\n [0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B],\n [0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34],\n [0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42],\n [0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF],\n [0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063],\n [0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F],\n [0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B],\n [0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F],\n [0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB],\n ];\n const COMBINING_HIGH = [\n [0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F],\n [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169],\n [0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD],\n [0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F],\n [0xE0100, 0xE01EF]\n ];\n // binary search\n function bisearch(ucs, data) {\n let min = 0;\n let max = data.length - 1;\n let mid;\n if (ucs < data[0][0] || ucs > data[max][1])\n return false;\n while (max >= min) {\n mid = (min + max) >> 1;\n if (ucs > data[mid][1])\n min = mid + 1;\n else if (ucs < data[mid][0])\n max = mid - 1;\n else\n return true;\n }\n return false;\n }\n function wcwidthBMP(ucs) {\n // test for 8-bit control characters\n if (ucs === 0)\n return opts.nul;\n if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0))\n return opts.control;\n // binary search in table of non-spacing characters\n if (bisearch(ucs, COMBINING_BMP))\n return 0;\n // if we arrive here, ucs is not a combining or C0/C1 control character\n if (isWideBMP(ucs)) {\n return 2;\n }\n return 1;\n }\n function isWideBMP(ucs) {\n return (\n ucs >= 0x1100 && (\n ucs <= 0x115f || // Hangul Jamo init. consonants\n ucs === 0x2329 ||\n ucs === 0x232a ||\n (ucs >= 0x2e80 && ucs <= 0xa4cf && ucs !== 0x303f) || // CJK..Yi\n (ucs >= 0xac00 && ucs <= 0xd7a3) || // Hangul Syllables\n (ucs >= 0xf900 && ucs <= 0xfaff) || // CJK Compat Ideographs\n (ucs >= 0xfe10 && ucs <= 0xfe19) || // Vertical forms\n (ucs >= 0xfe30 && ucs <= 0xfe6f) || // CJK Compat Forms\n (ucs >= 0xff00 && ucs <= 0xff60) || // Fullwidth Forms\n (ucs >= 0xffe0 && ucs <= 0xffe6)));\n }\n function wcwidthHigh(ucs) {\n if (bisearch(ucs, COMBINING_HIGH))\n return 0;\n if ((ucs >= 0x20000 && ucs <= 0x2fffd) || (ucs >= 0x30000 && ucs <= 0x3fffd)) {\n return 2;\n }\n return 1;\n }\n const control = opts.control | 0;\n let table = null;\n function init_table() {\n // lookup table for BMP\n const CODEPOINTS = 65536; // BMP holds 65536 codepoints\n const BITWIDTH = 2; // a codepoint can have a width of 0, 1 or 2\n const ITEMSIZE = 32; // using uint32_t\n const CONTAINERSIZE = CODEPOINTS * BITWIDTH / ITEMSIZE;\n const CODEPOINTS_PER_ITEM = ITEMSIZE / BITWIDTH;\n table = (typeof Uint32Array === 'undefined')\n ? new Array(CONTAINERSIZE)\n : new Uint32Array(CONTAINERSIZE);\n for (let i = 0; i < CONTAINERSIZE; ++i) {\n let num = 0;\n let pos = CODEPOINTS_PER_ITEM;\n while (pos--)\n num = (num << 2) | wcwidthBMP(CODEPOINTS_PER_ITEM * i + pos);\n table[i] = num;\n }\n return table;\n }\n // get width from lookup table\n // position in container : num / CODEPOINTS_PER_ITEM\n // ==> n = table[Math.floor(num / 16)]\n // ==> n = table[num >> 4]\n // 16 codepoints per number: FFEEDDCCBBAA99887766554433221100\n // position in number : (num % CODEPOINTS_PER_ITEM) * BITWIDTH\n // ==> m = (n % 16) * 2\n // ==> m = (num & 15) << 1\n // right shift to position m\n // ==> n = n >> m e.g. m=12 000000000000FFEEDDCCBBAA99887766\n // we are only interested in 2 LSBs, cut off higher bits\n // ==> n = n & 3 e.g. 000000000000000000000000000000XX\n return function (num) {\n num = num | 0; // get asm.js like optimization under V8\n if (num < 32)\n return control | 0;\n if (num < 127)\n return 1;\n let t = table || init_table();\n if (num < 65536)\n return t[num >> 4] >> ((num & 15) << 1) & 3;\n // do a full search for high codepoints\n return wcwidthHigh(num);\n };\n})({nul: 0, control: 0}); // configurable options\n","/**\n * @license MIT\n */\n\nimport { IEventEmitter } from './Interfaces';\n\ninterface ListenerType {\n (): void;\n listener?: () => void;\n};\n\nexport class EventEmitter implements IEventEmitter {\n private _events: {[type: string]: ListenerType[]};\n\n constructor() {\n // Restore the previous events if available, this will happen if the\n // constructor is called multiple times on the same object (terminal reset).\n this._events = this._events || {};\n }\n\n public on(type, listener): void {\n this._events[type] = this._events[type] || [];\n this._events[type].push(listener);\n }\n\n public off(type, listener): void {\n if (!this._events[type]) {\n return;\n }\n\n let obj = this._events[type];\n let i = obj.length;\n\n while (i--) {\n if (obj[i] === listener || obj[i].listener === listener) {\n obj.splice(i, 1);\n return;\n }\n }\n }\n\n public removeAllListeners(type): void {\n if (this._events[type]) {\n delete this._events[type];\n }\n }\n\n public once(type, listener): any {\n function on() {\n let args = Array.prototype.slice.call(arguments);\n this.off(type, on);\n return listener.apply(this, args);\n }\n (on).listener = listener;\n return this.on(type, on);\n }\n\n public emit(type: string, ...args: any[]): void {\n if (!this._events[type]) {\n return;\n }\n let obj = this._events[type];\n for (let i = 0; i < obj.length; i++) {\n obj[i].apply(this, args);\n }\n }\n\n public listeners(type): ListenerType[] {\n return this._events[type] || [];\n }\n}\n","/**\n * @license MIT\n */\n\n/**\n * C0 control codes\n * See = https://en.wikipedia.org/wiki/C0_and_C1_control_codes\n */\nexport namespace C0 {\n /** Null (Caret = ^@, C = \\0) */\n export const NUL = '\\x00';\n /** Start of Heading (Caret = ^A) */\n export const SOH = '\\x01';\n /** Start of Text (Caret = ^B) */\n export const STX = '\\x02';\n /** End of Text (Caret = ^C) */\n export const ETX = '\\x03';\n /** End of Transmission (Caret = ^D) */\n export const EOT = '\\x04';\n /** Enquiry (Caret = ^E) */\n export const ENQ = '\\x05';\n /** Acknowledge (Caret = ^F) */\n export const ACK = '\\x06';\n /** Bell (Caret = ^G, C = \\a) */\n export const BEL = '\\x07';\n /** Backspace (Caret = ^H, C = \\b) */\n export const BS = '\\x08';\n /** Character Tabulation, Horizontal Tabulation (Caret = ^I, C = \\t) */\n export const HT = '\\x09';\n /** Line Feed (Caret = ^J, C = \\n) */\n export const LF = '\\x0a';\n /** Line Tabulation, Vertical Tabulation (Caret = ^K, C = \\v) */\n export const VT = '\\x0b';\n /** Form Feed (Caret = ^L, C = \\f) */\n export const FF = '\\x0c';\n /** Carriage Return (Caret = ^M, C = \\r) */\n export const CR = '\\x0d';\n /** Shift Out (Caret = ^N) */\n export const SO = '\\x0e';\n /** Shift In (Caret = ^O) */\n export const SI = '\\x0f';\n /** Data Link Escape (Caret = ^P) */\n export const DLE = '\\x10';\n /** Device Control One (XON) (Caret = ^Q) */\n export const DC1 = '\\x11';\n /** Device Control Two (Caret = ^R) */\n export const DC2 = '\\x12';\n /** Device Control Three (XOFF) (Caret = ^S) */\n export const DC3 = '\\x13';\n /** Device Control Four (Caret = ^T) */\n export const DC4 = '\\x14';\n /** Negative Acknowledge (Caret = ^U) */\n export const NAK = '\\x15';\n /** Synchronous Idle (Caret = ^V) */\n export const SYN = '\\x16';\n /** End of Transmission Block (Caret = ^W) */\n export const ETB = '\\x17';\n /** Cancel (Caret = ^X) */\n export const CAN = '\\x18';\n /** End of Medium (Caret = ^Y) */\n export const EM = '\\x19';\n /** Substitute (Caret = ^Z) */\n export const SUB = '\\x1a';\n /** Escape (Caret = ^[, C = \\e) */\n export const ESC = '\\x1b';\n /** File Separator (Caret = ^\\) */\n export const FS = '\\x1c';\n /** Group Separator (Caret = ^]) */\n export const GS = '\\x1d';\n /** Record Separator (Caret = ^^) */\n export const RS = '\\x1e';\n /** Unit Separator (Caret = ^_) */\n export const US = '\\x1f';\n /** Space */\n export const SP = '\\x20';\n /** Delete (Caret = ^?) */\n export const DEL = '\\x7f';\n};\n","/**\n * @license MIT\n */\n\nimport { ITerminal } from './Interfaces';\n\ninterface IPosition {\n start: number;\n end: number;\n}\n\n/**\n * Encapsulates the logic for handling compositionstart, compositionupdate and compositionend\n * events, displaying the in-progress composition to the UI and forwarding the final composition\n * to the handler.\n */\nexport class CompositionHelper {\n /**\n * Whether input composition is currently happening, eg. via a mobile keyboard, speech input or\n * IME. This variable determines whether the compositionText should be displayed on the UI.\n */\n private isComposing: boolean;\n\n /**\n * The position within the input textarea's value of the current composition.\n */\n private compositionPosition: IPosition;\n\n /**\n * Whether a composition is in the process of being sent, setting this to false will cancel any\n * in-progress composition.\n */\n private isSendingComposition: boolean;\n\n /**\n * Creates a new CompositionHelper.\n * @param textarea The textarea that xterm uses for input.\n * @param compositionView The element to display the in-progress composition in.\n * @param terminal The Terminal to forward the finished composition to.\n */\n constructor(\n private textarea: HTMLTextAreaElement,\n private compositionView: HTMLElement,\n private terminal: ITerminal\n ) {\n this.isComposing = false;\n this.isSendingComposition = false;\n this.compositionPosition = { start: null, end: null };\n }\n\n /**\n * Handles the compositionstart event, activating the composition view.\n */\n public compositionstart() {\n this.isComposing = true;\n this.compositionPosition.start = this.textarea.value.length;\n this.compositionView.textContent = '';\n this.compositionView.classList.add('active');\n }\n\n /**\n * Handles the compositionupdate event, updating the composition view.\n * @param {CompositionEvent} ev The event.\n */\n public compositionupdate(ev: CompositionEvent) {\n this.compositionView.textContent = ev.data;\n this.updateCompositionElements();\n setTimeout(() => {\n this.compositionPosition.end = this.textarea.value.length;\n }, 0);\n }\n\n /**\n * Handles the compositionend event, hiding the composition view and sending the composition to\n * the handler.\n */\n public compositionend() {\n this.finalizeComposition(true);\n }\n\n /**\n * Handles the keydown event, routing any necessary events to the CompositionHelper functions.\n * @param ev The keydown event.\n * @return Whether the Terminal should continue processing the keydown event.\n */\n public keydown(ev: KeyboardEvent) {\n if (this.isComposing || this.isSendingComposition) {\n if (ev.keyCode === 229) {\n // Continue composing if the keyCode is the \"composition character\"\n return false;\n } else if (ev.keyCode === 16 || ev.keyCode === 17 || ev.keyCode === 18) {\n // Continue composing if the keyCode is a modifier key\n return false;\n } else {\n // Finish composition immediately. This is mainly here for the case where enter is\n // pressed and the handler needs to be triggered before the command is executed.\n this.finalizeComposition(false);\n }\n }\n\n if (ev.keyCode === 229) {\n // If the \"composition character\" is used but gets to this point it means a non-composition\n // character (eg. numbers and punctuation) was pressed when the IME was active.\n this.handleAnyTextareaChanges();\n return false;\n }\n\n return true;\n }\n\n /**\n * Finalizes the composition, resuming regular input actions. This is called when a composition\n * is ending.\n * @param waitForPropogation Whether to wait for events to propogate before sending\n * the input. This should be false if a non-composition keystroke is entered before the\n * compositionend event is triggered, such as enter, so that the composition is send before\n * the command is executed.\n */\n private finalizeComposition(waitForPropogation: boolean) {\n this.compositionView.classList.remove('active');\n this.isComposing = false;\n this.clearTextareaPosition();\n\n if (!waitForPropogation) {\n // Cancel any delayed composition send requests and send the input immediately.\n this.isSendingComposition = false;\n const input = this.textarea.value.substring(this.compositionPosition.start, this.compositionPosition.end);\n this.terminal.handler(input);\n } else {\n // Make a deep copy of the composition position here as a new compositionstart event may\n // fire before the setTimeout executes.\n const currentCompositionPosition = {\n start: this.compositionPosition.start,\n end: this.compositionPosition.end,\n };\n\n // Since composition* events happen before the changes take place in the textarea on most\n // browsers, use a setTimeout with 0ms time to allow the native compositionend event to\n // complete. This ensures the correct character is retrieved, this solution was used\n // because:\n // - The compositionend event's data property is unreliable, at least on Chromium\n // - The last compositionupdate event's data property does not always accurately describe\n // the character, a counter example being Korean where an ending consonsant can move to\n // the following character if the following input is a vowel.\n this.isSendingComposition = true;\n setTimeout(() => {\n // Ensure that the input has not already been sent\n if (this.isSendingComposition) {\n this.isSendingComposition = false;\n let input;\n if (this.isComposing) {\n // Use the end position to get the string if a new composition has started.\n input = this.textarea.value.substring(currentCompositionPosition.start, currentCompositionPosition.end);\n } else {\n // Don't use the end position here in order to pick up any characters after the\n // composition has finished, for example when typing a non-composition character\n // (eg. 2) after a composition character.\n input = this.textarea.value.substring(currentCompositionPosition.start);\n }\n this.terminal.handler(input);\n }\n }, 0);\n }\n }\n\n /**\n * Apply any changes made to the textarea after the current event chain is allowed to complete.\n * This should be called when not currently composing but a keydown event with the \"composition\n * character\" (229) is triggered, in order to allow non-composition text to be entered when an\n * IME is active.\n */\n private handleAnyTextareaChanges() {\n const oldValue = this.textarea.value;\n setTimeout(() => {\n // Ignore if a composition has started since the timeout\n if (!this.isComposing) {\n const newValue = this.textarea.value;\n const diff = newValue.replace(oldValue, '');\n if (diff.length > 0) {\n this.terminal.handler(diff);\n }\n }\n }, 0);\n }\n\n /**\n * Positions the composition view on top of the cursor and the textarea just below it (so the\n * IME helper dialog is positioned correctly).\n * @param dontRecurse Whether to use setTimeout to recursively trigger another update, this is\n * necessary as the IME events across browsers are not consistently triggered.\n */\n public updateCompositionElements(dontRecurse?: boolean) {\n if (!this.isComposing) {\n return;\n }\n const cursor = this.terminal.element.querySelector('.terminal-cursor');\n if (cursor) {\n // Take .xterm-rows offsetTop into account as well in case it's positioned absolutely within\n // the .xterm element.\n const xtermRows = this.terminal.element.querySelector('.xterm-rows');\n const cursorTop = xtermRows.offsetTop + cursor.offsetTop;\n\n this.compositionView.style.left = cursor.offsetLeft + 'px';\n this.compositionView.style.top = cursorTop + 'px';\n this.compositionView.style.height = cursor.offsetHeight + 'px';\n this.compositionView.style.lineHeight = cursor.offsetHeight + 'px';\n // Sync the textarea to the exact position of the composition view so the IME knows where the\n // text is.\n const compositionViewBounds = this.compositionView.getBoundingClientRect();\n this.textarea.style.left = cursor.offsetLeft + 'px';\n this.textarea.style.top = cursorTop + 'px';\n this.textarea.style.width = compositionViewBounds.width + 'px';\n this.textarea.style.height = compositionViewBounds.height + 'px';\n this.textarea.style.lineHeight = compositionViewBounds.height + 'px';\n }\n if (!dontRecurse) {\n setTimeout(() => this.updateCompositionElements(true), 0);\n }\n };\n\n /**\n * Clears the textarea's position so that the cursor does not blink on IE.\n * @private\n */\n private clearTextareaPosition() {\n this.textarea.style.left = '';\n this.textarea.style.top = '';\n };\n}\n","/**\n * @license MIT\n */\n\n/**\n * The character sets supported by the terminal. These enable several languages\n * to be represented within the terminal with only 8-bit encoding. See ISO 2022\n * for a discussion on character sets. Only VT100 character sets are supported.\n */\nexport const CHARSETS: {[key: string]: {[key: string]: string}} = {};\n\n/**\n * The default character set, US.\n */\nexport const DEFAULT_CHARSET = CHARSETS['B'];\n\n/**\n * DEC Special Character and Line Drawing Set.\n * Reference: http://vt100.net/docs/vt102-ug/table5-13.html\n * A lot of curses apps use this if they see TERM=xterm.\n * testing: echo -e '\\e(0a\\e(B'\n * The xterm output sometimes seems to conflict with the\n * reference above. xterm seems in line with the reference\n * when running vttest however.\n * The table below now uses xterm's output from vttest.\n */\nCHARSETS['0'] = {\n '`': '\\u25c6', // '◆'\n 'a': '\\u2592', // '▒'\n 'b': '\\u0009', // '\\t'\n 'c': '\\u000c', // '\\f'\n 'd': '\\u000d', // '\\r'\n 'e': '\\u000a', // '\\n'\n 'f': '\\u00b0', // '°'\n 'g': '\\u00b1', // '±'\n 'h': '\\u2424', // '\\u2424' (NL)\n 'i': '\\u000b', // '\\v'\n 'j': '\\u2518', // '┘'\n 'k': '\\u2510', // '┐'\n 'l': '\\u250c', // '┌'\n 'm': '\\u2514', // '└'\n 'n': '\\u253c', // '┼'\n 'o': '\\u23ba', // '⎺'\n 'p': '\\u23bb', // '⎻'\n 'q': '\\u2500', // '─'\n 'r': '\\u23bc', // '⎼'\n 's': '\\u23bd', // '⎽'\n 't': '\\u251c', // '├'\n 'u': '\\u2524', // '┤'\n 'v': '\\u2534', // '┴'\n 'w': '\\u252c', // '┬'\n 'x': '\\u2502', // '│'\n 'y': '\\u2264', // '≤'\n 'z': '\\u2265', // '≥'\n '{': '\\u03c0', // 'π'\n '|': '\\u2260', // '≠'\n '}': '\\u00a3', // '£'\n '~': '\\u00b7' // '·'\n};\n\n/**\n * British character set\n * ESC (A\n * Reference: http://vt100.net/docs/vt220-rm/table2-5.html\n */\nCHARSETS['A'] = {\n '#': '£'\n};\n\n/**\n * United States character set\n * ESC (B\n */\nCHARSETS['B'] = null;\n\n/**\n * Dutch character set\n * ESC (4\n * Reference: http://vt100.net/docs/vt220-rm/table2-6.html\n */\nCHARSETS['4'] = {\n '#': '£',\n '@': '¾',\n '[': 'ij',\n '\\\\': '½',\n ']': '|',\n '{': '¨',\n '|': 'f',\n '}': '¼',\n '~': '´'\n};\n\n/**\n * Finnish character set\n * ESC (C or ESC (5\n * Reference: http://vt100.net/docs/vt220-rm/table2-7.html\n */\nCHARSETS['C'] =\nCHARSETS['5'] = {\n '[': 'Ä',\n '\\\\': 'Ö',\n ']': 'Å',\n '^': 'Ü',\n '`': 'é',\n '{': 'ä',\n '|': 'ö',\n '}': 'å',\n '~': 'ü'\n};\n\n/**\n * French character set\n * ESC (R\n * Reference: http://vt100.net/docs/vt220-rm/table2-8.html\n */\nCHARSETS['R'] = {\n '#': '£',\n '@': 'à',\n '[': '°',\n '\\\\': 'ç',\n ']': '§',\n '{': 'é',\n '|': 'ù',\n '}': 'è',\n '~': '¨'\n};\n\n/**\n * French Canadian character set\n * ESC (Q\n * Reference: http://vt100.net/docs/vt220-rm/table2-9.html\n */\nCHARSETS['Q'] = {\n '@': 'à',\n '[': 'â',\n '\\\\': 'ç',\n ']': 'ê',\n '^': 'î',\n '`': 'ô',\n '{': 'é',\n '|': 'ù',\n '}': 'è',\n '~': 'û'\n};\n\n/**\n * German character set\n * ESC (K\n * Reference: http://vt100.net/docs/vt220-rm/table2-10.html\n */\nCHARSETS['K'] = {\n '@': '§',\n '[': 'Ä',\n '\\\\': 'Ö',\n ']': 'Ü',\n '{': 'ä',\n '|': 'ö',\n '}': 'ü',\n '~': 'ß'\n};\n\n/**\n * Italian character set\n * ESC (Y\n * Reference: http://vt100.net/docs/vt220-rm/table2-11.html\n */\nCHARSETS['Y'] = {\n '#': '£',\n '@': '§',\n '[': '°',\n '\\\\': 'ç',\n ']': 'é',\n '`': 'ù',\n '{': 'à',\n '|': 'ò',\n '}': 'è',\n '~': 'ì'\n};\n\n/**\n * Norwegian/Danish character set\n * ESC (E or ESC (6\n * Reference: http://vt100.net/docs/vt220-rm/table2-12.html\n */\nCHARSETS['E'] =\nCHARSETS['6'] = {\n '@': 'Ä',\n '[': 'Æ',\n '\\\\': 'Ø',\n ']': 'Å',\n '^': 'Ü',\n '`': 'ä',\n '{': 'æ',\n '|': 'ø',\n '}': 'å',\n '~': 'ü'\n};\n\n/**\n * Spanish character set\n * ESC (Z\n * Reference: http://vt100.net/docs/vt220-rm/table2-13.html\n */\nCHARSETS['Z'] = {\n '#': '£',\n '@': '§',\n '[': '¡',\n '\\\\': 'Ñ',\n ']': '¿',\n '{': '°',\n '|': 'ñ',\n '}': 'ç'\n};\n\n/**\n * Swedish character set\n * ESC (H or ESC (7\n * Reference: http://vt100.net/docs/vt220-rm/table2-14.html\n */\nCHARSETS['H'] =\nCHARSETS['7'] = {\n '@': 'É',\n '[': 'Ä',\n '\\\\': 'Ö',\n ']': 'Å',\n '^': 'Ü',\n '`': 'é',\n '{': 'ä',\n '|': 'ö',\n '}': 'å',\n '~': 'ü'\n};\n\n/**\n * Swiss character set\n * ESC (=\n * Reference: http://vt100.net/docs/vt220-rm/table2-15.html\n */\nCHARSETS['='] = {\n '#': 'ù',\n '@': 'à',\n '[': 'é',\n '\\\\': 'ç',\n ']': 'ê',\n '^': 'î',\n '_': 'è',\n '`': 'ô',\n '{': 'ä',\n '|': 'ö',\n '}': 'ü',\n '~': 'û'\n};\n","/**\n * @license MIT\n */\n\nimport { ITerminal, IBufferSet } from './Interfaces';\nimport { Buffer } from './Buffer';\nimport { EventEmitter } from './EventEmitter';\n\n/**\n * The BufferSet represents the set of two buffers used by xterm terminals (normal and alt) and\n * provides also utilities for working with them.\n */\nexport class BufferSet extends EventEmitter implements IBufferSet {\n private _normal: Buffer;\n private _alt: Buffer;\n private _activeBuffer: Buffer;\n\n /**\n * Create a new BufferSet for the given terminal.\n * @param {Terminal} terminal - The terminal the BufferSet will belong to\n */\n constructor(private _terminal: ITerminal) {\n super();\n this._normal = new Buffer(this._terminal);\n this._normal.fillViewportRows();\n this._alt = new Buffer(this._terminal);\n this._activeBuffer = this._normal;\n }\n\n /**\n * Returns the alt Buffer of the BufferSet\n * @returns {Buffer}\n */\n public get alt(): Buffer {\n return this._alt;\n }\n\n /**\n * Returns the normal Buffer of the BufferSet\n * @returns {Buffer}\n */\n public get active(): Buffer {\n return this._activeBuffer;\n }\n\n /**\n * Returns the currently active Buffer of the BufferSet\n * @returns {Buffer}\n */\n public get normal(): Buffer {\n return this._normal;\n }\n\n /**\n * Sets the normal Buffer of the BufferSet as its currently active Buffer\n */\n public activateNormalBuffer(): void {\n // The alt buffer should always be cleared when we switch to the normal\n // buffer. This frees up memory since the alt buffer should always be new\n // when activated.\n this._alt.clear();\n\n this._activeBuffer = this._normal;\n this.emit('activate', this._normal);\n }\n\n /**\n * Sets the alt Buffer of the BufferSet as its currently active Buffer\n */\n public activateAltBuffer(): void {\n // Since the alt buffer is always cleared when the normal buffer is\n // activated, we want to fill it when switching to it.\n this._alt.fillViewportRows();\n\n this._activeBuffer = this._alt;\n this.emit('activate', this._alt);\n }\n\n /**\n * Resizes both normal and alt buffers, adjusting their data accordingly.\n * @param newCols The new number of columns.\n * @param newRows The new number of rows.\n */\n public resize(newCols: number, newRows: number): void {\n this._normal.resize(newCols, newRows);\n this._alt.resize(newCols, newRows);\n }\n}\n","/**\n * @license MIT\n */\n\nimport { ITerminal, IBuffer } from './Interfaces';\nimport { CircularList } from './utils/CircularList';\n\n/**\n * This class represents a terminal buffer (an internal state of the terminal), where the\n * following information is stored (in high-level):\n * - text content of this particular buffer\n * - cursor position\n * - scroll position\n */\nexport class Buffer implements IBuffer {\n private _lines: CircularList<[number, string, number][]>;\n\n public ydisp: number;\n public ybase: number;\n public y: number;\n public x: number;\n public scrollBottom: number;\n public scrollTop: number;\n public tabs: any;\n public savedY: number;\n public savedX: number;\n\n /**\n * Create a new Buffer.\n * @param {Terminal} _terminal - The terminal the Buffer will belong to\n * @param {number} ydisp - The scroll position of the Buffer in the viewport\n * @param {number} ybase - The scroll position of the y cursor (ybase + y = the y position within the Buffer)\n * @param {number} y - The cursor's y position after ybase\n * @param {number} x - The cursor's x position after ybase\n */\n constructor(\n private _terminal: ITerminal\n ) {\n this.clear();\n }\n\n public get lines(): CircularList<[number, string, number][]> {\n return this._lines;\n }\n\n /**\n * Fills the buffer's viewport with blank lines.\n */\n public fillViewportRows(): void {\n if (this._lines.length === 0) {\n let i = this._terminal.rows;\n while (i--) {\n this.lines.push(this._terminal.blankLine());\n }\n }\n }\n\n /**\n * Clears the buffer to it's initial state, discarding all previous data.\n */\n public clear(): void {\n this.ydisp = 0;\n this.ybase = 0;\n this.y = 0;\n this.x = 0;\n this.scrollBottom = 0;\n this.scrollTop = 0;\n this.tabs = {};\n this._lines = new CircularList<[number, string, number][]>(this._terminal.scrollback);\n this.scrollBottom = this._terminal.rows - 1;\n }\n\n /**\n * Resizes the buffer, adjusting its data accordingly.\n * @param newCols The new number of columns.\n * @param newRows The new number of rows.\n */\n public resize(newCols: number, newRows: number): void {\n // Don't resize the buffer if it's empty and hasn't been used yet.\n if (this._lines.length === 0) {\n return;\n }\n\n // Deal with columns increasing (we don't do anything when columns reduce)\n if (this._terminal.cols < newCols) {\n const ch: [number, string, number] = [this._terminal.defAttr, ' ', 1]; // does xterm use the default attr?\n for (let i = 0; i < this._lines.length; i++) {\n if (this._lines.get(i) === undefined) {\n this._lines.set(i, this._terminal.blankLine());\n }\n while (this._lines.get(i).length < newCols) {\n this._lines.get(i).push(ch);\n }\n }\n }\n\n // Resize rows in both directions as needed\n let addToY = 0;\n if (this._terminal.rows < newRows) {\n for (let y = this._terminal.rows; y < newRows; y++) {\n if (this._lines.length < newRows + this.ybase) {\n if (this.ybase > 0 && this._lines.length <= this.ybase + this.y + addToY + 1) {\n // There is room above the buffer and there are no empty elements below the line,\n // scroll up\n this.ybase--;\n addToY++;\n if (this.ydisp > 0) {\n // Viewport is at the top of the buffer, must increase downwards\n this.ydisp--;\n }\n } else {\n // Add a blank line if there is no buffer left at the top to scroll to, or if there\n // are blank lines after the cursor\n this._lines.push(this._terminal.blankLine());\n }\n }\n }\n } else { // (this._terminal.rows >= newRows)\n for (let y = this._terminal.rows; y > newRows; y--) {\n if (this._lines.length > newRows + this.ybase) {\n if (this._lines.length > this.ybase + this.y + 1) {\n // The line is a blank line below the cursor, remove it\n this._lines.pop();\n } else {\n // The line is the cursor, scroll down\n this.ybase++;\n this.ydisp++;\n }\n }\n }\n }\n\n // Make sure that the cursor stays on screen\n if (this.y >= newRows) {\n this.y = newRows - 1;\n }\n if (addToY) {\n this.y += addToY;\n }\n\n if (this.x >= newCols) {\n this.x = newCols - 1;\n }\n\n this.scrollTop = 0;\n this.scrollBottom = newRows - 1;\n }\n}\n",null],"names":[],"mappings":"AsBAA;;;ADKA;AASA;AAqBA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AAAA;AAGA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AArIa;;;;;;;;;;;;;;;;;ADTb;AACA;AAMA;AAAA;AASA;AAAA;AAAA;AAEA;AACA;AACA;AACA;;AACA;AAMA;AAAA;AACA;AACA;;;AAAA;AAMA;AAAA;AACA;AACA;;;AAAA;AAMA;AAAA;AACA;AACA;;;AAAA;AAKA;AAIA;AAEA;AACA;AACA;AAKA;AAGA;AAEA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AAAA;AA3Ea;;;;;;;ADHA;AAKA;AAYb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AAMA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;AD3OA;AAwBA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAMA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAOA;AACA;AACA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAGA;AACA;AACA;AAEA;AAGA;AACA;AACA;AAEA;AACA;AAUA;AAAA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAAA;AAGA;AACA;AACA;AACA;AAUA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAAA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AAAA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAMA;AACA;AACA;AACA;AAAA;AACA;AAAA;AApNa;;;;;;;ADRb;AAAA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AACA;AAAC;;;;;;;ADpEA;AAED;AAGA;AAGA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AAAA;AAAA;;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AA3Da;;;;;;;ADNb;AACA;AASA;AAEA;AAAA;AAAA;AAEA;AACA;AAGA;AAEA;AACA;AACA;AAEA;AAIA;AAEA;AACA;AAGA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAIA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAGA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAGA;AAEA;AAGA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAMA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAMA;AACA;AAMA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAOA;AACA;AACA;AAOA;AACA;AACA;AAMA;AACA;AAEA;AACA;AAAA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AAEA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AAcA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAaA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AAMA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AAMA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAMA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AAIA;AACA;AACA;AACA;AAuCA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AAIA;AACA;AACA;AAAA;AACA;AACA;AAAA;AAGA;AACA;AAAA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAOA;AACA;AAAA;AACA;AAAA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAUA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAwFA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AAKA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAoFA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAkEA;AAEA;AACA;AACA;AACA;AAEA;AAOA;AACA;AACA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AACA;AAAA;AAEA;AACA;AACA;AAAA;AAEA;AACA;AACA;AAIA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAGA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AACA;AAIA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAAA;AAEA;AACA;AACA;AAIA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAAA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAyBA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAGA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAGA;AACA;AAGA;AACA;AAGA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAYA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AAAA;AA96Ca;AAg7CA;AAGb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAaA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;;;;;;;ADnlDA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AAKA;AAeA;AAFA;AAGA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AAMA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AAOA;AACA;AACA;AAYA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAOA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA;AAAA;AAOA;AACA;AAEA;AACA;AACA;AACA;AAQA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AAEA;AACA;AACA;AACA;AAEA;AAGA;AACA;AACA;AACA;AAAA;AAEA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AAAA;AAAA;AAAA;;AACA;AACA;AACA;AACA;AACA;AACA;AAWA;AAGA;AACA;AACA;AAKA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAhUmB;AANN;;;;;;;ADhCb;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AAKA;AACA;AACA;AAEA;AACA;AAOA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAIA;AACA;AAGA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AAKA;AACA;AAIA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAIA;AACA;AAAA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AAEA;AACA;AAIA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAGA;AACA;AAMA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AAQA;AACA;AACA;AAQA;AACA;AACA;AAOA;AACA;AACA;AAKA;AACA;AACA;AAMA;AACA;AACA;AACA;AAKA;AACA;AACA;AASA;AAAA;AA9da;;;;;;;ADjKb;AAOA;AAKA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAAC;AAED;AAEA;AAQA;AAAA;AANA;AACA;AACA;AAEA;AAKA;AACA;AACA;AACA;AAKA;AAQA;AACA;AACA;AACA;AACA;AACA;AAMA;AAKA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAuBA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAGA;AACA;AAAA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAIA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAAA;AAOA;AAEA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AAGA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAQA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AApWa;AAwWb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;AD3YA;AACA;AAGA;AAEA;AACA;AAMA;AAKA;AAKA;AAMA;AAIA;AACA;AAEA;AACA;AAaA;AAAA;AACA;AACA;AACA;AACA;AAUA;AAAA;AAiCA;AAAA;AACA;AACA;AACA;AACA;AATA;AAYA;AACA;AAEA;AACA;;AACA;AAKA;AAAA;AACA;AACA;AAEA;AAMA;AACA;AAMA;AACA;AACA;AACA;AAKA;AACA;AACA;AAOA;AACA;AACA;AACA;AAEA;AAAA;;;AAAA;AACA;AAAA;;;AAAA;AAKA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAAA;AAKA;AAAA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAIA;AACA;AACA;AAEA;AACA;;;AAAA;AAKA;AACA;AACA;AACA;AACA;AAOA;AAAA;AAEA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAGA;AACA;AAEA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAMA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AAEA;AACA;AACA;AAGA;AACA;AAGA;AAGA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AAKA;AAAA;AAEA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AAGA;AAGA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AAGA;AAIA;AACA;AACA;AAAA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AAGA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAKA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAAA;AA1lBa;;;;;;;AD1Db;AAuBA;AACA;AAEA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAKA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;;;AAAA;AAMA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AACA;AAGA;AAEA;AACA;AACA;AACA;AACA;AACA;;;AAAA;AAKA;AACA;AACA;AACA;AACA;AAOA;AAEA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAAA;AArHa;;;;;;;ADCb;AAaA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AAEA;AACA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AAQA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAAA;AAMA;AACA;AACA;AAAA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AAAA;AArIa;;;;;;;ADWb;AACA;AACA;AACA;AACA;AACA;AALA;AAWA;AACA;AACA;AACA;AAAA;AACA;AACA;AAGA;AACA;AATA;AAgBA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAzBA;AAgCA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AApBA;AA4BA;AACA;AAGA;AACA;AACA;AANA;;;;;;;ADvGA;AAEA;AACA;AACA;AAEa;AACA;AAKA;AACA;AACA;AACA;AACA;;;;;;;ADhBb;AACA;AAYA;AAAA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAnCA;;;;;;;;;;;;;;;;;ADdA;AAKA;AAAA;AAOA;AAAA;AAEA;AACA;;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAnDa;;;;;;;;;;;;;;;;;ADJb;AAGA;AAAA;AAKA;AAAA;AAEA;AACA;AACA;;AACA;AAEA;AAAA;AACA;AACA;AAEA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAXA;AAaA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AATA;AAWA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAAA;AAUA;AACA;AACA;AAUA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAMA;AACA;AACA;AAWA;AAAA;AAAA;AAAA;;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AAAA;AAhMa;;;;;;;ADAb;AAYA;AAAA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAAA;AAhE0B;AAET;AAHJ;;;;;;;ADEb;AACA;AACA;AAFA;AAEC;;;;;;;ADPD;AAEA;AACA;AACA;AAEA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AAjBA;AAgCA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AAGA;AACA;AAEA;AACA;AApBA;AAgCA;AACA;AACA;AACA;AAGA;AACA;AAEA;AACA;AAVA;;;;;;;AD1DA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAcA;AAOA;AAMA;AAOA;AAkBA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AAEA;AAAA;AACA;AAEA;AACA;AAEA;AAIA;AAIA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAGA;AACA;AAQA;AAGA;AAGA;AAGA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAEA;AAGA;AACA;AAEA;AAKA;AAEA;AACA;AAOA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAIA;AACA;AAKA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AAKA;AACA;AACA;AAMA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AAAA;AACA;AAEA;AACA;AACA;AAGA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAKA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAQA;AAAA;AACA;AAEA;AAEA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AAIA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AAGA;AAIA;AAMA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAIA;AAOA;AACA;AAQA;AACA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AAYA;AACA;AAKA;AACA;AAIA;AAGA;AACA;AAAA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAIA;AACA;AACA;AAIA;AACA;AAGA;AACA;AAAA;AAIA;AAEA;AACA;AAIA;AACA;AACA;AAAA;AACA;AAAA;AACA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAQA;AAOA;AAGA;AACA;AACA;AACA;AACA;AAAA;AACA;AAAA;AACA;AAAA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAAA;AACA;AAAA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAaA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AAGA;AAEA;AACA;AAAA;AACA;AACA;AAGA;AAEA;AACA;AAEA;AAIA;AACA;AAEA;AAAA;AAGA;AAIA;AACA;AACA;AACA;AACA;AAGA;AAAA;AAGA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AAMA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAIA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAQA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AACA;AAGA;AAGA;AAEA;AAEA;AACA;AAAA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAQA;AACA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAMA;AACA;AACA;AAKA;AACA;AACA;AAKA;AACA;AACA;AAMA;AACA;AAKA;AAGA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AAEA;AACA;AAOA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAMA;AACA;AACA;AAQA;AACA;AACA;AACA;AACA;AAUA;AACA;AACA;AAQA;AACA;AACA;AACA;AACA;AAEA;AACA;AAQA;AACA;AACA;AACA;AACA;AAEA;AACA;AAYA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AAMA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AASA;AACA;AAGA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAIA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AAIA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AAGA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AAGA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AACA;AAAA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAMA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AAEA;AAGA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAMA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAMA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AAKA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AAKA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAOA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AAAA;AAEA;AAGA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AAMA;AACA;AAAA;AACA;AAAA;AAOA;AAKA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAOA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AAOA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAQA;AACA;AACA;AACA;AAEA;AAMA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAOA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AAOA;AAEA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAOA;AAOA;AACA;AAUA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAQA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AAGA;AACA;AAGA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AASA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAGA;AACA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAQA;AACA;AACA;AAEA;;;"} \ No newline at end of file diff --git a/package.json b/package.json index e63e2e7a..d0ba5c01 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "xterm", "description": "Full xterm terminal, in your browser", - "version": "2.9.0", + "version": "2.9.1", "ignore": [ "demo", "test", From 45157d80c66bdf75c2c79ad8ec81929134e797ee Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 6 Aug 2017 12:59:40 -0700 Subject: [PATCH 022/378] Properly cancel event when in mouse mode Fixes #848 --- src/Terminal.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index cd44e7fb..b6a9d104 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1100,7 +1100,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT if (!this.mouseEvents) return; if (this.x10Mouse || this.vt300Mouse || this.decLocator) return; sendButton(ev); - return this.cancel(ev); + ev.preventDefault(); }); // allow wheel scrolling in @@ -2247,6 +2247,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.buffer.tabs[this.buffer.x] = true; } + // TODO: Remove cancel function and cancelEvents option public cancel(ev: Event, force?: boolean): boolean { if (!this.options.cancelEvents && !force) { return; From 63268318ed26469afe9a31898941767a8e284367 Mon Sep 17 00:00:00 2001 From: Peter Baumgarten Date: Sun, 6 Aug 2017 15:09:11 -0500 Subject: [PATCH 023/378] Marking that a C++ compiler is required. On some linux distributions a C++ compiler is not installed by default. This can frustrate some new users. --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ef9ea422..5a5f8e51 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,9 @@ Xterm.js works seamlessly in Electron apps and may even work on earlier versions ### Linux or macOS -Run the following commands: +First, be sure that a C++ compiler such as GCC-C++ or Clang is installed + +Then run the following commands: ``` $ npm install From 109d467b95970a479de0a33083aabfa8e3e09996 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 6 Aug 2017 13:28:58 -0700 Subject: [PATCH 024/378] Make buffer length = rows + scrollback Fixes #847 --- src/Buffer.test.ts | 4 ++-- src/Buffer.ts | 2 +- src/BufferSet.ts | 12 ++++++++++++ src/Terminal.ts | 24 +++++++----------------- src/test/test.js | 17 +---------------- src/utils/CircularList.ts | 3 +++ 6 files changed, 26 insertions(+), 36 deletions(-) diff --git a/src/Buffer.test.ts b/src/Buffer.test.ts index 99bd6d0f..1e220915 100644 --- a/src/Buffer.test.ts +++ b/src/Buffer.test.ts @@ -24,9 +24,9 @@ describe('Buffer', () => { }); describe('constructor', () => { - it('should create a CircularList with max length equal to scrollback, for its lines', () => { + it('should create a CircularList with max length equal to rows + scrollback, for its lines', () => { assert.instanceOf(buffer.lines, CircularList); - assert.equal(buffer.lines.maxLength, terminal.options.scrollback); + assert.equal(buffer.lines.maxLength, terminal.rows + terminal.options.scrollback); }); it('should set the Buffer\'s scrollBottom value equal to the terminal\'s rows -1', () => { assert.equal(buffer.scrollBottom, terminal.rows - 1); diff --git a/src/Buffer.ts b/src/Buffer.ts index eb346d5b..9d2222ff 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -67,7 +67,7 @@ export class Buffer implements IBuffer { this.scrollBottom = 0; this.scrollTop = 0; this.tabs = {}; - this._lines = new CircularList(this._terminal.options.scrollback); + this._lines = new CircularList(this._terminal.rows + this._terminal.options.scrollback); this.scrollBottom = this._terminal.rows - 1; } diff --git a/src/BufferSet.ts b/src/BufferSet.ts index 24d6c314..9136c5fb 100644 --- a/src/BufferSet.ts +++ b/src/BufferSet.ts @@ -76,6 +76,17 @@ export class BufferSet extends EventEmitter implements IBufferSet { this.emit('activate', this._alt); } + /** + * Refreshes the max length of the buffer set, this should be called whenever + * terminal rows or scrollback changes. + * @param rows The number of terminal rows. + */ + public refreshMaxLength(rows: number): void { + this._normal.lines.maxLength = rows + this._terminal.options.scrollback; + // TODO: The alt buffer should never have scrollback, see https://github.com/sourcelair/xterm.js/issues/802 + this._alt.lines.maxLength = rows + this._terminal.options.scrollback; + } + /** * Resizes both normal and alt buffers, adjusting their data accordingly. * @param newCols The new number of columns. @@ -84,5 +95,6 @@ export class BufferSet extends EventEmitter implements IBufferSet { public resize(newCols: number, newRows: number): void { this._normal.resize(newCols, newRows); this._alt.resize(newCols, newRows); + this.refreshMaxLength(newRows); } } diff --git a/src/Terminal.ts b/src/Terminal.ts index cd44e7fb..2afb4502 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -439,18 +439,10 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT } switch (key) { case 'scrollback': - if (value < this.rows) { - let msg = 'Setting the scrollback value less than the number of rows '; - - msg += `(${this.rows}) is not allowed.`; - - console.warn(msg); - return; - } - if (this.options[key] !== value) { - if (this.buffer.lines.length > value) { - const amountToTrim = this.buffer.lines.length - value; + const newBufferLength = this.rows + value; + if (this.buffer.lines.length > newBufferLength) { + const amountToTrim = this.buffer.lines.length - newBufferLength; const needsRefresh = (this.buffer.ydisp - amountToTrim < 0); this.buffer.lines.trimStart(amountToTrim); this.buffer.ybase = Math.max(this.buffer.ybase - amountToTrim, 0); @@ -459,8 +451,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.refresh(0, this.rows - 1); } } - this.buffer.lines.maxLength = value; - this.viewport.syncScrollArea(); } break; } @@ -473,6 +463,10 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.element.classList.toggle(`xterm-cursor-style-underline`, value === 'underline'); this.element.classList.toggle(`xterm-cursor-style-bar`, value === 'bar'); break; + case 'scrollback': + this.buffers.refreshMaxLength(this.rows); + this.viewport.syncScrollArea(); + break; case 'tabStopWidth': this.setupStops(); break; } } @@ -1935,10 +1929,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT return; } - if (y > this.getOption('scrollback')) { - this.setOption('scrollback', y); - } - let line; let el; let i; diff --git a/src/test/test.js b/src/test/test.js index 5c4dd5f2..51d1644c 100644 --- a/src/test/test.js +++ b/src/test/test.js @@ -44,16 +44,6 @@ describe('xterm.js', function() { }); describe('setOption', function() { - let originalWarn; - let warnCallCount; - beforeEach(() => { - originalWarn = console.warn; - warnCallCount = 0; - console.warn = () => warnCallCount++; - }); - afterEach(() => { - console.warn = originalWarn; - }); it('should set the option correctly', function() { xterm.setOption('cursorBlink', true); assert.equal(xterm.cursorBlink, true); @@ -63,12 +53,7 @@ describe('xterm.js', function() { assert.equal(xterm.options.cursorBlink, false); }); it('should throw when setting a non-existant option', function() { - assert.throws(xterm.setOption.bind(xterm, 'fake', true)); - }); - it('should warn and do nothing when scrollback is less than number of rows', function() { - xterm.setOption('scrollback', xterm.rows - 1); - assert.equal(xterm.getOption('scrollback'), 1000); - assert.equal(warnCallCount, 1); + assert.throws(() => xterm.setOption('fake', true)); }); }); diff --git a/src/utils/CircularList.ts b/src/utils/CircularList.ts index 373d78b0..23d6c337 100644 --- a/src/utils/CircularList.ts +++ b/src/utils/CircularList.ts @@ -25,6 +25,9 @@ export class CircularList extends EventEmitter implements ICircularList { } public set maxLength(newMaxLength: number) { + if (this.maxLength === newMaxLength) { + return; + } // Reconstruct array, starting at index 0. Only transfer values from the // indexes 0 to length. let newArray = new Array(newMaxLength); From 26977b370ecd77e44503c8de1a4e2fb92d547adb Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 6 Aug 2017 13:47:03 -0700 Subject: [PATCH 025/378] Fix crash on negative tab stop width Fixes #851 --- src/Terminal.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Terminal.ts b/src/Terminal.ts index cd44e7fb..d410f51f 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -438,6 +438,12 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT throw new Error('No option with key "' + key + '"'); } switch (key) { + case 'tabStopWidth': + if (value < 1) { + console.warn(`tabStopWidth cannot be less than 1, value: ${value}`); + return; + } + break; case 'scrollback': if (value < this.rows) { let msg = 'Setting the scrollback value less than the number of rows '; From 27f7679494620e6a2467370c792c58894bd450a5 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 6 Aug 2017 14:17:27 -0700 Subject: [PATCH 026/378] Add some padding and a shrink threshold to CircularList This is needed so that resize is still performant. Now the list size is only rebuild (O(n)) when it goes beyond 40 entries of the initial max length. The List is also rebuilt when the size shrinks below 120 of the actual backing max length (init_max_length + 40 - 120). --- src/utils/CircularList.test.ts | 29 ++++++++++++++++++++++ src/utils/CircularList.ts | 45 +++++++++++++++++++++++++++------- 2 files changed, 65 insertions(+), 9 deletions(-) diff --git a/src/utils/CircularList.test.ts b/src/utils/CircularList.test.ts index e27e7f7a..891ba3c5 100644 --- a/src/utils/CircularList.test.ts +++ b/src/utils/CircularList.test.ts @@ -5,6 +5,13 @@ import { assert } from 'chai'; import { CircularList } from './CircularList'; +class TestCircularList extends CircularList { + public get array(): T[] { return this._array; } +} + +const MAX_LENGTH_PADDING = 40; +const MAX_LENGTH_SHRINK_REBUILD_THRESHOLD = 120; + describe('CircularList', () => { describe('push', () => { it('should push values onto the array', () => { @@ -68,6 +75,28 @@ describe('CircularList', () => { list.maxLength = 4; assert.equal(list.maxLength, 4); }); + + it('should resize the backing array\'s size only when it increases beyond the allocated padding', () => { + const initSize = 200; + const list = new TestCircularList(initSize); + const expectedBackingMaxLength = initSize + MAX_LENGTH_PADDING; + assert.equal(list.array.length, expectedBackingMaxLength); + list.maxLength = expectedBackingMaxLength; + assert.equal(list.array.length, expectedBackingMaxLength); + list.maxLength = expectedBackingMaxLength + 1; + assert.equal(list.array.length, expectedBackingMaxLength + 1 + MAX_LENGTH_PADDING); + }); + + it('should resize the backing array\'s size only when it reduces beyond the shrink threshold', () => { + const initSize = 200; + const list = new TestCircularList(initSize); + const expectedThresholdMaxLength = initSize + MAX_LENGTH_PADDING - MAX_LENGTH_SHRINK_REBUILD_THRESHOLD; + assert.equal(list.array.length, initSize + MAX_LENGTH_PADDING); + list.maxLength = expectedThresholdMaxLength; + assert.equal(list.array.length, initSize + MAX_LENGTH_PADDING); + list.maxLength = expectedThresholdMaxLength - 1; + assert.equal(list.array.length, expectedThresholdMaxLength - 1 + MAX_LENGTH_PADDING); + }); }); describe('length', () => { diff --git a/src/utils/CircularList.ts b/src/utils/CircularList.ts index 23d6c337..ed8c065d 100644 --- a/src/utils/CircularList.ts +++ b/src/utils/CircularList.ts @@ -8,33 +8,60 @@ import { EventEmitter } from '../EventEmitter'; import { ICircularList } from '../Interfaces'; +/** + * Padding added to the max length of the CircularList, this allows increasing + * the max length of the list without reconstructing the backing array and then + * copying over the values. + */ +const MAX_LENGTH_PADDING = 40; + +const MAX_LENGTH_SHRINK_REBUILD_THRESHOLD = MAX_LENGTH_PADDING * 3; + export class CircularList extends EventEmitter implements ICircularList { - private _array: T[]; + protected _array: T[]; private _startIndex: number; private _length: number; - constructor(maxLength: number) { + constructor( + private _maxLength: number + ) { super(); - this._array = new Array(maxLength); + this._array = new Array(this._maxLength + MAX_LENGTH_PADDING); this._startIndex = 0; this._length = 0; } public get maxLength(): number { - return this._array.length; + return this._maxLength; } public set maxLength(newMaxLength: number) { - if (this.maxLength === newMaxLength) { + // There was no change in maxLength, return early. + if (this._maxLength === newMaxLength) { return; } + + // Ensure the array has padding to expand into. + if (this._array.length >= newMaxLength) { + // Ensure that the array hasn't shrunk significantly, if it has we + // should rebuild the array with a new max length to reclaim some memory. + if (this._array.length - newMaxLength <= MAX_LENGTH_SHRINK_REBUILD_THRESHOLD) { + // Increase the max length and return. + this._maxLength = newMaxLength; + return; + } + } + // Reconstruct array, starting at index 0. Only transfer values from the - // indexes 0 to length. - let newArray = new Array(newMaxLength); + // indexes 0 to length. The new array's size includes some padding that + // allows the array's size to increase a little without rebuilding the + // array. + let newArray = new Array(newMaxLength + MAX_LENGTH_PADDING); for (let i = 0; i < Math.min(newMaxLength, this.length); i++) { newArray[i] = this._array[this._getCyclicIndex(i)]; } this._array = newArray; + this._maxLength = newMaxLength; this._startIndex = 0; } @@ -92,9 +119,9 @@ export class CircularList extends EventEmitter implements ICircularList { */ public push(value: T): void { this._array[this._getCyclicIndex(this._length)] = value; - if (this._length === this.maxLength) { + if (this._length === this._maxLength) { this._startIndex++; - if (this._startIndex === this.maxLength) { + if (this._startIndex === this._maxLength) { this._startIndex = 0; } this.emit('trim', 1); From a57f55dcbf7962662d8ba3e6da75bbea96eb331b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 6 Aug 2017 18:18:49 -0700 Subject: [PATCH 027/378] Support buffers with scrollback of 0, force alt as 0 Fixes #802 --- src/Buffer.test.ts | 18 +++++++++++++++++- src/Buffer.ts | 46 ++++++++++++++++++++++++++++++++++++++------- src/BufferSet.ts | 17 ++--------------- src/InputHandler.ts | 27 +++++--------------------- src/Terminal.ts | 11 +++++------ 5 files changed, 68 insertions(+), 51 deletions(-) diff --git a/src/Buffer.test.ts b/src/Buffer.test.ts index 1e220915..2e2ae50c 100644 --- a/src/Buffer.test.ts +++ b/src/Buffer.test.ts @@ -20,7 +20,7 @@ describe('Buffer', () => { terminal.cols = INIT_COLS; terminal.rows = INIT_ROWS; terminal.options.scrollback = 1000; - buffer = new Buffer(terminal); + buffer = new Buffer(terminal, false); }); describe('constructor', () => { @@ -145,4 +145,20 @@ describe('Buffer', () => { }); }); }); + + describe('alt buffer', () => { + it('should always have a scrollback of 0', () => { + assert.equal(terminal.options.scrollback, 1000); + // Test size on initialization + buffer = new Buffer(terminal, true); + buffer.fillViewportRows(); + assert.equal(buffer.lines.maxLength, INIT_ROWS); + // Test size on buffer increase + buffer.resize(INIT_COLS, INIT_ROWS * 2); + assert.equal(buffer.lines.maxLength, INIT_ROWS * 2); + // Test size on buffer decrease + buffer.resize(INIT_COLS, INIT_ROWS / 2); + assert.equal(buffer.lines.maxLength, INIT_ROWS / 2); + }); + }); }); diff --git a/src/Buffer.ts b/src/Buffer.ts index 9d2222ff..5fe97779 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -28,14 +28,12 @@ export class Buffer implements IBuffer { /** * Create a new Buffer. - * @param {Terminal} _terminal - The terminal the Buffer will belong to - * @param {number} ydisp - The scroll position of the Buffer in the viewport - * @param {number} ybase - The scroll position of the y cursor (ybase + y = the y position within the Buffer) - * @param {number} y - The cursor's y position after ybase - * @param {number} x - The cursor's x position after ybase + * @param _terminal The terminal the Buffer will belong to. + * @param _isAltBuffer Whether the buffer is the alt buffer. */ constructor( - private _terminal: ITerminal + private _terminal: ITerminal, + private _isAltBuffer: boolean ) { this.clear(); } @@ -44,6 +42,20 @@ export class Buffer implements IBuffer { return this._lines; } + /** + * Gets the correct buffer length based on the rows provided, the terminal's + * scrollback and whether this is an alt buffer. + * @param rows The terminal rows to use in the calculation. + */ + private _getCorrectBufferLength(rows: number): number { + // The alt buffer should never have scrollback. + // See http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer + if (this._isAltBuffer) { + return rows; + } + return rows + this._terminal.options.scrollback; + } + /** * Fills the buffer's viewport with blank lines. */ @@ -67,7 +79,7 @@ export class Buffer implements IBuffer { this.scrollBottom = 0; this.scrollTop = 0; this.tabs = {}; - this._lines = new CircularList(this._terminal.rows + this._terminal.options.scrollback); + this._lines = new CircularList(this._getCorrectBufferLength(this._terminal.rows)); this.scrollBottom = this._terminal.rows - 1; } @@ -82,6 +94,13 @@ export class Buffer implements IBuffer { return; } + // Increase max length if needed before adjustments to allow space to fill + // as required. + const newMaxLength = this._getCorrectBufferLength(newRows); + if (newMaxLength > this._lines.maxLength) { + this._lines.maxLength = newMaxLength; + } + // Deal with columns increasing (we don't do anything when columns reduce) if (this._terminal.cols < newCols) { const ch: CharData = [this._terminal.defAttr, ' ', 1]; // does xterm use the default attr? @@ -131,6 +150,19 @@ export class Buffer implements IBuffer { } } + // Reduce max length if needed after adjustments, this is done after as it + // would otherwise cut data from the bottom of the buffer. + if (newMaxLength < this._lines.maxLength) { + // Trim from the top of the buffer and adjust ybase and ydisp. + // const amountToTrim = this._lines.length - newMaxLength; + // if (amountToTrim > 0) { + // this._lines.trimStart(amountToTrim); + // this.ybase = Math.max(this.ybase - amountToTrim, 0); + // this.ydisp = Math.max(this.ydisp - amountToTrim, 0); + // } + this._lines.maxLength = newMaxLength; + } + // Make sure that the cursor stays on screen if (this.y >= newRows) { this.y = newRows - 1; diff --git a/src/BufferSet.ts b/src/BufferSet.ts index 9136c5fb..851fc47d 100644 --- a/src/BufferSet.ts +++ b/src/BufferSet.ts @@ -21,9 +21,9 @@ export class BufferSet extends EventEmitter implements IBufferSet { */ constructor(private _terminal: ITerminal) { super(); - this._normal = new Buffer(this._terminal); + this._normal = new Buffer(this._terminal, false); this._normal.fillViewportRows(); - this._alt = new Buffer(this._terminal); + this._alt = new Buffer(this._terminal, true); this._activeBuffer = this._normal; } @@ -71,22 +71,10 @@ export class BufferSet extends EventEmitter implements IBufferSet { // Since the alt buffer is always cleared when the normal buffer is // activated, we want to fill it when switching to it. this._alt.fillViewportRows(); - this._activeBuffer = this._alt; this.emit('activate', this._alt); } - /** - * Refreshes the max length of the buffer set, this should be called whenever - * terminal rows or scrollback changes. - * @param rows The number of terminal rows. - */ - public refreshMaxLength(rows: number): void { - this._normal.lines.maxLength = rows + this._terminal.options.scrollback; - // TODO: The alt buffer should never have scrollback, see https://github.com/sourcelair/xterm.js/issues/802 - this._alt.lines.maxLength = rows + this._terminal.options.scrollback; - } - /** * Resizes both normal and alt buffers, adjusting their data accordingly. * @param newCols The new number of columns. @@ -95,6 +83,5 @@ export class BufferSet extends EventEmitter implements IBufferSet { public resize(newCols: number, newRows: number): void { this._normal.resize(newCols, newRows); this._alt.resize(newCols, newRows); - this.refreshMaxLength(newRows); } } diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 5362cae9..4036ac15 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -442,23 +442,13 @@ export class InputHandler implements IInputHandler { } let row: number = this._terminal.buffer.y + this._terminal.buffer.ybase; - let j: number; - j = this._terminal.rows - 1 - this._terminal.buffer.scrollBottom; - j = this._terminal.rows - 1 + this._terminal.buffer.ybase - j + 1; - + let scrollBottomRowsOffset = this._terminal.rows - 1 - this._terminal.buffer.scrollBottom; + let scrollBottomAbsolute = this._terminal.rows - 1 + this._terminal.buffer.ybase - scrollBottomRowsOffset + 1; while (param--) { - if (this._terminal.buffer.lines.length === this._terminal.buffer.lines.maxLength) { - // Trim the start of lines to make room for the new line - this._terminal.buffer.lines.trimStart(1); - this._terminal.buffer.ybase--; - this._terminal.buffer.ydisp--; - row--; - j--; - } // test: echo -e '\e[44m\e[1L\e[0m' // blankLine(true) - xterm/linux behavior + this._terminal.buffer.lines.splice(scrollBottomAbsolute - 1, 1); this._terminal.buffer.lines.splice(row, 0, this._terminal.blankLine(true)); - this._terminal.buffer.lines.splice(j, 1); } // this.maxRange(); @@ -480,18 +470,11 @@ export class InputHandler implements IInputHandler { let j: number; j = this._terminal.rows - 1 - this._terminal.buffer.scrollBottom; j = this._terminal.rows - 1 + this._terminal.buffer.ybase - j; - while (param--) { - if (this._terminal.buffer.lines.length === this._terminal.buffer.lines.maxLength) { - // Trim the start of lines to make room for the new line - this._terminal.buffer.lines.trimStart(1); - this._terminal.buffer.ybase -= 1; - this._terminal.buffer.ydisp -= 1; - } // test: echo -e '\e[44m\e[1M\e[0m' // blankLine(true) - xterm/linux behavior - this._terminal.buffer.lines.splice(j + 1, 0, this._terminal.blankLine(true)); - this._terminal.buffer.lines.splice(row, 1); + this._terminal.buffer.lines.splice(row - 1, 1); + this._terminal.buffer.lines.splice(j, 0, this._terminal.blankLine(true)); } // this.maxRange(); diff --git a/src/Terminal.ts b/src/Terminal.ts index 2afb4502..743537b1 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -464,7 +464,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.element.classList.toggle(`xterm-cursor-style-bar`, value === 'bar'); break; case 'scrollback': - this.buffers.refreshMaxLength(this.rows); + this.buffers.resize(this.cols, this.rows); this.viewport.syncScrollArea(); break; case 'tabStopWidth': this.setupStops(); break; @@ -1177,17 +1177,16 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT let row; // Make room for the new row in lines - if (this.buffer.lines.length === this.buffer.lines.maxLength) { + const bufferNeedsTrimming = this.buffer.lines.length === this.buffer.lines.maxLength; + if (bufferNeedsTrimming) { this.buffer.lines.trimStart(1); this.buffer.ybase--; - if (this.buffer.ydisp !== 0) { - this.buffer.ydisp--; - } + this.buffer.ydisp = Math.max(this.buffer.ydisp - 1, 0); } this.buffer.ybase++; - // TODO: Why is this done twice? + // Scroll the viewport down to the bottom if the user is not scrolling if (!this.userScrolling) { this.buffer.ydisp = this.buffer.ybase; } From e7ee3ec63c005f7073d8daab8dd95eaff2f33c83 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 6 Aug 2017 18:56:37 -0700 Subject: [PATCH 028/378] Fix trimming on a buffer with 0 scrollback Unfortunately this removed the padding idea from CircularList as it would only work when the list is not full. --- src/Buffer.test.ts | 15 +++++++++++++++ src/Buffer.ts | 12 ++++++------ src/utils/CircularList.test.ts | 25 ------------------------- src/utils/CircularList.ts | 28 +++------------------------- 4 files changed, 24 insertions(+), 56 deletions(-) diff --git a/src/Buffer.test.ts b/src/Buffer.test.ts index 2e2ae50c..d96e6bde 100644 --- a/src/Buffer.test.ts +++ b/src/Buffer.test.ts @@ -88,6 +88,21 @@ describe('Buffer', () => { assert.equal(buffer.ydisp, 5); assert.equal(buffer.ybase, 5); }); + + describe('no scrollback', () => { + it('should trim from the top of the buffer when the cursor reaches the bottom', () => { + terminal.options.scrollback = 0; + buffer = new Buffer(terminal, false); + assert.equal(buffer.lines.maxLength, INIT_ROWS); + buffer.y = INIT_ROWS - 1; + buffer.fillViewportRows(); + buffer.lines.get(5)[0][1] = 'a'; + buffer.lines.get(INIT_ROWS - 1)[0][1] = 'b'; + buffer.resize(INIT_COLS, INIT_ROWS - 5); + assert.equal(buffer.lines.get(0)[0][1], 'a'); + assert.equal(buffer.lines.get(INIT_ROWS - 1 - 5)[0][1], 'b'); + }); + }); }); describe('row size increased', () => { diff --git a/src/Buffer.ts b/src/Buffer.ts index 5fe97779..74f440ea 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -154,12 +154,12 @@ export class Buffer implements IBuffer { // would otherwise cut data from the bottom of the buffer. if (newMaxLength < this._lines.maxLength) { // Trim from the top of the buffer and adjust ybase and ydisp. - // const amountToTrim = this._lines.length - newMaxLength; - // if (amountToTrim > 0) { - // this._lines.trimStart(amountToTrim); - // this.ybase = Math.max(this.ybase - amountToTrim, 0); - // this.ydisp = Math.max(this.ydisp - amountToTrim, 0); - // } + const amountToTrim = this._lines.length - newMaxLength; + if (amountToTrim > 0) { + this._lines.trimStart(amountToTrim); + this.ybase = Math.max(this.ybase - amountToTrim, 0); + this.ydisp = Math.max(this.ydisp - amountToTrim, 0); + } this._lines.maxLength = newMaxLength; } diff --git a/src/utils/CircularList.test.ts b/src/utils/CircularList.test.ts index 891ba3c5..f404b6f2 100644 --- a/src/utils/CircularList.test.ts +++ b/src/utils/CircularList.test.ts @@ -9,9 +9,6 @@ class TestCircularList extends CircularList { public get array(): T[] { return this._array; } } -const MAX_LENGTH_PADDING = 40; -const MAX_LENGTH_SHRINK_REBUILD_THRESHOLD = 120; - describe('CircularList', () => { describe('push', () => { it('should push values onto the array', () => { @@ -75,28 +72,6 @@ describe('CircularList', () => { list.maxLength = 4; assert.equal(list.maxLength, 4); }); - - it('should resize the backing array\'s size only when it increases beyond the allocated padding', () => { - const initSize = 200; - const list = new TestCircularList(initSize); - const expectedBackingMaxLength = initSize + MAX_LENGTH_PADDING; - assert.equal(list.array.length, expectedBackingMaxLength); - list.maxLength = expectedBackingMaxLength; - assert.equal(list.array.length, expectedBackingMaxLength); - list.maxLength = expectedBackingMaxLength + 1; - assert.equal(list.array.length, expectedBackingMaxLength + 1 + MAX_LENGTH_PADDING); - }); - - it('should resize the backing array\'s size only when it reduces beyond the shrink threshold', () => { - const initSize = 200; - const list = new TestCircularList(initSize); - const expectedThresholdMaxLength = initSize + MAX_LENGTH_PADDING - MAX_LENGTH_SHRINK_REBUILD_THRESHOLD; - assert.equal(list.array.length, initSize + MAX_LENGTH_PADDING); - list.maxLength = expectedThresholdMaxLength; - assert.equal(list.array.length, initSize + MAX_LENGTH_PADDING); - list.maxLength = expectedThresholdMaxLength - 1; - assert.equal(list.array.length, expectedThresholdMaxLength - 1 + MAX_LENGTH_PADDING); - }); }); describe('length', () => { diff --git a/src/utils/CircularList.ts b/src/utils/CircularList.ts index ed8c065d..e188fb5d 100644 --- a/src/utils/CircularList.ts +++ b/src/utils/CircularList.ts @@ -8,15 +8,6 @@ import { EventEmitter } from '../EventEmitter'; import { ICircularList } from '../Interfaces'; -/** - * Padding added to the max length of the CircularList, this allows increasing - * the max length of the list without reconstructing the backing array and then - * copying over the values. - */ -const MAX_LENGTH_PADDING = 40; - -const MAX_LENGTH_SHRINK_REBUILD_THRESHOLD = MAX_LENGTH_PADDING * 3; - export class CircularList extends EventEmitter implements ICircularList { protected _array: T[]; private _startIndex: number; @@ -26,7 +17,7 @@ export class CircularList extends EventEmitter implements ICircularList { private _maxLength: number ) { super(); - this._array = new Array(this._maxLength + MAX_LENGTH_PADDING); + this._array = new Array(this._maxLength); this._startIndex = 0; this._length = 0; } @@ -41,22 +32,9 @@ export class CircularList extends EventEmitter implements ICircularList { return; } - // Ensure the array has padding to expand into. - if (this._array.length >= newMaxLength) { - // Ensure that the array hasn't shrunk significantly, if it has we - // should rebuild the array with a new max length to reclaim some memory. - if (this._array.length - newMaxLength <= MAX_LENGTH_SHRINK_REBUILD_THRESHOLD) { - // Increase the max length and return. - this._maxLength = newMaxLength; - return; - } - } - // Reconstruct array, starting at index 0. Only transfer values from the - // indexes 0 to length. The new array's size includes some padding that - // allows the array's size to increase a little without rebuilding the - // array. - let newArray = new Array(newMaxLength + MAX_LENGTH_PADDING); + // indexes 0 to length. + let newArray = new Array(newMaxLength); for (let i = 0; i < Math.min(newMaxLength, this.length); i++) { newArray[i] = this._array[this._getCyclicIndex(i)]; } From fb6cbbdaad67801c2850d8f86994dfcf0b327ea0 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 6 Aug 2017 19:02:56 -0700 Subject: [PATCH 029/378] Fix vscode debugging This broke when the TS addon was added --- src/addons/search/search.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/addons/search/search.ts b/src/addons/search/search.ts index 1a9ac337..0b506b69 100644 --- a/src/addons/search/search.ts +++ b/src/addons/search/search.ts @@ -11,7 +11,7 @@ declare var require: any; declare var window: any; (function (addon) { - if ('Terminal' in window) { + if (typeof window !== 'undefined' && 'Terminal' in window) { /** * Plain browser environment */ @@ -21,7 +21,7 @@ declare var window: any; * CommonJS environment */ module.exports = addon(require('../../xterm')); - } else if (typeof define == 'function') { + } else if (typeof define === 'function') { /** * Require.js is available */ From 8c46f1b6baab79015877d0a72fa3c3fde39fcdb6 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 6 Aug 2017 19:19:49 -0700 Subject: [PATCH 030/378] Remove deprecated attachCustomKeydownHandler Fixes #744 --- src/Terminal.ts | 12 ------------ src/test/test.js | 2 +- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index cd44e7fb..43d42fbe 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1358,18 +1358,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.write(data + '\r\n'); } - /** - * DEPRECATED: only for backward compatibility. Please use attachCustomKeyEventHandler() instead. - * @param {function} customKeydownHandler The custom KeyboardEvent handler to attach. This is a - * function that takes a KeyboardEvent, allowing consumers to stop propogation and/or prevent - * the default action. The function returns whether the event should be processed by xterm.js. - */ - public attachCustomKeydownHandler(customKeydownHandler: CustomKeyEventHandler): void { - const message = 'attachCustomKeydownHandler() is DEPRECATED and will be removed soon. Please use attachCustomKeyEventHandler() instead.'; - console.warn(message); - this.attachCustomKeyEventHandler(customKeydownHandler); - } - /** * Attaches a custom key event handler which is run before keys are processed, giving consumers of * xterm.js ultimate control as to what keys should be processed by the terminal and what keys diff --git a/src/test/test.js b/src/test/test.js index 5c4dd5f2..df1dab4b 100644 --- a/src/test/test.js +++ b/src/test/test.js @@ -239,7 +239,7 @@ describe('xterm.js', function() { xterm.writeln('test'); } var startYDisp = (xterm.rows * 2) + 1; - xterm.attachCustomKeydownHandler(function () { + xterm.attachCustomKeyEventHandler(function () { return false; }); From b1c6a9783956d8bde3d7214b6dc6c7c156fd0830 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 6 Aug 2017 19:22:15 -0700 Subject: [PATCH 031/378] No longer focus the terminal by default Fixes #646 --- src/Terminal.ts | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index cd44e7fb..493e48bc 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -759,20 +759,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT // Initialize global actions that need to be taken on the document. this.initGlobal(); - /** - * Automatic focus functionality. - * TODO: Default to `false` starting with xterm.js 3.0. - */ - if (typeof focus === 'undefined') { - let message = 'You did not pass the `focus` argument in `Terminal.prototype.open()`.\n'; - - message += 'The `focus` argument now defaults to `true` but starting with xterm.js 3.0 '; - message += 'it will default to `false`.'; - - console.warn(message); - focus = true; - } - if (focus) { this.focus(); } From c2bb507fe4ffa279a15c85deafaa77ca11d12a1a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 7 Aug 2017 15:21:55 -0700 Subject: [PATCH 032/378] Move BufferLines.ts into Buffer.ts Part of #791 --- src/Buffer.ts | 51 +++++++++++++++++++++++++++++++++ src/InputHandler.ts | 3 +- src/Interfaces.ts | 3 +- src/SelectionManager.test.ts | 34 +++++++++++----------- src/SelectionManager.ts | 29 +++++++++---------- src/Terminal.ts | 5 ++-- src/utils/BufferLine.ts | 55 ------------------------------------ src/utils/TestUtils.ts | 3 ++ 8 files changed, 91 insertions(+), 92 deletions(-) delete mode 100644 src/utils/BufferLine.ts diff --git a/src/Buffer.ts b/src/Buffer.ts index eb346d5b..ed3309b6 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -6,6 +6,9 @@ import { ITerminal, IBuffer } from './Interfaces'; import { CircularList } from './utils/CircularList'; import { LineData, CharData } from './Types'; +const CHAR_DATA_CHAR_INDEX = 1; +const CHAR_DATA_WIDTH_INDEX = 2; + /** * This class represents a terminal buffer (an internal state of the terminal), where the * following information is stored (in high-level): @@ -146,4 +149,52 @@ export class Buffer implements IBuffer { this.scrollTop = 0; this.scrollBottom = newRows - 1; } + + /** + * Translates a buffer line to a string, with optional start and end columns. + * Wide characters will count as two columns in the resulting string. This + * function is useful for getting the actual text underneath the raw selection + * position. + * @param line The line being translated. + * @param trimRight Whether to trim whitespace to the right. + * @param startCol The column to start at. + * @param endCol The column to end at. + */ + public translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol: number = 0, endCol: number = null): string { + // Get full line + let lineString = ''; + let widthAdjustedStartCol = startCol; + let widthAdjustedEndCol = endCol; + const line = this.lines.get(lineIndex); + for (let i = 0; i < line.length; i++) { + const char = line[i]; + lineString += char[CHAR_DATA_CHAR_INDEX]; + // Adjust start and end cols for wide characters if they affect their + // column indexes + if (char[CHAR_DATA_WIDTH_INDEX] === 0) { + if (startCol >= i) { + widthAdjustedStartCol--; + } + if (endCol >= i) { + widthAdjustedEndCol--; + } + } + } + + // Calculate the final end col by trimming whitespace on the right of the + // line if needed. + let finalEndCol = widthAdjustedEndCol || line.length; + if (trimRight) { + const rightWhitespaceIndex = lineString.search(/\s+$/); + if (rightWhitespaceIndex !== -1) { + finalEndCol = Math.min(finalEndCol, rightWhitespaceIndex); + } + // Return the empty string if only trimmed whitespace is selected + if (finalEndCol <= widthAdjustedStartCol) { + return ''; + } + } + + return lineString.substring(widthAdjustedStartCol, finalEndCol); + } } diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 5362cae9..defff541 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -945,6 +945,7 @@ export class InputHandler implements IInputHandler { case 47: // alt screen buffer case 1047: // alt screen buffer this._terminal.buffers.activateAltBuffer(); + this._terminal.selectionManager.setBuffer(this._terminal.buffer); this._terminal.viewport.syncScrollArea(); this._terminal.showCursor(); break; @@ -1113,7 +1114,7 @@ export class InputHandler implements IInputHandler { // if (params[0] === 1049) { // this.restoreCursor(params); // } - this._terminal.selectionManager.setBuffer(this._terminal.buffer.lines); + this._terminal.selectionManager.setBuffer(this._terminal.buffer); this._terminal.refresh(0, this._terminal.rows - 1); this._terminal.viewport.syncScrollArea(); this._terminal.showCursor(); diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 55317fbc..f97d9294 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -144,6 +144,7 @@ export interface IBuffer { scrollTop: number; savedY: number; savedX: number; + translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol?: number, endCol?: number): string; } export interface IBufferSet { @@ -166,7 +167,7 @@ export interface ISelectionManager { disable(): void; enable(): void; - setBuffer(buffer: ICircularList): void; + setBuffer(buffer: IBuffer): void; setSelection(row: number, col: number, length: number): void; } diff --git a/src/SelectionManager.test.ts b/src/SelectionManager.test.ts index 698cda7e..c7fd9aef 100644 --- a/src/SelectionManager.test.ts +++ b/src/SelectionManager.test.ts @@ -4,7 +4,7 @@ import jsdom = require('jsdom'); import { assert } from 'chai'; -import { ITerminal, ICircularList } from './Interfaces'; +import { ITerminal, ICircularList, IBuffer } from './Interfaces'; import { CharMeasure } from './utils/CharMeasure'; import { CircularList } from './utils/CircularList'; import { SelectionManager } from './SelectionManager'; @@ -16,7 +16,7 @@ import { LineData } from './Types'; class TestSelectionManager extends SelectionManager { constructor( terminal: ITerminal, - buffer: ICircularList, + buffer: IBuffer, rowContainer: HTMLElement, charMeasure: CharMeasure ) { @@ -40,7 +40,7 @@ describe('SelectionManager', () => { let document: Document; let terminal: ITerminal; - let bufferLines: ICircularList; + let buffer: IBuffer; let rowContainer: HTMLElement; let selectionManager: TestSelectionManager; @@ -55,8 +55,8 @@ describe('SelectionManager', () => { terminal.options.scrollback = 100; terminal.buffers = new BufferSet(terminal); terminal.buffer = terminal.buffers.active; - bufferLines = terminal.buffer.lines; - selectionManager = new TestSelectionManager(terminal, bufferLines, rowContainer, null); + buffer = terminal.buffer; + selectionManager = new TestSelectionManager(terminal, buffer, rowContainer, null); }); function stringToRow(text: string): LineData { @@ -69,7 +69,7 @@ describe('SelectionManager', () => { describe('_selectWordAt', () => { it('should expand selection for normal width chars', () => { - bufferLines.set(0, stringToRow('foo bar')); + buffer.lines.set(0, stringToRow('foo bar')); selectionManager.selectWordAt([0, 0]); assert.equal(selectionManager.selectionText, 'foo'); selectionManager.selectWordAt([1, 0]); @@ -86,7 +86,7 @@ describe('SelectionManager', () => { assert.equal(selectionManager.selectionText, 'bar'); }); it('should expand selection for whitespace', () => { - bufferLines.set(0, stringToRow('a b')); + buffer.lines.set(0, stringToRow('a b')); selectionManager.selectWordAt([0, 0]); assert.equal(selectionManager.selectionText, 'a'); selectionManager.selectWordAt([1, 0]); @@ -100,7 +100,7 @@ describe('SelectionManager', () => { }); it('should expand selection for wide characters', () => { // Wide characters use a special format - bufferLines.set(0, [ + buffer.lines.set(0, [ [null, '中', 2], [null, '', 0], [null, '文', 2], @@ -152,7 +152,7 @@ describe('SelectionManager', () => { assert.equal(selectionManager.selectionText, 'foo'); }); it('should select up to non-path characters that are commonly adjacent to paths', () => { - bufferLines.set(0, stringToRow('(cd)[ef]{gh}\'ij"')); + buffer.lines.set(0, stringToRow('(cd)[ef]{gh}\'ij"')); selectionManager.selectWordAt([0, 0]); assert.equal(selectionManager.selectionText, '(cd'); selectionManager.selectWordAt([1, 0]); @@ -190,7 +190,7 @@ describe('SelectionManager', () => { describe('_selectLineAt', () => { it('should select the entire line', () => { - bufferLines.set(0, stringToRow('foo bar')); + buffer.lines.set(0, stringToRow('foo bar')); selectionManager.selectLineAt(0); assert.equal(selectionManager.selectionText, 'foo bar', 'The selected text is correct'); assert.deepEqual(selectionManager.model.finalSelectionStart, [0, 0]); @@ -200,14 +200,14 @@ describe('SelectionManager', () => { describe('selectAll', () => { it('should select the entire buffer, beyond the viewport', () => { - bufferLines.length = 5; - bufferLines.set(0, stringToRow('1')); - bufferLines.set(1, stringToRow('2')); - bufferLines.set(2, stringToRow('3')); - bufferLines.set(3, stringToRow('4')); - bufferLines.set(4, stringToRow('5')); + buffer.lines.length = 5; + buffer.lines.set(0, stringToRow('1')); + buffer.lines.set(1, stringToRow('2')); + buffer.lines.set(2, stringToRow('3')); + buffer.lines.set(3, stringToRow('4')); + buffer.lines.set(4, stringToRow('5')); selectionManager.selectAll(); - terminal.buffer.ybase = bufferLines.length - terminal.rows; + terminal.buffer.ybase = buffer.lines.length - terminal.rows; assert.equal(selectionManager.selectionText, '1\n2\n3\n4\n5'); }); }); diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 781a8eda..f217e721 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -7,9 +7,8 @@ import * as Browser from './utils/Browser'; import { CharMeasure } from './utils/CharMeasure'; import { CircularList } from './utils/CircularList'; import { EventEmitter } from './EventEmitter'; -import { ITerminal, ICircularList, ISelectionManager } from './Interfaces'; +import { ITerminal, ICircularList, ISelectionManager, IBuffer } from './Interfaces'; import { SelectionModel } from './SelectionModel'; -import { translateBufferLineToString } from './utils/BufferLine'; import { LineData } from './Types'; /** @@ -102,7 +101,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager constructor( private _terminal: ITerminal, - private _buffer: ICircularList, + private _buffer: IBuffer, private _rowContainer: HTMLElement, private _charMeasure: CharMeasure ) { @@ -127,7 +126,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager // reverseIndex) and delete in a splice is only ever used when the same // number of elements was just added. Given this is could actually be // beneficial to leave the selection as is for these cases. - this._buffer.on('trim', (amount: number) => this._onTrim(amount)); + this._buffer.lines.on('trim', (amount: number) => this._onTrim(amount)); } /** @@ -151,7 +150,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager * switched in or out. * @param buffer The active buffer. */ - public setBuffer(buffer: ICircularList): void { + public setBuffer(buffer: IBuffer): void { this._buffer = buffer; this.clearSelection(); } @@ -184,12 +183,12 @@ export class SelectionManager extends EventEmitter implements ISelectionManager // Get first row const startRowEndCol = start[1] === end[1] ? end[0] : null; let result: string[] = []; - result.push(translateBufferLineToString(this._buffer.get(start[1]), true, start[0], startRowEndCol)); + result.push(this._buffer.translateBufferLineToString(start[1], true, start[0], startRowEndCol)); // Get middle rows for (let i = start[1] + 1; i <= end[1] - 1; i++) { - const bufferLine = this._buffer.get(i); - const lineText = translateBufferLineToString(bufferLine, true); + const bufferLine = this._buffer.lines.get(i); + const lineText = this._buffer.translateBufferLineToString(i, true); if ((bufferLine).isWrapped) { result[result.length - 1] += lineText; } else { @@ -199,8 +198,8 @@ export class SelectionManager extends EventEmitter implements ISelectionManager // Get final row if (start[1] !== end[1]) { - const bufferLine = this._buffer.get(end[1]); - const lineText = translateBufferLineToString(bufferLine, true, 0, end[0]); + const bufferLine = this._buffer.lines.get(end[1]); + const lineText = this._buffer.translateBufferLineToString(end[1], true, 0, end[0]); if ((bufferLine).isWrapped) { result[result.length - 1] += lineText; } else { @@ -413,7 +412,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager this._model.selectionEnd = null; // Ensure the line exists - const line = this._buffer.get(this._model.selectionStart[1]); + const line = this._buffer.lines.get(this._model.selectionStart[1]); if (!line) { return; } @@ -493,8 +492,8 @@ export class SelectionManager extends EventEmitter implements ISelectionManager // If the character is a wide character include the cell to the right in the // selection. Note that selections at the very end of the line will never // have a character. - if (this._model.selectionEnd[1] < this._buffer.length) { - const char = this._buffer.get(this._model.selectionEnd[1])[this._model.selectionEnd[0]]; + if (this._model.selectionEnd[1] < this._buffer.lines.length) { + const char = this._buffer.lines.get(this._model.selectionEnd[1])[this._model.selectionEnd[0]]; if (char && char[2] === 0) { this._model.selectionEnd[0]++; } @@ -562,12 +561,12 @@ export class SelectionManager extends EventEmitter implements ISelectionManager * @param coords The coordinates to get the word at. */ private _getWordAt(coords: [number, number]): IWordPosition { - const bufferLine = this._buffer.get(coords[1]); + const bufferLine = this._buffer.lines.get(coords[1]); if (!bufferLine) { return null; } - const line = translateBufferLineToString(bufferLine, false); + const line = this._buffer.translateBufferLineToString(coords[1], false); // Get actual index, taking into consideration wide characters let endIndex = this._convertViewportColToCharacterIndex(bufferLine, coords); diff --git a/src/Terminal.ts b/src/Terminal.ts index 4536733e..31eaef8a 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -28,7 +28,6 @@ import * as Browser from './utils/Browser'; import * as Mouse from './utils/Mouse'; import { CHARSETS } from './Charsets'; import { getRawByteCoords } from './utils/Mouse'; -import { translateBufferLineToString } from './utils/BufferLine'; import { CustomKeyEventHandler, Charset, LinkMatcherHandler, LinkMatcherValidationCallback, CharData, LineData, Option, StringOption, BooleanOption, StringArrayOption, NumberOption, GeometryOption, HandlerOption } from './Types'; import { ITerminal, IBrowser, ITerminalOptions, IInputHandlingTerminal, ILinkMatcherOptions } from './Interfaces'; @@ -379,7 +378,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT // Ensure the selection manager has the correct buffer if (this.selectionManager) { - this.selectionManager.setBuffer(this.buffer.lines); + this.selectionManager.setBuffer(this.buffer); } this.setupStops(); @@ -744,7 +743,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.viewport = new Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasure); this.renderer = new Renderer(this); - this.selectionManager = new SelectionManager(this, this.buffer.lines, this.rowContainer, this.charMeasure); + this.selectionManager = new SelectionManager(this, this.buffer, this.rowContainer, this.charMeasure); this.selectionManager.on('refresh', data => { this.renderer.refreshSelection(data.start, data.end); }); diff --git a/src/utils/BufferLine.ts b/src/utils/BufferLine.ts deleted file mode 100644 index 3dc6525a..00000000 --- a/src/utils/BufferLine.ts +++ /dev/null @@ -1,55 +0,0 @@ -/** - * @license MIT - */ - -// TODO: This module should be merged into a buffer or buffer line class - -const LINE_DATA_CHAR_INDEX = 1; -const LINE_DATA_WIDTH_INDEX = 2; - -/** - * Translates a buffer line to a string, with optional start and end columns. - * Wide characters will count as two columns in the resulting string. This - * function is useful for getting the actual text underneath the raw selection - * position. - * @param line The line being translated. - * @param trimRight Whether to trim whitespace to the right. - * @param startCol The column to start at. - * @param endCol The column to end at. - */ -export function translateBufferLineToString(line: any, trimRight: boolean, startCol: number = 0, endCol: number = null): string { - // Get full line - let lineString = ''; - let widthAdjustedStartCol = startCol; - let widthAdjustedEndCol = endCol; - for (let i = 0; i < line.length; i++) { - const char = line[i]; - lineString += char[LINE_DATA_CHAR_INDEX]; - // Adjust start and end cols for wide characters if they affect their - // column indexes - if (char[LINE_DATA_WIDTH_INDEX] === 0) { - if (startCol >= i) { - widthAdjustedStartCol--; - } - if (endCol >= i) { - widthAdjustedEndCol--; - } - } - } - - // Calculate the final end col by trimming whitespace on the right of the - // line if needed. - let finalEndCol = widthAdjustedEndCol || line.length; - if (trimRight) { - const rightWhitespaceIndex = lineString.search(/\s+$/); - if (rightWhitespaceIndex !== -1) { - finalEndCol = Math.min(finalEndCol, rightWhitespaceIndex); - } - // Return the empty string if only trimmed whitespace is selected - if (finalEndCol <= widthAdjustedStartCol) { - return ''; - } - } - - return lineString.substring(widthAdjustedStartCol, finalEndCol); -} diff --git a/src/utils/TestUtils.ts b/src/utils/TestUtils.ts index 159da055..fc785588 100644 --- a/src/utils/TestUtils.ts +++ b/src/utils/TestUtils.ts @@ -180,4 +180,7 @@ export class MockBuffer implements IBuffer { scrollTop: number; savedY: number; savedX: number; + translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol?: number, endCol?: number): string { + throw new Error('Method not implemented.'); + } } From fd61c8e8742da24c39867eeebf46e3f0ac6728d8 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 7 Aug 2017 15:37:22 -0700 Subject: [PATCH 033/378] Use a const for all refs to data char and width Part of #791 --- src/Buffer.ts | 4 +- src/InputHandler.ts | 11 +- src/Renderer.ts | 5 +- src/SelectionManager.ts | 20 +-- src/test/escape-sequences-test.js | 3 +- src/test/test.js | 236 +++++++++++++++--------------- 6 files changed, 140 insertions(+), 139 deletions(-) diff --git a/src/Buffer.ts b/src/Buffer.ts index ed3309b6..46fe6ba8 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -6,8 +6,8 @@ import { ITerminal, IBuffer } from './Interfaces'; import { CircularList } from './utils/CircularList'; import { LineData, CharData } from './Types'; -const CHAR_DATA_CHAR_INDEX = 1; -const CHAR_DATA_WIDTH_INDEX = 2; +export const CHAR_DATA_CHAR_INDEX = 1; +export const CHAR_DATA_WIDTH_INDEX = 2; /** * This class represents a terminal buffer (an internal state of the terminal), where the diff --git a/src/InputHandler.ts b/src/InputHandler.ts index defff541..1c54abef 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -6,6 +6,7 @@ import { IInputHandler, ITerminal, IInputHandlingTerminal } from './Interfaces'; import { C0 } from './EscapeSequences'; import { DEFAULT_CHARSET } from './Charsets'; import { CharData } from './Types'; +import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from './Buffer'; /** * The terminal's standard implementation of IInputHandler, this handles all @@ -34,14 +35,14 @@ export class InputHandler implements IInputHandler { if (!ch_width && this._terminal.buffer.x) { // dont overflow left if (this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1]) { - if (!this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][2]) { + if (!this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][CHAR_DATA_WIDTH_INDEX]) { // found empty cell after fullwidth, need to go 2 cells back if (this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 2]) - this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 2][1] += char; + this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 2][CHAR_DATA_CHAR_INDEX] += char; } else { - this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][1] += char; + this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][CHAR_DATA_CHAR_INDEX] += char; } this._terminal.updateRange(this._terminal.buffer.y); } @@ -77,9 +78,9 @@ export class InputHandler implements IInputHandler { // remove last cell, if it's width is 0 // we have to adjust the second last cell as well const removed = this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).pop(); - if (removed[2] === 0 + if (removed[CHAR_DATA_WIDTH_INDEX] === 0 && this._terminal.buffer.lines.get(row)[this._terminal.cols - 2] - && this._terminal.buffer.lines.get(row)[this._terminal.cols - 2][2] === 2) { + && this._terminal.buffer.lines.get(row)[this._terminal.cols - 2][CHAR_DATA_WIDTH_INDEX] === 2) { this._terminal.buffer.lines.get(row)[this._terminal.cols - 2] = [this._terminal.curAttr, ' ', 1]; } diff --git a/src/Renderer.ts b/src/Renderer.ts index f706bffc..f2999afa 100644 --- a/src/Renderer.ts +++ b/src/Renderer.ts @@ -4,6 +4,7 @@ import { ITerminal } from './Interfaces'; import { DomElementObjectPool } from './utils/DomElementObjectPool'; +import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from './Buffer'; /** * The maximum number of refresh frames to skip when the write buffer is non- @@ -168,8 +169,8 @@ export class Renderer { for (let i = 0; i < width; i++) { // TODO: Could data be a more specific type? let data: any = line[i][0]; - const ch = line[i][1]; - const ch_width: any = line[i][2]; + const ch = line[i][CHAR_DATA_CHAR_INDEX]; + const ch_width: any = line[i][CHAR_DATA_WIDTH_INDEX]; const isCursor: boolean = i === x; if (!ch_width) { continue; diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index f217e721..ee03f79a 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -10,6 +10,7 @@ import { EventEmitter } from './EventEmitter'; import { ITerminal, ICircularList, ISelectionManager, IBuffer } from './Interfaces'; import { SelectionModel } from './SelectionModel'; import { LineData } from './Types'; +import { CHAR_DATA_WIDTH_INDEX } from './Buffer'; /** * The number of pixels the mouse needs to be above or below the viewport in @@ -33,11 +34,6 @@ const DRAG_SCROLL_INTERVAL = 50; */ const WORD_SEPARATORS = ' ()[]{}\'"'; -// TODO: Move these constants elsewhere, they belong in a buffer or buffer -// data/line class. -const LINE_DATA_CHAR_INDEX = 1; -const LINE_DATA_WIDTH_INDEX = 2; - const NON_BREAKING_SPACE_CHAR = String.fromCharCode(160); const ALL_NON_BREAKING_SPACE_REGEX = new RegExp(NON_BREAKING_SPACE_CHAR, 'g'); @@ -420,7 +416,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager // If the mouse is over the second half of a wide character, adjust the // selection to cover the whole character const char = line[this._model.selectionStart[0]]; - if (char[LINE_DATA_WIDTH_INDEX] === 0) { + if (char[CHAR_DATA_WIDTH_INDEX] === 0) { this._model.selectionStart[0]++; } } @@ -494,7 +490,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager // have a character. if (this._model.selectionEnd[1] < this._buffer.lines.length) { const char = this._buffer.lines.get(this._model.selectionEnd[1])[this._model.selectionEnd[0]]; - if (char && char[2] === 0) { + if (char && char[CHAR_DATA_WIDTH_INDEX] === 0) { this._model.selectionEnd[0]++; } } @@ -541,7 +537,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager let charIndex = coords[0]; for (let i = 0; coords[0] >= i; i++) { const char = bufferLine[i]; - if (char[LINE_DATA_WIDTH_INDEX] === 0) { + if (char[CHAR_DATA_WIDTH_INDEX] === 0) { charIndex--; } } @@ -594,17 +590,17 @@ export class SelectionManager extends EventEmitter implements ISelectionManager let endCol = coords[0]; // Consider the initial position, skip it and increment the wide char // variable - if (bufferLine[startCol][LINE_DATA_WIDTH_INDEX] === 0) { + if (bufferLine[startCol][CHAR_DATA_WIDTH_INDEX] === 0) { leftWideCharCount++; startCol--; } - if (bufferLine[endCol][LINE_DATA_WIDTH_INDEX] === 2) { + if (bufferLine[endCol][CHAR_DATA_WIDTH_INDEX] === 2) { rightWideCharCount++; endCol++; } // Expand the string in both directions until a space is hit while (startIndex > 0 && !this._isCharWordSeparator(line.charAt(startIndex - 1))) { - if (bufferLine[startCol - 1][LINE_DATA_WIDTH_INDEX] === 0) { + if (bufferLine[startCol - 1][CHAR_DATA_WIDTH_INDEX] === 0) { // If the next character is a wide char, record it and skip the column leftWideCharCount++; startCol--; @@ -613,7 +609,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager startCol--; } while (endIndex + 1 < line.length && !this._isCharWordSeparator(line.charAt(endIndex + 1))) { - if (bufferLine[endCol + 1][LINE_DATA_WIDTH_INDEX] === 2) { + if (bufferLine[endCol + 1][CHAR_DATA_WIDTH_INDEX] === 2) { // If the next character is a wide char, record it and skip the column rightWideCharCount++; endCol++; diff --git a/src/test/escape-sequences-test.js b/src/test/escape-sequences-test.js index d0447451..51e5d017 100644 --- a/src/test/escape-sequences-test.js +++ b/src/test/escape-sequences-test.js @@ -3,6 +3,7 @@ var fs = require('fs'); var os = require('os'); var pty = require('node-pty'); var Terminal = require('../xterm'); +var CHAR_DATA_CHAR_INDEX = require('../Buffer').CHAR_DATA_CHAR_INDEX; if (os.platform() === 'win32') { // Skip tests on Windows since pty.open isn't supported @@ -65,7 +66,7 @@ function terminalToString(term) { for (var line = term.buffer.ybase; line < term.buffer.ybase + term.rows; line++) { line_s = ''; for (var cell=0; cell Date: Mon, 7 Aug 2017 22:15:13 -0700 Subject: [PATCH 034/378] addons/test.js and test.js -> Terminal.test.ts Part of #862 --- src/Interfaces.ts | 11 + src/Terminal.test.ts | 907 ++++++++++++++++++++++++++++++++++++++++ src/Terminal.ts | 42 +- src/Viewport.test.ts | 2 +- src/test/addons/test.js | 10 - src/test/test.js | 889 --------------------------------------- src/utils/TestUtils.ts | 36 +- 7 files changed, 974 insertions(+), 923 deletions(-) create mode 100644 src/Terminal.test.ts delete mode 100644 src/test/addons/test.js delete mode 100644 src/test/test.js diff --git a/src/Interfaces.ts b/src/Interfaces.ts index f97d9294..f4ae917f 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -158,6 +158,9 @@ export interface IBufferSet { export interface IViewport { syncScrollArea(): void; + onWheel(ev: WheelEvent): void; + onTouchStart(ev: TouchEvent): void; + onTouchMove(ev: TouchEvent): void; } export interface ISelectionManager { @@ -171,6 +174,14 @@ export interface ISelectionManager { setSelection(row: number, col: number, length: number): 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; diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts new file mode 100644 index 00000000..753733c2 --- /dev/null +++ b/src/Terminal.test.ts @@ -0,0 +1,907 @@ +import { assert, expect } from 'chai'; +import { Terminal } from './Terminal'; +import { MockViewport, MockCompositionHelper } from './utils/TestUtils'; +import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from './Buffer'; + +class TestTerminal extends Terminal { + public evaluateKeyEscapeSequence(ev: any): {cancel: boolean, key: string, scrollDisp: number} { return this._evaluateKeyEscapeSequence(ev); } + public keyDown(ev: any): boolean { return this._keyDown(ev); } + public keyPress(ev: any): boolean { return this._keyPress(ev); } +} + +describe('term.js addons', () => { + let term: TestTerminal; + + beforeEach(() => { + term = new TestTerminal(); + term.refresh = () => {}; + term.viewport = new MockViewport(); + (term).compositionHelper = new MockCompositionHelper(); + // Force synchronous writes + term.write = (data) => { + term.writeBuffer.push(data); + (term).innerWrite(); + }; + (term).element = { + classList: { + toggle: () => {}, + remove: () => {} + } + }; + }); + + it('should load addons with Terminal.loadAddon', () => { + Terminal.loadAddon('attach'); + // Test that addon was loaded successfully, adding attach to Terminal's + // prototype. + assert.equal(typeof (Terminal).prototype.attach, 'function'); + }); + + describe('getOption', () => { + it('should retrieve the option correctly', () => { + // In the `options` namespace. + term.options.cursorBlink = true; + assert.equal(term.getOption('cursorBlink'), true); + + // On the Terminal instance + delete term.options.cursorBlink; + term.options.cursorBlink = false; + assert.equal(term.getOption('cursorBlink'), false); + }); + it('should throw when retrieving a non-existant option', () => { + assert.throws(term.getOption.bind(term, 'fake', true)); + }); + }); + + describe('attachCustomKeyEventHandler', () => { + let evKeyDown = { + preventDefault: () => {}, + stopPropagation: () => {}, + type: 'keydown', + keyCode: 77 + }; + let evKeyPress = { + preventDefault: () => {}, + stopPropagation: () => {}, + type: 'keypress', + keyCode: 77 + }; + + beforeEach(() => { + term.handler = () => {}; + term.showCursor = () => {}; + term.clearSelection = () => {}; + }); + + it('should process the keydown/keypress event based on what the handler returns', () => { + assert.equal(term.keyDown(evKeyDown), true); + assert.equal(term.keyPress(evKeyPress), true); + term.attachCustomKeyEventHandler(ev => ev.keyCode === 77); + assert.equal(term.keyDown(evKeyDown), true); + assert.equal(term.keyPress(evKeyPress), true); + term.attachCustomKeyEventHandler(ev => ev.keyCode !== 77); + assert.equal(term.keyDown(evKeyDown), false); + assert.equal(term.keyPress(evKeyPress), false); + }); + + it('should alive after reset(ESC c Full Reset (RIS))', () => { + term.attachCustomKeyEventHandler(ev => ev.keyCode !== 77); + assert.equal(term.keyDown(evKeyDown), false); + assert.equal(term.keyPress(evKeyPress), false); + term.reset(); + assert.equal(term.keyDown(evKeyDown), false); + assert.equal(term.keyPress(evKeyPress), false); + }); + }); + + describe('setOption', () => { + let originalWarn; + let warnCallCount; + beforeEach(() => { + originalWarn = console.warn; + warnCallCount = 0; + console.warn = () => warnCallCount++; + }); + afterEach(() => { + console.warn = originalWarn; + }); + it('should set the option correctly', () => { + term.setOption('cursorBlink', true); + assert.equal(term.options.cursorBlink, true); + term.setOption('cursorBlink', false); + assert.equal(term.options.cursorBlink, false); + }); + it('should throw when setting a non-existant option', () => { + assert.throws(term.setOption.bind(term, 'fake', true)); + }); + it('should warn and do nothing when scrollback is less than number of rows', () => { + term.setOption('scrollback', term.rows - 1); + assert.equal(term.getOption('scrollback'), 1000); + assert.equal(warnCallCount, 1); + }); + }); + + describe('clear', () => { + it('should clear a buffer equal to rows', () => { + let promptLine = term.buffer.lines.get(term.buffer.ybase + term.buffer.y); + term.clear(); + assert.equal(term.buffer.y, 0); + assert.equal(term.buffer.ybase, 0); + assert.equal(term.buffer.ydisp, 0); + assert.equal(term.buffer.lines.length, term.rows); + assert.deepEqual(term.buffer.lines.get(0), promptLine); + for (let i = 1; i < term.rows; i++) { + assert.deepEqual(term.buffer.lines.get(i), term.blankLine()); + } + }); + it('should clear a buffer larger than rows', () => { + // Fill the buffer with dummy rows + for (let i = 0; i < term.rows * 2; i++) { + term.write('test\n'); + } + + let promptLine = term.buffer.lines.get(term.buffer.ybase + term.buffer.y); + term.clear(); + assert.equal(term.buffer.y, 0); + assert.equal(term.buffer.ybase, 0); + assert.equal(term.buffer.ydisp, 0); + assert.equal(term.buffer.lines.length, term.rows); + assert.deepEqual(term.buffer.lines.get(0), promptLine); + for (let i = 1; i < term.rows; i++) { + assert.deepEqual(term.buffer.lines.get(i), term.blankLine()); + } + }); + it('should not break the prompt when cleared twice', () => { + let promptLine = term.buffer.lines.get(term.buffer.ybase + term.buffer.y); + term.clear(); + term.clear(); + assert.equal(term.buffer.y, 0); + assert.equal(term.buffer.ybase, 0); + assert.equal(term.buffer.ydisp, 0); + assert.equal(term.buffer.lines.length, term.rows); + assert.deepEqual(term.buffer.lines.get(0), promptLine); + for (let i = 1; i < term.rows; i++) { + assert.deepEqual(term.buffer.lines.get(i), term.blankLine()); + } + }); + }); + + describe('scroll', () => { + describe('scrollDisp', () => { + let startYDisp; + beforeEach(() => { + for (let i = 0; i < term.rows * 2; i++) { + term.writeln('test'); + } + startYDisp = term.rows + 1; + }); + it('should scroll a single line', () => { + assert.equal(term.buffer.ydisp, startYDisp); + term.scrollDisp(-1); + assert.equal(term.buffer.ydisp, startYDisp - 1); + term.scrollDisp(1); + assert.equal(term.buffer.ydisp, startYDisp); + }); + it('should scroll multiple lines', () => { + assert.equal(term.buffer.ydisp, startYDisp); + term.scrollDisp(-5); + assert.equal(term.buffer.ydisp, startYDisp - 5); + term.scrollDisp(5); + assert.equal(term.buffer.ydisp, startYDisp); + }); + it('should not scroll beyond the bounds of the buffer', () => { + assert.equal(term.buffer.ydisp, startYDisp); + term.scrollDisp(1); + assert.equal(term.buffer.ydisp, startYDisp); + for (let i = 0; i < startYDisp; i++) { + term.scrollDisp(-1); + } + assert.equal(term.buffer.ydisp, 0); + term.scrollDisp(-1); + assert.equal(term.buffer.ydisp, 0); + }); + }); + + describe('scrollPages', () => { + let startYDisp; + beforeEach(() => { + for (let i = 0; i < term.rows * 3; i++) { + term.writeln('test'); + } + startYDisp = (term.rows * 2) + 1; + }); + it('should scroll a single page', () => { + assert.equal(term.buffer.ydisp, startYDisp); + term.scrollPages(-1); + assert.equal(term.buffer.ydisp, startYDisp - (term.rows - 1)); + term.scrollPages(1); + assert.equal(term.buffer.ydisp, startYDisp); + }); + it('should scroll a multiple pages', () => { + assert.equal(term.buffer.ydisp, startYDisp); + term.scrollPages(-2); + assert.equal(term.buffer.ydisp, startYDisp - (term.rows - 1) * 2); + term.scrollPages(2); + assert.equal(term.buffer.ydisp, startYDisp); + }); + }); + + describe('scrollToTop', () => { + beforeEach(() => { + for (let i = 0; i < term.rows * 3; i++) { + term.writeln('test'); + } + }); + it('should scroll to the top', () => { + assert.notEqual(term.buffer.ydisp, 0); + term.scrollToTop(); + assert.equal(term.buffer.ydisp, 0); + }); + }); + + describe('scrollToBottom', () => { + let startYDisp; + beforeEach(() => { + for (let i = 0; i < term.rows * 3; i++) { + term.writeln('test'); + } + startYDisp = (term.rows * 2) + 1; + }); + it('should scroll to the bottom', () => { + term.scrollDisp(-1); + term.scrollToBottom(); + assert.equal(term.buffer.ydisp, startYDisp); + term.scrollPages(-1); + term.scrollToBottom(); + assert.equal(term.buffer.ydisp, startYDisp); + term.scrollToTop(); + term.scrollToBottom(); + assert.equal(term.buffer.ydisp, startYDisp); + }); + }); + + describe('keyDown', () => { + it('should scroll down, when a key is pressed and terminal is scrolled up', () => { + // Override _evaluateKeyEscapeSequence to return cancel code + (term)._evaluateKeyEscapeSequence = () => { + return { key: 'a' }; + }; + let event = { + type: 'keydown', + keyCode: 0, + preventDefault: () => {}, + stopPropagation: () => {} + }; + + term.buffer.ydisp = 0; + term.buffer.ybase = 40; + term.keyDown(event); + + // Ensure that now the terminal is scrolled to bottom + assert.equal(term.buffer.ydisp, term.buffer.ybase); + }); + + it('should not scroll down, when a custom keydown handler prevents the event', () => { + // Add some output to the terminal + for (let i = 0; i < term.rows * 3; i++) { + term.writeln('test'); + } + let startYDisp = (term.rows * 2) + 1; + term.attachCustomKeyEventHandler(() => { + return false; + }); + + assert.equal(term.buffer.ydisp, startYDisp); + term.scrollDisp(-1); + assert.equal(term.buffer.ydisp, startYDisp - 1); + term.keyDown({ keyCode: 0 }); + assert.equal(term.buffer.ydisp, startYDisp - 1); + }); + }); + }); + + describe('evaluateKeyEscapeSequence', () => { + it('should return the correct escape sequence for unmodified keys', () => { + // Backspace + assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 8 }).key, '\x7f'); // ^? + // Tab + assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 9 }).key, '\t'); + // Return/enter + assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 13 }).key, '\r'); // CR + // Escape + assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 27 }).key, '\x1b'); + // Page up, page down + assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 33 }).key, '\x1b[5~'); // CSI 5 ~ + assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 34 }).key, '\x1b[6~'); // CSI 6 ~ + // End, Home + assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 35 }).key, '\x1b[F'); // SS3 F + assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 36 }).key, '\x1b[H'); // SS3 H + // Left, up, right, down arrows + assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 37 }).key, '\x1b[D'); // CSI D + assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 38 }).key, '\x1b[A'); // CSI A + assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 39 }).key, '\x1b[C'); // CSI C + assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 40 }).key, '\x1b[B'); // CSI B + // Insert + assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 45 }).key, '\x1b[2~'); // CSI 2 ~ + // Delete + assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 46 }).key, '\x1b[3~'); // CSI 3 ~ + // F1-F12 + assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 112 }).key, '\x1bOP'); // SS3 P + assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 113 }).key, '\x1bOQ'); // SS3 Q + assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 114 }).key, '\x1bOR'); // SS3 R + assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 115 }).key, '\x1bOS'); // SS3 S + assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 116 }).key, '\x1b[15~'); // CSI 1 5 ~ + assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 117 }).key, '\x1b[17~'); // CSI 1 7 ~ + assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 118 }).key, '\x1b[18~'); // CSI 1 8 ~ + assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 119 }).key, '\x1b[19~'); // CSI 1 9 ~ + assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 120 }).key, '\x1b[20~'); // CSI 2 0 ~ + assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 121 }).key, '\x1b[21~'); // CSI 2 1 ~ + assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 122 }).key, '\x1b[23~'); // CSI 2 3 ~ + assert.equal(term.evaluateKeyEscapeSequence({ keyCode: 123 }).key, '\x1b[24~'); // CSI 2 4 ~ + }); + it('should return \\x1b[3;5~ for ctrl+delete', () => { + assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 46 }).key, '\x1b[3;5~'); + }); + it('should return \\x1b[3;2~ for shift+delete', () => { + assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 46 }).key, '\x1b[3;2~'); + }); + it('should return \\x1b[3;3~ for alt+delete', () => { + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 46 }).key, '\x1b[3;3~'); + }); + it('should return \\x1b[5D for ctrl+left', () => { + assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 37 }).key, '\x1b[1;5D'); // CSI 5 D + }); + it('should return \\x1b[5C for ctrl+right', () => { + assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 39 }).key, '\x1b[1;5C'); // CSI 5 C + }); + it('should return \\x1b[5A for ctrl+up', () => { + assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 38 }).key, '\x1b[1;5A'); // CSI 5 A + }); + it('should return \\x1b[5B for ctrl+down', () => { + assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 40 }).key, '\x1b[1;5B'); // CSI 5 B + }); + + describe('On non-macOS platforms', () => { + beforeEach(() => { + term.browser.isMac = false; + }); + // Evalueate alt + arrow key movement, which is a feature of terminal emulators but not VT100 + // http://unix.stackexchange.com/a/108106 + it('should return \\x1b[5D for alt+left', () => { + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 37 }).key, '\x1b[1;5D'); // CSI 5 D + }); + it('should return \\x1b[5C for alt+right', () => { + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 39 }).key, '\x1b[1;5C'); // CSI 5 C + }); + }); + + describe('On macOS platforms', () => { + beforeEach(() => { + term.browser.isMac = true; + }); + it('should return \\x1bb for alt+left', () => { + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 37 }).key, '\x1bb'); // CSI 5 D + }); + it('should return \\x1bf for alt+right', () => { + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 39 }).key, '\x1bf'); // CSI 5 C + }); + }); + + it('should return \\x1b[5A for alt+up', () => { + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 38 }).key, '\x1b[1;5A'); // CSI 5 A + }); + it('should return \\x1b[5B for alt+down', () => { + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 40 }).key, '\x1b[1;5B'); // CSI 5 B + }); + it('should return the correct escape sequence for modified F1-F12 keys', () => { + assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 112 }).key, '\x1b[1;2P'); + assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 113 }).key, '\x1b[1;2Q'); + assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 114 }).key, '\x1b[1;2R'); + assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 115 }).key, '\x1b[1;2S'); + assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 116 }).key, '\x1b[15;2~'); + assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 117 }).key, '\x1b[17;2~'); + assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 118 }).key, '\x1b[18;2~'); + assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 119 }).key, '\x1b[19;2~'); + assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 120 }).key, '\x1b[20;2~'); + assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 121 }).key, '\x1b[21;2~'); + assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 122 }).key, '\x1b[23;2~'); + assert.equal(term.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 123 }).key, '\x1b[24;2~'); + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 112 }).key, '\x1b[1;3P'); + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 113 }).key, '\x1b[1;3Q'); + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 114 }).key, '\x1b[1;3R'); + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 115 }).key, '\x1b[1;3S'); + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 116 }).key, '\x1b[15;3~'); + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 117 }).key, '\x1b[17;3~'); + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 118 }).key, '\x1b[18;3~'); + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 119 }).key, '\x1b[19;3~'); + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 120 }).key, '\x1b[20;3~'); + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 121 }).key, '\x1b[21;3~'); + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 122 }).key, '\x1b[23;3~'); + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, keyCode: 123 }).key, '\x1b[24;3~'); + + assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 112 }).key, '\x1b[1;5P'); + assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 113 }).key, '\x1b[1;5Q'); + assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 114 }).key, '\x1b[1;5R'); + assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 115 }).key, '\x1b[1;5S'); + assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 116 }).key, '\x1b[15;5~'); + assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 117 }).key, '\x1b[17;5~'); + assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 118 }).key, '\x1b[18;5~'); + assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 119 }).key, '\x1b[19;5~'); + assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 120 }).key, '\x1b[20;5~'); + assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 121 }).key, '\x1b[21;5~'); + assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 122 }).key, '\x1b[23;5~'); + assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 123 }).key, '\x1b[24;5~'); + }); + }); + + describe('Third level shift', () => { + let evKeyDown; + let evKeyPress; + + beforeEach(() => { + term.handler = () => {}; + term.showCursor = () => {}; + term.clearSelection = () => {}; + // term.compositionHelper = { + // isComposing: false, + // keydown: { + // bind: () => { + // return () => { return true; }; + // } + // } + // }; + evKeyDown = { + preventDefault: () => {}, + stopPropagation: () => {}, + type: 'keydown', + altKey: null, + keyCode: null + }; + evKeyPress = { + preventDefault: () => {}, + stopPropagation: () => {}, + type: 'keypress', + altKey: null, + charCode: null, + keyCode: null + }; + }); + + describe('On Mac OS', () => { + beforeEach(() => { + term.browser.isMac = true; + }); + + it('should not interfere with the alt key on keyDown', () => { + evKeyDown.altKey = true; + evKeyDown.keyCode = 81; + assert.equal(term.keyDown(evKeyDown), true); + evKeyDown.altKey = true; + evKeyDown.keyCode = 192; + assert.equal(term.keyDown(evKeyDown), true); + }); + + it('should interefere with the alt + arrow keys', () => { + evKeyDown.altKey = true; + evKeyDown.keyCode = 37; + assert.equal(term.keyDown(evKeyDown), false); + evKeyDown.altKey = true; + evKeyDown.keyCode = 39; + assert.equal(term.keyDown(evKeyDown), false); + }); + + it('should emit key with alt + key on keyPress', (done) => { + let keys = ['@', '@', '\\', '\\', '|', '|']; + + term.on('keypress', (key) => { + if (key) { + let index = keys.indexOf(key); + assert(index !== -1, 'Emitted wrong key: ' + key); + keys.splice(index, 1); + } + if (keys.length === 0) done(); + }); + + evKeyPress.altKey = true; + // @ + evKeyPress.charCode = null; + evKeyPress.keyCode = 64; + term.keyPress(evKeyPress); + // Firefox @ + evKeyPress.charCode = 64; + evKeyPress.keyCode = 0; + term.keyPress(evKeyPress); + // \ + evKeyPress.charCode = null; + evKeyPress.keyCode = 92; + term.keyPress(evKeyPress); + // Firefox \ + evKeyPress.charCode = 92; + evKeyPress.keyCode = 0; + term.keyPress(evKeyPress); + // | + evKeyPress.charCode = null; + evKeyPress.keyCode = 124; + term.keyPress(evKeyPress); + // Firefox | + evKeyPress.charCode = 124; + evKeyPress.keyCode = 0; + term.keyPress(evKeyPress); + }); + }); + + describe('On MS Windows', () => { + beforeEach(() => { + term.browser.isMSWindows = true; + }); + + it('should not interfere with the alt + ctrl key on keyDown', () => { + evKeyPress.altKey = true; + evKeyPress.ctrlKey = true; + evKeyPress.keyCode = 81; + assert.equal(term.keyDown(evKeyPress), true); + evKeyDown.altKey = true; + evKeyDown.ctrlKey = true; + evKeyDown.keyCode = 81; + assert.equal(term.keyDown(evKeyDown), true); + }); + + it('should interefere with the alt + ctrl + arrow keys', () => { + evKeyDown.altKey = true; + evKeyDown.ctrlKey = true; + + evKeyDown.keyCode = 37; + assert.equal(term.keyDown(evKeyDown), false); + evKeyDown.keyCode = 39; + assert.equal(term.keyDown(evKeyDown), false); + }); + + it('should emit key with alt + ctrl + key on keyPress', (done) => { + let keys = ['@', '@', '\\', '\\', '|', '|']; + + term.on('keypress', (key) => { + if (key) { + let index = keys.indexOf(key); + assert(index !== -1, 'Emitted wrong key: ' + key); + keys.splice(index, 1); + } + if (keys.length === 0) done(); + }); + + evKeyPress.altKey = true; + evKeyPress.ctrlKey = true; + + // @ + evKeyPress.charCode = null; + evKeyPress.keyCode = 64; + term.keyPress(evKeyPress); + // Firefox @ + evKeyPress.charCode = 64; + evKeyPress.keyCode = 0; + term.keyPress(evKeyPress); + // \ + evKeyPress.charCode = null; + evKeyPress.keyCode = 92; + term.keyPress(evKeyPress); + // Firefox \ + evKeyPress.charCode = 92; + evKeyPress.keyCode = 0; + term.keyPress(evKeyPress); + // | + evKeyPress.charCode = null; + evKeyPress.keyCode = 124; + term.keyPress(evKeyPress); + // Firefox | + evKeyPress.charCode = 124; + evKeyPress.keyCode = 0; + term.keyPress(evKeyPress); + }); + }); + }); + + describe('unicode - surrogates', () => { + it('2 characters per cell', function (): void { + this.timeout(10000); // This is needed because istanbul patches code and slows it down + let high = String.fromCharCode(0xD800); + for (let i = 0xDC00; i <= 0xDCFF; ++i) { + term.write(high + String.fromCharCode(i)); + let tchar = term.buffer.lines.get(0)[0]; + expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(high + String.fromCharCode(i)); + expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2); + expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1); + expect(term.buffer.lines.get(0)[1][CHAR_DATA_CHAR_INDEX]).eql(' '); + term.reset(); + } + }); + it('2 characters at last cell', () => { + let high = String.fromCharCode(0xD800); + for (let i = 0xDC00; i <= 0xDCFF; ++i) { + term.buffer.x = term.cols - 1; + term.write(high + String.fromCharCode(i)); + expect(term.buffer.lines.get(0)[term.buffer.x - 1][CHAR_DATA_CHAR_INDEX]).eql(high + String.fromCharCode(i)); + expect(term.buffer.lines.get(0)[term.buffer.x - 1][CHAR_DATA_CHAR_INDEX].length).eql(2); + expect(term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX]).eql(' '); + term.reset(); + } + }); + it('2 characters per cell over line end with autowrap', () => { + let high = String.fromCharCode(0xD800); + for (let i = 0xDC00; i <= 0xDCFF; ++i) { + term.buffer.x = term.cols - 1; + term.wraparoundMode = true; + term.write('a' + high + String.fromCharCode(i)); + expect(term.buffer.lines.get(0)[term.cols - 1][CHAR_DATA_CHAR_INDEX]).eql('a'); + expect(term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX]).eql(high + String.fromCharCode(i)); + expect(term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX].length).eql(2); + expect(term.buffer.lines.get(1)[1][CHAR_DATA_CHAR_INDEX]).eql(' '); + term.reset(); + } + }); + it('2 characters per cell over line end without autowrap', () => { + let high = String.fromCharCode(0xD800); + for (let i = 0xDC00; i <= 0xDCFF; ++i) { + term.buffer.x = term.cols - 1; + term.wraparoundMode = false; + term.write('a' + high + String.fromCharCode(i)); + // auto wraparound mode should cut off the rest of the line + expect(term.buffer.lines.get(0)[term.cols - 1][CHAR_DATA_CHAR_INDEX]).eql('a'); + expect(term.buffer.lines.get(0)[term.cols - 1][CHAR_DATA_CHAR_INDEX].length).eql(1); + expect(term.buffer.lines.get(1)[1][CHAR_DATA_CHAR_INDEX]).eql(' '); + term.reset(); + } + }); + it('splitted surrogates', () => { + let high = String.fromCharCode(0xD800); + for (let i = 0xDC00; i <= 0xDCFF; ++i) { + term.write(high); + term.write(String.fromCharCode(i)); + let tchar = term.buffer.lines.get(0)[0]; + expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(high + String.fromCharCode(i)); + expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2); + expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1); + expect(term.buffer.lines.get(0)[1][CHAR_DATA_CHAR_INDEX]).eql(' '); + term.reset(); + } + }); + }); + + describe('unicode - combining characters', () => { + it('café', () => { + term.write('cafe\u0301'); + expect(term.buffer.lines.get(0)[3][CHAR_DATA_CHAR_INDEX]).eql('e\u0301'); + expect(term.buffer.lines.get(0)[3][CHAR_DATA_CHAR_INDEX].length).eql(2); + expect(term.buffer.lines.get(0)[3][CHAR_DATA_WIDTH_INDEX]).eql(1); + }); + it('café - end of line', () => { + term.buffer.x = term.cols - 1 - 3; + term.write('cafe\u0301'); + expect(term.buffer.lines.get(0)[term.cols - 1][CHAR_DATA_CHAR_INDEX]).eql('e\u0301'); + expect(term.buffer.lines.get(0)[term.cols - 1][CHAR_DATA_CHAR_INDEX].length).eql(2); + expect(term.buffer.lines.get(0)[term.cols - 1][CHAR_DATA_WIDTH_INDEX]).eql(1); + expect(term.buffer.lines.get(0)[1][CHAR_DATA_CHAR_INDEX]).eql(' '); + expect(term.buffer.lines.get(0)[1][CHAR_DATA_CHAR_INDEX].length).eql(1); + expect(term.buffer.lines.get(0)[1][CHAR_DATA_WIDTH_INDEX]).eql(1); + }); + it('multiple combined é', () => { + term.wraparoundMode = true; + term.write(Array(100).join('e\u0301')); + for (let i = 0; i < term.cols; ++i) { + let tchar = term.buffer.lines.get(0)[i]; + expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('e\u0301'); + expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2); + expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1); + } + let tchar = term.buffer.lines.get(1)[0]; + expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('e\u0301'); + expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2); + expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1); + }); + it('multiple surrogate with combined', () => { + term.wraparoundMode = true; + term.write(Array(100).join('\uD800\uDC00\u0301')); + for (let i = 0; i < term.cols; ++i) { + let tchar = term.buffer.lines.get(0)[i]; + expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\uD800\uDC00\u0301'); + expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(3); + expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1); + } + let tchar = term.buffer.lines.get(1)[0]; + expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\uD800\uDC00\u0301'); + expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(3); + expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1); + }); + }); + + describe('unicode - fullwidth characters', () => { + it('cursor movement even', () => { + expect(term.buffer.x).eql(0); + term.write('¥'); + expect(term.buffer.x).eql(2); + }); + it('cursor movement odd', () => { + term.buffer.x = 1; + expect(term.buffer.x).eql(1); + term.write('¥'); + expect(term.buffer.x).eql(3); + }); + it('line of ¥ even', () => { + term.wraparoundMode = true; + term.write(Array(50).join('¥')); + for (let i = 0; i < term.cols; ++i) { + let tchar = term.buffer.lines.get(0)[i]; + if (i % 2) { + expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(''); + expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0); + expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(0); + } else { + expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥'); + expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1); + expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2); + } + } + let tchar = term.buffer.lines.get(1)[0]; + expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥'); + expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1); + expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2); + }); + it('line of ¥ odd', () => { + term.wraparoundMode = true; + term.buffer.x = 1; + term.write(Array(50).join('¥')); + for (let i = 1; i < term.cols - 1; ++i) { + let tchar = term.buffer.lines.get(0)[i]; + if (!(i % 2)) { + expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(''); + expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0); + expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(0); + } else { + expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥'); + expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1); + expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2); + } + } + let tchar = term.buffer.lines.get(0)[term.cols - 1]; + expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(' '); + expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1); + expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1); + tchar = term.buffer.lines.get(1)[0]; + expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥'); + expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1); + expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2); + }); + it('line of ¥ with combining odd', () => { + term.wraparoundMode = true; + term.buffer.x = 1; + term.write(Array(50).join('¥\u0301')); + for (let i = 1; i < term.cols - 1; ++i) { + let tchar = term.buffer.lines.get(0)[i]; + if (!(i % 2)) { + expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(''); + expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0); + expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(0); + } else { + expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥\u0301'); + expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2); + expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2); + } + } + let tchar = term.buffer.lines.get(0)[term.cols - 1]; + expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(' '); + expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1); + expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1); + tchar = term.buffer.lines.get(1)[0]; + expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥\u0301'); + expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2); + expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2); + }); + it('line of ¥ with combining even', () => { + term.wraparoundMode = true; + term.write(Array(50).join('¥\u0301')); + for (let i = 0; i < term.cols; ++i) { + let tchar = term.buffer.lines.get(0)[i]; + if (i % 2) { + expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(''); + expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0); + expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(0); + } else { + expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥\u0301'); + expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2); + expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2); + } + } + let tchar = term.buffer.lines.get(1)[0]; + expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥\u0301'); + expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2); + expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2); + }); + it('line of surrogate fullwidth with combining odd', () => { + term.wraparoundMode = true; + term.buffer.x = 1; + term.write(Array(50).join('\ud843\ude6d\u0301')); + for (let i = 1; i < term.cols - 1; ++i) { + let tchar = term.buffer.lines.get(0)[i]; + if (!(i % 2)) { + expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(''); + expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0); + expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(0); + } else { + expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\ud843\ude6d\u0301'); + expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(3); + expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2); + } + } + let tchar = term.buffer.lines.get(0)[term.cols - 1]; + expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(' '); + expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1); + expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1); + tchar = term.buffer.lines.get(1)[0]; + expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\ud843\ude6d\u0301'); + expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(3); + expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2); + }); + it('line of surrogate fullwidth with combining even', () => { + term.wraparoundMode = true; + term.write(Array(50).join('\ud843\ude6d\u0301')); + for (let i = 0; i < term.cols; ++i) { + let tchar = term.buffer.lines.get(0)[i]; + if (i % 2) { + expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(''); + expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0); + expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(0); + } else { + expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\ud843\ude6d\u0301'); + expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(3); + expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2); + } + } + let tchar = term.buffer.lines.get(1)[0]; + expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\ud843\ude6d\u0301'); + expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(3); + expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2); + }); + }); + + describe('insert mode', () => { + it('halfwidth - all', () => { + term.write(Array(9).join('0123456789').slice(-80)); + term.buffer.x = 10; + term.buffer.y = 0; + term.insertMode = true; + term.write('abcde'); + expect(term.buffer.lines.get(0).length).eql(term.cols); + expect(term.buffer.lines.get(0)[10][CHAR_DATA_CHAR_INDEX]).eql('a'); + expect(term.buffer.lines.get(0)[14][CHAR_DATA_CHAR_INDEX]).eql('e'); + expect(term.buffer.lines.get(0)[15][CHAR_DATA_CHAR_INDEX]).eql('0'); + expect(term.buffer.lines.get(0)[79][CHAR_DATA_CHAR_INDEX]).eql('4'); + }); + it('fullwidth - insert', () => { + term.write(Array(9).join('0123456789').slice(-80)); + term.buffer.x = 10; + term.buffer.y = 0; + term.insertMode = true; + term.write('¥¥¥'); + expect(term.buffer.lines.get(0).length).eql(term.cols); + expect(term.buffer.lines.get(0)[10][CHAR_DATA_CHAR_INDEX]).eql('¥'); + expect(term.buffer.lines.get(0)[11][CHAR_DATA_CHAR_INDEX]).eql(''); + expect(term.buffer.lines.get(0)[14][CHAR_DATA_CHAR_INDEX]).eql('¥'); + expect(term.buffer.lines.get(0)[15][CHAR_DATA_CHAR_INDEX]).eql(''); + expect(term.buffer.lines.get(0)[79][CHAR_DATA_CHAR_INDEX]).eql('3'); + }); + it('fullwidth - right border', () => { + term.write(Array(41).join('¥')); + term.buffer.x = 10; + term.buffer.y = 0; + term.insertMode = true; + term.write('a'); + expect(term.buffer.lines.get(0).length).eql(term.cols); + expect(term.buffer.lines.get(0)[10][CHAR_DATA_CHAR_INDEX]).eql('a'); + expect(term.buffer.lines.get(0)[11][CHAR_DATA_CHAR_INDEX]).eql('¥'); + expect(term.buffer.lines.get(0)[79][CHAR_DATA_CHAR_INDEX]).eql(' '); // fullwidth char got replaced + term.write('b'); + expect(term.buffer.lines.get(0).length).eql(term.cols); + expect(term.buffer.lines.get(0)[11][CHAR_DATA_CHAR_INDEX]).eql('b'); + expect(term.buffer.lines.get(0)[12][CHAR_DATA_CHAR_INDEX]).eql('¥'); + expect(term.buffer.lines.get(0)[79][CHAR_DATA_CHAR_INDEX]).eql(''); // empty cell after fullwidth + }); + }); +}); diff --git a/src/Terminal.ts b/src/Terminal.ts index 31eaef8a..a7b4b336 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -29,7 +29,7 @@ import * as Mouse from './utils/Mouse'; import { CHARSETS } from './Charsets'; import { getRawByteCoords } from './utils/Mouse'; import { CustomKeyEventHandler, Charset, LinkMatcherHandler, LinkMatcherValidationCallback, CharData, LineData, Option, StringOption, BooleanOption, StringArrayOption, NumberOption, GeometryOption, HandlerOption } from './Types'; -import { ITerminal, IBrowser, ITerminalOptions, IInputHandlingTerminal, ILinkMatcherOptions } from './Interfaces'; +import { ITerminal, IBrowser, ITerminalOptions, IInputHandlingTerminal, ILinkMatcherOptions, IViewport, ICompositionHelper } from './Interfaces'; // Declare for RequireJS in loadAddon declare var define: any; @@ -259,8 +259,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT private linkifier: Linkifier; public buffers: BufferSet; public buffer: Buffer; - public viewport: Viewport; - private compositionHelper: CompositionHelper; + public viewport: IViewport; + private compositionHelper: ICompositionHelper; public charMeasure: CharMeasure; public cols: number; @@ -601,14 +601,14 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT if (document.activeElement !== this) { return; } - self.keyDown(ev); + self._keyDown(ev); }, true); on(this.element, 'keypress', function (ev: KeyboardEvent): void { if (document.activeElement !== this) { return; } - self.keyPress(ev); + self._keyPress(ev); }, true); on(this.element, 'keyup', (ev: KeyboardEvent) => { @@ -618,19 +618,19 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT }, true); on(this.textarea, 'keydown', (ev: KeyboardEvent) => { - this.keyDown(ev); + this._keyDown(ev); }, true); on(this.textarea, 'keypress', (ev: KeyboardEvent) => { - this.keyPress(ev); + this._keyPress(ev); // Truncate the textarea's value, since it is not needed this.textarea.value = ''; }, true); - on(this.textarea, 'compositionstart', this.compositionHelper.compositionstart.bind(this.compositionHelper)); - on(this.textarea, 'compositionupdate', this.compositionHelper.compositionupdate.bind(this.compositionHelper)); - on(this.textarea, 'compositionend', this.compositionHelper.compositionend.bind(this.compositionHelper)); - this.on('refresh', this.compositionHelper.updateCompositionElements.bind(this.compositionHelper)); + on(this.textarea, 'compositionstart', () => this.compositionHelper.compositionstart()); + on(this.textarea, 'compositionupdate', (e: CompositionEvent) => this.compositionHelper.compositionupdate(e)); + on(this.textarea, 'compositionend', () => this.compositionHelper.compositionend()); + this.on('refresh', () => this.compositionHelper.updateCompositionElements()); this.on('refresh', (data) => this.queueLinkification(data.start, data.end)); } @@ -1474,21 +1474,21 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent * @param {KeyboardEvent} ev The keydown event to be handled. */ - private keyDown(ev: KeyboardEvent): boolean { + protected _keyDown(ev: KeyboardEvent): boolean { if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) { return false; } this.restartCursorBlinking(); - if (!this.compositionHelper.keydown.bind(this.compositionHelper)(ev)) { + if (!this.compositionHelper.keydown(ev)) { if (this.buffer.ybase !== this.buffer.ydisp) { this.scrollToBottom(); } return false; } - const result = this.evaluateKeyEscapeSequence(ev); + const result = this._evaluateKeyEscapeSequence(ev); if (result.key === C0.DC3) { // XOFF this.writeStopped = true; @@ -1501,7 +1501,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT return this.cancel(ev, true); } - if (isThirdLevelShift(ev)) { + if (isThirdLevelShift(this.browser, ev)) { return true; } @@ -1529,7 +1529,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * Reference: http://invisible-island.net/xterm/ctlseqs/ctlseqs.html * @param ev The keyboard event to be translated to key escape sequence. */ - private evaluateKeyEscapeSequence(ev: KeyboardEvent): {cancel: boolean, key: string, scrollDisp: number} { + protected _evaluateKeyEscapeSequence(ev: KeyboardEvent): {cancel: boolean, key: string, scrollDisp: number} { const result: {cancel: boolean, key: string, scrollDisp: number} = { // Whether to cancel event propogation (NOTE: this may not be needed since the event is // canceled at the end of keyDown @@ -1836,7 +1836,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent * @param {KeyboardEvent} ev The keypress event to be handled. */ - private keyPress(ev: KeyboardEvent): boolean { + protected _keyPress(ev: KeyboardEvent): boolean { let key; if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) { @@ -1856,7 +1856,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT } if (!key || ( - (ev.altKey || ev.ctrlKey || ev.metaKey) && !isThirdLevelShift(ev) + (ev.altKey || ev.ctrlKey || ev.metaKey) && !isThirdLevelShift(this.browser, ev) )) { return false; } @@ -2309,10 +2309,10 @@ function off(el: any, type: string, handler: (event: Event) => any, capture: boo el.removeEventListener(type, handler, capture); } -function isThirdLevelShift(ev: KeyboardEvent): boolean { +function isThirdLevelShift(browser: IBrowser, ev: KeyboardEvent): boolean { const thirdLevelKey = - (Browser.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) || - (Browser.isMSWindows && ev.altKey && ev.ctrlKey && !ev.metaKey); + (browser.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) || + (browser.isMSWindows && ev.altKey && ev.ctrlKey && !ev.metaKey); if (ev.type === 'keypress') { return thirdLevelKey; diff --git a/src/Viewport.test.ts b/src/Viewport.test.ts index 8c7a914b..428ef098 100644 --- a/src/Viewport.test.ts +++ b/src/Viewport.test.ts @@ -4,7 +4,7 @@ import { assert } from 'chai'; import { Viewport } from './Viewport'; -import {BufferSet} from './BufferSet'; +import { BufferSet } from './BufferSet'; describe('Viewport', () => { let terminal; diff --git a/src/test/addons/test.js b/src/test/addons/test.js deleted file mode 100644 index 42ede9db..00000000 --- a/src/test/addons/test.js +++ /dev/null @@ -1,10 +0,0 @@ -var assert = require('chai').assert; -var Terminal = require('../../xterm'); - -describe('xterm.js addons', function() { - it('should load addons with Terminal.loadAddon', function () { - Terminal.loadAddon('attach'); - // Test that addon was loaded successfully - assert.equal(typeof Terminal.prototype.attach, 'function'); - }); -}); diff --git a/src/test/test.js b/src/test/test.js deleted file mode 100644 index 2c44f1e7..00000000 --- a/src/test/test.js +++ /dev/null @@ -1,889 +0,0 @@ -var assert = require('chai').assert; -var expect = require('chai').expect; -var Terminal = require('../xterm'); -var CHAR_DATA_CHAR_INDEX = require('../Buffer').CHAR_DATA_CHAR_INDEX; -var CHAR_DATA_WIDTH_INDEX = require('../Buffer').CHAR_DATA_WIDTH_INDEX; - -describe('xterm.js', function() { - var xterm; - - beforeEach(function () { - xterm = new Terminal(); - xterm.refresh = function(){}; - xterm.viewport = { - syncScrollArea: function(){} - }; - xterm.compositionHelper = { - keydown: function(){ return true; } - }; - // Force synchronous writes - xterm.write = function(data) { - xterm.writeBuffer.push(data); - xterm.innerWrite(); - }; - xterm.element = { - classList: { - toggle: function(){}, - remove: function(){} - } - }; - }); - - describe('getOption', function() { - it('should retrieve the option correctly', function() { - // In the `options` namespace. - xterm.options.cursorBlink = true; - assert.equal(xterm.getOption('cursorBlink'), true); - - // On the Terminal instance - delete xterm.options.cursorBlink; - xterm.cursorBlink = false; - assert.equal(xterm.getOption('cursorBlink'), false); - }); - it('should throw when retrieving a non-existant option', function() { - assert.throws(xterm.getOption.bind(xterm, 'fake', true)); - }); - }); - - describe('setOption', function() { - let originalWarn; - let warnCallCount; - beforeEach(() => { - originalWarn = console.warn; - warnCallCount = 0; - console.warn = () => warnCallCount++; - }); - afterEach(() => { - console.warn = originalWarn; - }); - it('should set the option correctly', function() { - xterm.setOption('cursorBlink', true); - assert.equal(xterm.cursorBlink, true); - assert.equal(xterm.options.cursorBlink, true); - xterm.setOption('cursorBlink', false); - assert.equal(xterm.cursorBlink, false); - assert.equal(xterm.options.cursorBlink, false); - }); - it('should throw when setting a non-existant option', function() { - assert.throws(xterm.setOption.bind(xterm, 'fake', true)); - }); - it('should warn and do nothing when scrollback is less than number of rows', function() { - xterm.setOption('scrollback', xterm.rows - 1); - assert.equal(xterm.getOption('scrollback'), 1000); - assert.equal(warnCallCount, 1); - }); - }); - - describe('clear', function() { - it('should clear a buffer equal to rows', function() { - var promptLine = xterm.buffer.lines.get(xterm.buffer.ybase + xterm.buffer.y); - xterm.clear(); - assert.equal(xterm.buffer.y, 0); - assert.equal(xterm.buffer.ybase, 0); - assert.equal(xterm.buffer.ydisp, 0); - assert.equal(xterm.buffer.lines.length, xterm.rows); - assert.deepEqual(xterm.buffer.lines.get(0), promptLine); - for (var i = 1; i < xterm.rows; i++) { - assert.deepEqual(xterm.buffer.lines.get(i), xterm.blankLine()); - } - }); - it('should clear a buffer larger than rows', function() { - // Fill the buffer with dummy rows - for (var i = 0; i < xterm.rows * 2; i++) { - xterm.write('test\n'); - } - - var promptLine = xterm.buffer.lines.get(xterm.buffer.ybase + xterm.buffer.y); - xterm.clear(); - assert.equal(xterm.buffer.y, 0); - assert.equal(xterm.buffer.ybase, 0); - assert.equal(xterm.buffer.ydisp, 0); - assert.equal(xterm.buffer.lines.length, xterm.rows); - assert.deepEqual(xterm.buffer.lines.get(0), promptLine); - for (var i = 1; i < xterm.rows; i++) { - assert.deepEqual(xterm.buffer.lines.get(i), xterm.blankLine()); - } - }); - it('should not break the prompt when cleared twice', function() { - var promptLine = xterm.buffer.lines.get(xterm.buffer.ybase + xterm.buffer.y); - xterm.clear(); - xterm.clear(); - assert.equal(xterm.buffer.y, 0); - assert.equal(xterm.buffer.ybase, 0); - assert.equal(xterm.buffer.ydisp, 0); - assert.equal(xterm.buffer.lines.length, xterm.rows); - assert.deepEqual(xterm.buffer.lines.get(0), promptLine); - for (var i = 1; i < xterm.rows; i++) { - assert.deepEqual(xterm.buffer.lines.get(i), xterm.blankLine()); - } - }); - }); - - describe('scroll', function() { - describe('scrollDisp', function() { - var startYDisp; - beforeEach(function() { - for (var i = 0; i < xterm.rows * 2; i++) { - xterm.writeln('test'); - } - startYDisp = xterm.rows + 1; - }); - it('should scroll a single line', function() { - assert.equal(xterm.buffer.ydisp, startYDisp); - xterm.scrollDisp(-1); - assert.equal(xterm.buffer.ydisp, startYDisp - 1); - xterm.scrollDisp(1); - assert.equal(xterm.buffer.ydisp, startYDisp); - }); - it('should scroll multiple lines', function() { - assert.equal(xterm.buffer.ydisp, startYDisp); - xterm.scrollDisp(-5); - assert.equal(xterm.buffer.ydisp, startYDisp - 5); - xterm.scrollDisp(5); - assert.equal(xterm.buffer.ydisp, startYDisp); - }); - it('should not scroll beyond the bounds of the buffer', function() { - assert.equal(xterm.buffer.ydisp, startYDisp); - xterm.scrollDisp(1); - assert.equal(xterm.buffer.ydisp, startYDisp); - for (var i = 0; i < startYDisp; i++) { - xterm.scrollDisp(-1); - } - assert.equal(xterm.buffer.ydisp, 0); - xterm.scrollDisp(-1); - assert.equal(xterm.buffer.ydisp, 0); - }); - }); - - describe('scrollPages', function() { - var startYDisp; - beforeEach(function() { - for (var i = 0; i < xterm.rows * 3; i++) { - xterm.writeln('test'); - } - startYDisp = (xterm.rows * 2) + 1; - }); - it('should scroll a single page', function() { - assert.equal(xterm.buffer.ydisp, startYDisp); - xterm.scrollPages(-1); - assert.equal(xterm.buffer.ydisp, startYDisp - (xterm.rows - 1)); - xterm.scrollPages(1); - assert.equal(xterm.buffer.ydisp, startYDisp); - }); - it('should scroll a multiple pages', function() { - assert.equal(xterm.buffer.ydisp, startYDisp); - xterm.scrollPages(-2); - assert.equal(xterm.buffer.ydisp, startYDisp - (xterm.rows - 1) * 2); - xterm.scrollPages(2); - assert.equal(xterm.buffer.ydisp, startYDisp); - }); - }); - - describe('scrollToTop', function() { - beforeEach(function() { - for (var i = 0; i < xterm.rows * 3; i++) { - xterm.writeln('test'); - } - }); - it('should scroll to the top', function() { - assert.notEqual(xterm.buffer.ydisp, 0); - xterm.scrollToTop(); - assert.equal(xterm.buffer.ydisp, 0); - }); - }); - - describe('scrollToBottom', function() { - var startYDisp; - beforeEach(function() { - for (var i = 0; i < xterm.rows * 3; i++) { - xterm.writeln('test'); - } - startYDisp = (xterm.rows * 2) + 1; - }); - it('should scroll to the bottom', function() { - xterm.scrollDisp(-1); - xterm.scrollToBottom(); - assert.equal(xterm.buffer.ydisp, startYDisp); - xterm.scrollPages(-1); - xterm.scrollToBottom(); - assert.equal(xterm.buffer.ydisp, startYDisp); - xterm.scrollToTop(); - xterm.scrollToBottom(); - assert.equal(xterm.buffer.ydisp, startYDisp); - }); - }); - - describe('keyDown', function () { - it('should scroll down, when a key is pressed and terminal is scrolled up', function () { - // Override evaluateKeyEscapeSequence to return cancel code - xterm.evaluateKeyEscapeSequence = function() { - return { key: 'a' }; - }; - var event = { - type: 'keydown', - keyCode: 0, - preventDefault: function(){}, - stopPropagation: function(){} - }; - - xterm.buffer.ydisp = 0; - xterm.buffer.ybase = 40; - assert.notEqual(xterm.buffer.ydisp, xterm.buffer.ybase); - xterm.keyDown(event); - - // Ensure that now the terminal is scrolled to bottom - assert.equal(xterm.buffer.ydisp, xterm.buffer.ybase); - }); - - it('should not scroll down, when a custom keydown handler prevents the event', function () { - // Add some output to the terminal - for (var i = 0; i < xterm.rows * 3; i++) { - xterm.writeln('test'); - } - var startYDisp = (xterm.rows * 2) + 1; - xterm.attachCustomKeyEventHandler(function () { - return false; - }); - - assert.equal(xterm.buffer.ydisp, startYDisp); - xterm.scrollDisp(-1); - assert.equal(xterm.buffer.ydisp, startYDisp - 1); - xterm.keyDown({ keyCode: 0 }); - assert.equal(xterm.buffer.ydisp, startYDisp - 1); - }); - }); - }); - - describe('evaluateKeyEscapeSequence', function() { - it('should return the correct escape sequence for unmodified keys', function() { - // Backspace - assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 8 }).key, '\x7f'); // ^? - // Tab - assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 9 }).key, '\t'); - // Return/enter - assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 13 }).key, '\r'); // CR - // Escape - assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 27 }).key, '\x1b'); - // Page up, page down - assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 33 }).key, '\x1b[5~'); // CSI 5 ~ - assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 34 }).key, '\x1b[6~'); // CSI 6 ~ - // End, Home - assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 35 }).key, '\x1b[F'); // SS3 F - assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 36 }).key, '\x1b[H'); // SS3 H - // Left, up, right, down arrows - assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 37 }).key, '\x1b[D'); // CSI D - assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 38 }).key, '\x1b[A'); // CSI A - assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 39 }).key, '\x1b[C'); // CSI C - assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 40 }).key, '\x1b[B'); // CSI B - // Insert - assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 45 }).key, '\x1b[2~'); // CSI 2 ~ - // Delete - assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 46 }).key, '\x1b[3~'); // CSI 3 ~ - // F1-F12 - assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 112 }).key, '\x1bOP'); // SS3 P - assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 113 }).key, '\x1bOQ'); // SS3 Q - assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 114 }).key, '\x1bOR'); // SS3 R - assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 115 }).key, '\x1bOS'); // SS3 S - assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 116 }).key, '\x1b[15~'); // CSI 1 5 ~ - assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 117 }).key, '\x1b[17~'); // CSI 1 7 ~ - assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 118 }).key, '\x1b[18~'); // CSI 1 8 ~ - assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 119 }).key, '\x1b[19~'); // CSI 1 9 ~ - assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 120 }).key, '\x1b[20~'); // CSI 2 0 ~ - assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 121 }).key, '\x1b[21~'); // CSI 2 1 ~ - assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 122 }).key, '\x1b[23~'); // CSI 2 3 ~ - assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 123 }).key, '\x1b[24~'); // CSI 2 4 ~ - }); - it('should return \\x1b[3;5~ for ctrl+delete', function() { - assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 46 }).key, '\x1b[3;5~'); - }); - it('should return \\x1b[3;2~ for shift+delete', function() { - assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 46 }).key, '\x1b[3;2~'); - }); - it('should return \\x1b[3;3~ for alt+delete', function() { - assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 46 }).key, '\x1b[3;3~'); - }); - it('should return \\x1b[5D for ctrl+left', function() { - assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 37 }).key, '\x1b[1;5D'); // CSI 5 D - }); - it('should return \\x1b[5C for ctrl+right', function() { - assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 39 }).key, '\x1b[1;5C'); // CSI 5 C - }); - it('should return \\x1b[5A for ctrl+up', function() { - assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 38 }).key, '\x1b[1;5A'); // CSI 5 A - }); - it('should return \\x1b[5B for ctrl+down', function() { - assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 40 }).key, '\x1b[1;5B'); // CSI 5 B - }); - - describe('On non-macOS platforms', function() { - beforeEach(function() { - xterm.browser.isMac = false; - }); - // Evalueate alt + arrow key movement, which is a feature of terminal emulators but not VT100 - // http://unix.stackexchange.com/a/108106 - it('should return \\x1b[5D for alt+left', function() { - assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 37 }).key, '\x1b[1;5D'); // CSI 5 D - }); - it('should return \\x1b[5C for alt+right', function() { - assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 39 }).key, '\x1b[1;5C'); // CSI 5 C - }); - }); - - describe('On macOS platforms', function() { - beforeEach(function() { - xterm.browser.isMac = true; - }); - it('should return \\x1bb for alt+left', function() { - assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 37 }).key, '\x1bb'); // CSI 5 D - }); - it('should return \\x1bf for alt+right', function() { - assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 39 }).key, '\x1bf'); // CSI 5 C - }); - }); - - it('should return \\x1b[5A for alt+up', function() { - assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 38 }).key, '\x1b[1;5A'); // CSI 5 A - }); - it('should return \\x1b[5B for alt+down', function() { - assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 40 }).key, '\x1b[1;5B'); // CSI 5 B - }); - it('should return the correct escape sequence for modified F1-F12 keys', function() { - assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 112 }).key, '\x1b[1;2P'); - assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 113 }).key, '\x1b[1;2Q'); - assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 114 }).key, '\x1b[1;2R'); - assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 115 }).key, '\x1b[1;2S'); - assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 116 }).key, '\x1b[15;2~'); - assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 117 }).key, '\x1b[17;2~'); - assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 118 }).key, '\x1b[18;2~'); - assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 119 }).key, '\x1b[19;2~'); - assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 120 }).key, '\x1b[20;2~'); - assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 121 }).key, '\x1b[21;2~'); - assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 122 }).key, '\x1b[23;2~'); - assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 123 }).key, '\x1b[24;2~'); - assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 112 }).key, '\x1b[1;3P'); - assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 113 }).key, '\x1b[1;3Q'); - assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 114 }).key, '\x1b[1;3R'); - assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 115 }).key, '\x1b[1;3S'); - assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 116 }).key, '\x1b[15;3~'); - assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 117 }).key, '\x1b[17;3~'); - assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 118 }).key, '\x1b[18;3~'); - assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 119 }).key, '\x1b[19;3~'); - assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 120 }).key, '\x1b[20;3~'); - assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 121 }).key, '\x1b[21;3~'); - assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 122 }).key, '\x1b[23;3~'); - assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 123 }).key, '\x1b[24;3~'); - - assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 112 }).key, '\x1b[1;5P'); - assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 113 }).key, '\x1b[1;5Q'); - assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 114 }).key, '\x1b[1;5R'); - assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 115 }).key, '\x1b[1;5S'); - assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 116 }).key, '\x1b[15;5~'); - assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 117 }).key, '\x1b[17;5~'); - assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 118 }).key, '\x1b[18;5~'); - assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 119 }).key, '\x1b[19;5~'); - assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 120 }).key, '\x1b[20;5~'); - assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 121 }).key, '\x1b[21;5~'); - assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 122 }).key, '\x1b[23;5~'); - assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 123 }).key, '\x1b[24;5~'); - }); - }); - - describe('attachCustomKeyEventHandler', function () { - var evKeyDown = { - preventDefault: function() {}, - stopPropagation: function() {}, - type: 'keydown' - } - var evKeyPress = { - preventDefault: function() {}, - stopPropagation: function() {}, - type: 'keypress' - } - - beforeEach(function() { - xterm.handler = function() {}; - xterm.showCursor = function() {}; - xterm.clearSelection = function() {}; - xterm.compositionHelper = { - keydown: { - bind: function() { - return function () { return true; } - } - }, - keypress: { - bind: function() { - return function () { return true; } - } - } - } - }); - - it('should process the keydown/keypress event based on what the handler returns', function () { - assert.equal(xterm.keyDown(Object.assign({}, evKeyDown, { keyCode: 77 })), true); - assert.equal(xterm.keyPress(Object.assign({}, evKeyPress, { keyCode: 77 })), true); - xterm.attachCustomKeyEventHandler(function (ev) { - return ev.keyCode === 77; - }); - assert.equal(xterm.keyDown(Object.assign({}, evKeyDown, { keyCode: 77 })), true); - assert.equal(xterm.keyPress(Object.assign({}, evKeyPress, { keyCode: 77 })), true); - xterm.attachCustomKeyEventHandler(function (ev) { - return ev.keyCode !== 77; - }); - assert.equal(xterm.keyDown(Object.assign({}, evKeyDown, { keyCode: 77 })), false); - assert.equal(xterm.keyPress(Object.assign({}, evKeyPress, { keyCode: 77 })), false); - }); - - it('should alive after reset(ESC c Full Reset (RIS))', function () { - xterm.attachCustomKeyEventHandler(function (ev) { - return ev.keyCode !== 77; - }); - assert.equal(xterm.keyDown(Object.assign({}, evKeyDown, { keyCode: 77 })), false); - assert.equal(xterm.keyPress(Object.assign({}, evKeyPress, { keyCode: 77 })), false); - xterm.reset(); - assert.equal(xterm.keyDown(Object.assign({}, evKeyDown, { keyCode: 77 })), false); - assert.equal(xterm.keyPress(Object.assign({}, evKeyPress, { keyCode: 77 })), false); - }); - }); - - describe('Third level shift', function() { - var evKeyDown = { - preventDefault: function() {}, - stopPropagation: function() {}, - type: 'keydown' - }, - evKeyPress = { - preventDefault: function() {}, - stopPropagation: function() {}, - type: 'keypress' - }; - - beforeEach(function() { - xterm.handler = function() {}; - xterm.showCursor = function() {}; - xterm.clearSelection = function() {}; - xterm.compositionHelper = { - isComposing: false, - keydown: { - bind: function() { - return function() { return true; }; - } - } - }; - }); - - describe('On Mac OS', function() { - beforeEach(function() { - xterm.browser.isMac = true; - }); - - it('should not interfere with the alt key on keyDown', function() { - assert.equal( - xterm.keyDown(Object.assign({}, evKeyDown, { altKey: true, keyCode: 81 })), - true - ); - assert.equal( - xterm.keyDown(Object.assign({}, evKeyDown, { altKey: true, keyCode: 192 })), - true - ); - }); - - it('should interefere with the alt + arrow keys', function() { - assert.equal( - xterm.keyDown(Object.assign({}, evKeyDown, { altKey: true, keyCode: 37 })), - false - ); - assert.equal( - xterm.keyDown(Object.assign({}, evKeyDown, { altKey: true, keyCode: 39 })), - false - ); - }); - - it('should emit key with alt + key on keyPress', function(done) { - var keys = ['@', '@', '\\', '\\', '|', '|']; - - xterm.on('keypress', function(key) { - if (key) { - var index = keys.indexOf(key); - assert(index !== -1, "Emitted wrong key: " + key); - keys.splice(index, 1); - } - if (keys.length === 0) done(); - }); - - xterm.keyPress(Object.assign({}, evKeyPress, { altKey: true, keyCode: 64 })); // @ - // Firefox - xterm.keyPress(Object.assign({}, evKeyPress, { altKey: true, charCode: 64, keyCode: 0 })); - xterm.keyPress(Object.assign({}, evKeyPress, { altKey: true, keyCode: 92 })); // \ - xterm.keyPress(Object.assign({}, evKeyPress, { altKey: true, charCode: 92, keyCode: 0 })); - xterm.keyPress(Object.assign({}, evKeyPress, { altKey: true, keyCode: 124 })); // | - xterm.keyPress(Object.assign({}, evKeyPress, { altKey: true, charCode: 124, keyCode: 0 })); - }); - }); - - describe('On MS Windows', function() { - beforeEach(function() { - xterm.browser.isMSWindows = true; - }); - - it('should not interfere with the alt + ctrl key on keyDown', function() { - assert.equal( - xterm.keyDown(Object.assign({}, evKeyDown, { altKey: true, ctrlKey: true, keyCode: 81 })), - true - ); - assert.equal( - xterm.keyDown(Object.assign({}, evKeyDown, { altKey: true, ctrlKey: true, keyCode: 192 })), - true - ); - }); - - it('should interefere with the alt + ctrl + arrow keys', function() { - assert.equal( - xterm.keyDown(Object.assign({}, evKeyDown, { altKey: true, ctrlKey: true, keyCode: 37 })), - false - ); - assert.equal( - xterm.keyDown(Object.assign({}, evKeyDown, { altKey: true, ctrlKey: true, keyCode: 39 })), - false - ); - }); - - it('should emit key with alt + ctrl + key on keyPress', function(done) { - var keys = ['@', '@', '\\', '\\', '|', '|']; - - xterm.on('keypress', function(key) { - if (key) { - var index = keys.indexOf(key); - assert(index !== -1, "Emitted wrong key: " + key); - keys.splice(index, 1); - } - if (keys.length === 0) done(); - }); - - xterm.keyPress( - Object.assign({}, evKeyPress, { altKey: true, ctrlKey: true, keyCode: 64 }) - ); // @ - xterm.keyPress( - Object.assign({}, evKeyPress, { altKey: true, ctrlKey: true, charCode: 64, keyCode: 0 }) - ); - xterm.keyPress( - Object.assign({}, evKeyPress, { altKey: true, ctrlKey: true, keyCode: 92 }) - ); // \ - xterm.keyPress( - Object.assign({}, evKeyPress, { altKey: true, ctrlKey: true, charCode: 92, keyCode: 0 }) - ); - xterm.keyPress( - Object.assign({}, evKeyPress, { altKey: true, ctrlKey: true, keyCode: 124 }) - ); // | - xterm.keyPress( - Object.assign({}, evKeyPress, { altKey: true, ctrlKey: true, charCode: 124, keyCode: 0 }) - ); - }); - }); - }); - - describe('unicode - surrogates', function() { - it('2 characters per cell', function () { - this.timeout(10000); // This is needed because istanbul patches code and slows it down - var high = String.fromCharCode(0xD800); - for (var i=0xDC00; i<=0xDCFF; ++i) { - xterm.write(high + String.fromCharCode(i)); - var tchar = xterm.buffer.lines.get(0)[0]; - expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(high + String.fromCharCode(i)); - expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2); - expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1); - expect(xterm.buffer.lines.get(0)[1][CHAR_DATA_CHAR_INDEX]).eql(' '); - xterm.reset(); - } - }); - it('2 characters at last cell', function() { - var high = String.fromCharCode(0xD800); - for (var i=0xDC00; i<=0xDCFF; ++i) { - xterm.buffer.x = xterm.cols - 1; - xterm.write(high + String.fromCharCode(i)); - expect(xterm.buffer.lines.get(0)[xterm.buffer.x-1][CHAR_DATA_CHAR_INDEX]).eql(high + String.fromCharCode(i)); - expect(xterm.buffer.lines.get(0)[xterm.buffer.x-1][CHAR_DATA_CHAR_INDEX].length).eql(2); - expect(xterm.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX]).eql(' '); - xterm.reset(); - } - }); - it('2 characters per cell over line end with autowrap', function() { - var high = String.fromCharCode(0xD800); - for (var i=0xDC00; i<=0xDCFF; ++i) { - xterm.buffer.x = xterm.cols - 1; - xterm.wraparoundMode = true; - xterm.write('a' + high + String.fromCharCode(i)); - expect(xterm.buffer.lines.get(0)[xterm.cols-1][CHAR_DATA_CHAR_INDEX]).eql('a'); - expect(xterm.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX]).eql(high + String.fromCharCode(i)); - expect(xterm.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX].length).eql(2); - expect(xterm.buffer.lines.get(1)[1][CHAR_DATA_CHAR_INDEX]).eql(' '); - xterm.reset(); - } - }); - it('2 characters per cell over line end without autowrap', function() { - var high = String.fromCharCode(0xD800); - for (var i=0xDC00; i<=0xDCFF; ++i) { - xterm.buffer.x = xterm.cols - 1; - xterm.wraparoundMode = false; - xterm.write('a' + high + String.fromCharCode(i)); - // auto wraparound mode should cut off the rest of the line - expect(xterm.buffer.lines.get(0)[xterm.cols-1][CHAR_DATA_CHAR_INDEX]).eql('a'); - expect(xterm.buffer.lines.get(0)[xterm.cols-1][CHAR_DATA_CHAR_INDEX].length).eql(1); - expect(xterm.buffer.lines.get(1)[1][CHAR_DATA_CHAR_INDEX]).eql(' '); - xterm.reset(); - } - }); - it('splitted surrogates', function() { - var high = String.fromCharCode(0xD800); - for (var i=0xDC00; i<=0xDCFF; ++i) { - xterm.write(high); - xterm.write(String.fromCharCode(i)); - var tchar = xterm.buffer.lines.get(0)[0]; - expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(high + String.fromCharCode(i)); - expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2); - expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1); - expect(xterm.buffer.lines.get(0)[1][CHAR_DATA_CHAR_INDEX]).eql(' '); - xterm.reset(); - } - }); - }); - - describe('unicode - combining characters', function() { - it('café', function () { - xterm.write('cafe\u0301'); - expect(xterm.buffer.lines.get(0)[3][CHAR_DATA_CHAR_INDEX]).eql('e\u0301'); - expect(xterm.buffer.lines.get(0)[3][CHAR_DATA_CHAR_INDEX].length).eql(2); - expect(xterm.buffer.lines.get(0)[3][CHAR_DATA_WIDTH_INDEX]).eql(1); - }); - it('café - end of line', function() { - xterm.buffer.x = xterm.cols - 1 - 3; - xterm.write('cafe\u0301'); - expect(xterm.buffer.lines.get(0)[xterm.cols-1][CHAR_DATA_CHAR_INDEX]).eql('e\u0301'); - expect(xterm.buffer.lines.get(0)[xterm.cols-1][CHAR_DATA_CHAR_INDEX].length).eql(2); - expect(xterm.buffer.lines.get(0)[xterm.cols-1][CHAR_DATA_WIDTH_INDEX]).eql(1); - expect(xterm.buffer.lines.get(0)[1][CHAR_DATA_CHAR_INDEX]).eql(' '); - expect(xterm.buffer.lines.get(0)[1][CHAR_DATA_CHAR_INDEX].length).eql(1); - expect(xterm.buffer.lines.get(0)[1][CHAR_DATA_WIDTH_INDEX]).eql(1); - }); - it('multiple combined é', function() { - xterm.wraparoundMode = true; - xterm.write(Array(100).join('e\u0301')); - for (var i=0; iBrowser; writeBuffer: string[]; children: HTMLElement[]; cursorHidden: boolean; @@ -184,3 +185,34 @@ export class MockBuffer implements IBuffer { throw new Error('Method not implemented.'); } } + +export class MockViewport implements IViewport { + onWheel(ev: WheelEvent): void { + throw new Error('Method not implemented.'); + } + onTouchStart(ev: TouchEvent): void { + throw new Error('Method not implemented.'); + } + onTouchMove(ev: TouchEvent): void { + throw new Error('Method not implemented.'); + } + syncScrollArea(): void { } +} + +export class MockCompositionHelper implements ICompositionHelper { + compositionstart(): void { + throw new Error('Method not implemented.'); + } + compositionupdate(ev: CompositionEvent): void { + throw new Error('Method not implemented.'); + } + compositionend(): void { + throw new Error('Method not implemented.'); + } + updateCompositionElements(dontRecurse?: boolean): void { + throw new Error('Method not implemented.'); + } + keydown(ev: KeyboardEvent): boolean { + return true; + } +} From 28c85b8fb920d69581816cd166d7f6ef459656d2 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 7 Aug 2017 22:24:22 -0700 Subject: [PATCH 035/378] Convert escape sequences test to TS Part of #862 --- src/test/escape-sequences-test.js | 141 ----------------------------- src/test/escape-sequences-test.ts | 144 ++++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+), 141 deletions(-) delete mode 100644 src/test/escape-sequences-test.js create mode 100644 src/test/escape-sequences-test.ts diff --git a/src/test/escape-sequences-test.js b/src/test/escape-sequences-test.js deleted file mode 100644 index 51e5d017..00000000 --- a/src/test/escape-sequences-test.js +++ /dev/null @@ -1,141 +0,0 @@ -var glob = require('glob'); -var fs = require('fs'); -var os = require('os'); -var pty = require('node-pty'); -var Terminal = require('../xterm'); -var CHAR_DATA_CHAR_INDEX = require('../Buffer').CHAR_DATA_CHAR_INDEX; - -if (os.platform() === 'win32') { - // Skip tests on Windows since pty.open isn't supported - return; -} - -var CONSOLE_LOG = console.log; - -// expect files need terminal at 80x25! -var COLS = 80; -var ROWS = 25; - -/** some helpers for pty interaction */ -// we need a pty in between to get the termios decorations -// for the basic test cases a raw pty device is enough -var primitive_pty = pty.native.open(COLS, ROWS); - -// fake sychronous pty write - read -// we just pipe the data from slave to master as a child program would do -// pty.js opens pipe fds with O_NONBLOCK -// just wait 10ms instead of setting fds to blocking mode -function ptyWriteRead(s, cb) { - fs.writeSync(primitive_pty.slave, s); - setTimeout(() => { - var b = Buffer(64000); - var bytes = fs.readSync(primitive_pty.master, b, 0, 64000); - cb(b.toString('utf8', 0, bytes)); - }); -} - -// make sure raw pty is at x=0 and has no pending data -function ptyReset(cb) { - ptyWriteRead('\r\n', cb); -} - -/* debug helpers */ -// generate colorful noisy output to compare xterm and emulator cell states -function formatError(in_, out_, expected) { - function addLineNumber(start, color) { - var counter = start || 0; - return function(s) { - counter += 1; - return '\x1b[33m' + (' ' + counter).slice(-2) + color + s; - } - } - var line80 = '12345678901234567890123456789012345678901234567890123456789012345678901234567890'; - var s = ''; - s += '\n\x1b[34m' + JSON.stringify(in_); - s += '\n\x1b[33m ' + line80 + '\n'; - s += out_.split('\n').map(addLineNumber(0, '\x1b[31m')).join('\n'); - s += '\n\x1b[33m ' + line80 + '\n'; - s += expected.split('\n').map(addLineNumber(0, '\x1b[32m')).join('\n'); - return s; -} - -// simple debug output of terminal cells -function terminalToString(term) { - var result = ''; - var line_s = ''; - for (var line = term.buffer.ybase; line < term.buffer.ybase + term.rows; line++) { - line_s = ''; - for (var cell=0; cell= 0) { - continue; - } - (function(filename) { - it(filename.split('/').slice(-1)[0], done => { - ptyReset(() => { - var in_file = fs.readFileSync(filename, 'utf8'); - ptyWriteRead(in_file, from_pty => { - // uncomment this to get log from terminal - //console.log = function(){}; - - // Perform a synchronous .write(data) - xterm.writeBuffer.push(from_pty); - xterm.innerWrite(); - - var from_emulator = terminalToString(xterm); - console.log = CONSOLE_LOG; - var expected = fs.readFileSync(filename.split('.')[0] + '.text', 'utf8'); - // Some of the tests have whitespace on the right of lines, we trim all the linex - // from xterm.js so ignore this for now at least. - var expectedRightTrimmed = expected.split('\n').map(function (l) { - return l.replace(/\s+$/, ''); - }).join('\n'); - if (from_emulator != expectedRightTrimmed) { - // uncomment to get noisy output - throw new Error(formatError(in_file, from_emulator, expected)); - // throw new Error('mismatch'); - } - done(); - }); - }); - }); - })(files[i]); - } -}); - diff --git a/src/test/escape-sequences-test.ts b/src/test/escape-sequences-test.ts new file mode 100644 index 00000000..554298d0 --- /dev/null +++ b/src/test/escape-sequences-test.ts @@ -0,0 +1,144 @@ +/** + * @license MIT + * + * This file contains integration tests for xterm.js + */ + +import * as glob from 'glob'; +import * as fs from 'fs'; +import * as os from 'os'; +import * as pty from 'node-pty'; +import { Terminal } from '../Terminal'; +import { CHAR_DATA_CHAR_INDEX } from '../Buffer'; + +let primitive_pty: any; + +// fake sychronous pty write - read +// we just pipe the data from slave to master as a child program would do +// pty.js opens pipe fds with O_NONBLOCK +// just wait 10ms instead of setting fds to blocking mode +function ptyWriteRead(data: string, cb: (result: string) => void): void { + fs.writeSync(primitive_pty.slave, data); + setTimeout(() => { + let b = new Buffer(64000); + let bytes = fs.readSync(primitive_pty.master, b, 0, 64000, null); + cb(b.toString('utf8', 0, bytes)); + }); +} + +// make sure raw pty is at x=0 and has no pending data +function ptyReset(cb: (result: string) => void): void { + ptyWriteRead('\r\n', cb); +} + +/* debug helpers */ +// generate colorful noisy output to compare xterm and emulator cell states +function formatError(in_: string, out_: string, expected: string): string { + function addLineNumber(start: number, color: string): (s: string) => string { + let counter = start || 0; + return function(s: string): string { + counter += 1; + return '\x1b[33m' + (' ' + counter).slice(-2) + color + s; + }; + } + let line80 = '12345678901234567890123456789012345678901234567890123456789012345678901234567890'; + let s = ''; + s += '\n\x1b[34m' + JSON.stringify(in_); + s += '\n\x1b[33m ' + line80 + '\n'; + s += out_.split('\n').map(addLineNumber(0, '\x1b[31m')).join('\n'); + s += '\n\x1b[33m ' + line80 + '\n'; + s += expected.split('\n').map(addLineNumber(0, '\x1b[32m')).join('\n'); + return s; +} + +// simple debug output of terminal cells +function terminalToString(term: Terminal): string { + let result = ''; + let line_s = ''; + for (let line = term.buffer.ybase; line < term.buffer.ybase + term.rows; line++) { + line_s = ''; + for (let cell = 0; cell < term.cols; ++cell) { + line_s += term.buffer.lines.get(line)[cell][CHAR_DATA_CHAR_INDEX]; + } + // rtrim empty cells as xterm does + line_s = line_s.replace(/\s+$/, ''); + result += line_s; + result += '\n'; + } + return result; +} + +// Skip tests on Windows since pty.open isn't supported +if (os.platform() !== 'win32') { + let CONSOLE_LOG = console.log; + + // expect files need terminal at 80x25! + let COLS = 80; + let ROWS = 25; + + /** some helpers for pty interaction */ + // we need a pty in between to get the termios decorations + // for the basic test cases a raw pty device is enough + primitive_pty = pty.native.open(COLS, ROWS); + + /** tests */ + describe('xterm output comparison', () => { + let xterm; + + beforeEach(() => { + xterm = new Terminal({ cols: COLS, rows: ROWS }); + xterm.refresh = () => {}; + xterm.viewport = { + syncScrollArea: () => {} + }; + }); + + // omit stack trace for escape sequence files + Error.stackTraceLimit = 0; + let files = glob.sync('**/escape_sequence_files/*.in'); + // only successful tests for now + let skip = [ + 10, 16, 17, 19, 32, 33, 34, 35, 36, 39, + 40, 42, 43, 44, 45, 46, 47, 48, 49, 50, + 51, 52, 54, 55, 56, 57, 58, 59, 60, 61, + 63, 68 + ]; + if (os.platform() === 'darwin') { + // These are failing on macOS only + skip.push(3, 7, 11, 67); + } + for (let i = 0; i < files.length; i++) { + if (skip.indexOf(i) >= 0) { + continue; + } + ((filename: string) => { + it(filename.split('/').slice(-1)[0], done => { + ptyReset(() => { + let in_file = fs.readFileSync(filename, 'utf8'); + ptyWriteRead(in_file, from_pty => { + // uncomment this to get log from terminal + // console.log = function(){}; + + // Perform a synchronous .write(data) + xterm.writeBuffer.push(from_pty); + xterm.innerWrite(); + + let from_emulator = terminalToString(xterm); + console.log = CONSOLE_LOG; + let expected = fs.readFileSync(filename.split('.')[0] + '.text', 'utf8'); + // Some of the tests have whitespace on the right of lines, we trim all the linex + // from xterm.js so ignore this for now at least. + let expectedRightTrimmed = expected.split('\n').map(l => l.replace(/\s+$/, '')).join('\n'); + if (from_emulator !== expectedRightTrimmed) { + // uncomment to get noisy output + throw new Error(formatError(in_file, from_emulator, expected)); + // throw new Error('mismatch'); + } + done(); + }); + }); + }); + })(files[i]); + } + }); +} From b3ad3eff8b14fdab9aa5adebd240518926e1ddc3 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 7 Aug 2017 22:38:42 -0700 Subject: [PATCH 036/378] Shuffle files around src/test/escape-sequences-test.ts -> src/Terminal.integration.ts src/test/escape_sequence_files/* -> fixtures/escape_sequence_files/ --- {src/test => fixtures}/escape_sequence_files/NOTES | 0 .../escape_sequence_files/t0001-all_printable.in | 0 .../escape_sequence_files/t0001-all_printable.text | 0 .../escape_sequence_files/t0002-history.in | 0 .../escape_sequence_files/t0002-history.text | 0 .../escape_sequence_files/t0002j-simple_string.in | 0 .../escape_sequence_files/t0002j-simple_string.text | 0 .../escape_sequence_files/t0003-line_wrap.in | 0 .../escape_sequence_files/t0003-line_wrap.text | 0 .../test => fixtures}/escape_sequence_files/t0003j-LF.in | 0 .../escape_sequence_files/t0003j-LF.text | 0 {src/test => fixtures}/escape_sequence_files/t0004-LF.in | 0 .../escape_sequence_files/t0004-LF.text | 0 .../test => fixtures}/escape_sequence_files/t0004j-CR.in | 0 .../escape_sequence_files/t0004j-CR.text | 0 {src/test => fixtures}/escape_sequence_files/t0005-CR.in | 0 .../escape_sequence_files/t0005-CR.text | 0 .../test => fixtures}/escape_sequence_files/t0006-IND.in | 0 .../escape_sequence_files/t0006-IND.text | 0 .../escape_sequence_files/t0007-space_at_end.in | 0 .../escape_sequence_files/t0007-space_at_end.text | 0 {src/test => fixtures}/escape_sequence_files/t0008-BS.in | 0 .../escape_sequence_files/t0008-BS.text | 0 .../test => fixtures}/escape_sequence_files/t0009-NEL.in | 0 .../escape_sequence_files/t0009-NEL.text | 0 {src/test => fixtures}/escape_sequence_files/t0010-RI.in | 0 .../escape_sequence_files/t0010-RI.text | 0 .../escape_sequence_files/t0011-RI_scroll.in | 0 .../escape_sequence_files/t0011-RI_scroll.text | 0 {src/test => fixtures}/escape_sequence_files/t0012-VT.in | 0 .../escape_sequence_files/t0012-VT.text | 0 {src/test => fixtures}/escape_sequence_files/t0013-FF.in | 0 .../escape_sequence_files/t0013-FF.text | 0 .../test => fixtures}/escape_sequence_files/t0014-CAN.in | 0 .../escape_sequence_files/t0014-CAN.text | 0 .../test => fixtures}/escape_sequence_files/t0015-SUB.in | 0 .../escape_sequence_files/t0015-SUB.text | 0 {src/test => fixtures}/escape_sequence_files/t0016-SU.in | 0 .../escape_sequence_files/t0016-SU.text | 0 {src/test => fixtures}/escape_sequence_files/t0017-SD.in | 0 .../escape_sequence_files/t0017-SD.text | 0 .../test => fixtures}/escape_sequence_files/t0020-CUF.in | 0 .../escape_sequence_files/t0020-CUF.text | 0 .../test => fixtures}/escape_sequence_files/t0021-CUB.in | 0 .../escape_sequence_files/t0021-CUB.text | 0 .../test => fixtures}/escape_sequence_files/t0022-CUU.in | 0 .../escape_sequence_files/t0022-CUU.text | 0 .../escape_sequence_files/t0023-CUU_scroll.in | 0 .../escape_sequence_files/t0023-CUU_scroll.text | 0 .../test => fixtures}/escape_sequence_files/t0024-CUD.in | 0 .../escape_sequence_files/t0024-CUD.text | 0 .../test => fixtures}/escape_sequence_files/t0025-CUP.in | 0 .../escape_sequence_files/t0025-CUP.text | 0 .../test => fixtures}/escape_sequence_files/t0026-CNL.in | 0 .../escape_sequence_files/t0026-CNL.text | 0 .../test => fixtures}/escape_sequence_files/t0027-CPL.in | 0 .../escape_sequence_files/t0027-CPL.text | 0 .../test => fixtures}/escape_sequence_files/t0030-HPR.in | 0 .../escape_sequence_files/t0030-HPR.text | 0 .../escape_sequence_files/t0031-HPB.in_ | 0 .../escape_sequence_files/t0031-HPB.text | 0 .../test => fixtures}/escape_sequence_files/t0032-VPB.in | 0 .../escape_sequence_files/t0032-VPB.text | 0 .../escape_sequence_files/t0033-VPB_scroll.in | 0 .../escape_sequence_files/t0033-VPB_scroll.text | 0 .../test => fixtures}/escape_sequence_files/t0034-VPR.in | 0 .../escape_sequence_files/t0034-VPR.text | 0 .../test => fixtures}/escape_sequence_files/t0035-HVP.in | 0 .../escape_sequence_files/t0035-HVP.text | 0 .../test => fixtures}/escape_sequence_files/t0040-REP.in | 0 .../escape_sequence_files/t0040-REP.text | 0 .../test => fixtures}/escape_sequence_files/t0050-ICH.in | 0 .../escape_sequence_files/t0050-ICH.text | 0 {src/test => fixtures}/escape_sequence_files/t0051-IL.in | 0 .../escape_sequence_files/t0051-IL.text | 0 {src/test => fixtures}/escape_sequence_files/t0052-DL.in | 0 .../escape_sequence_files/t0052-DL.text | 0 .../test => fixtures}/escape_sequence_files/t0053-DCH.in | 0 .../escape_sequence_files/t0053-DCH.text | 0 .../test => fixtures}/escape_sequence_files/t0054-ECH.in | 0 .../escape_sequence_files/t0054-ECH.text | 0 {src/test => fixtures}/escape_sequence_files/t0055-EL.in | 0 .../escape_sequence_files/t0055-EL.text | 0 {src/test => fixtures}/escape_sequence_files/t0056-ED.in | 0 .../escape_sequence_files/t0056-ED.text | 0 .../test => fixtures}/escape_sequence_files/t0057-ED3.in | 0 .../escape_sequence_files/t0057-ED3.note | 0 .../escape_sequence_files/t0057-ED3.text | 0 .../escape_sequence_files/t0060-DECSC.in | 0 .../escape_sequence_files/t0060-DECSC.text | 0 .../escape_sequence_files/t0061-CSI_s.in | 0 .../escape_sequence_files/t0061-CSI_s.text | 0 .../escape_sequence_files/t0070-DECSTBM_LF.in | 0 .../escape_sequence_files/t0070-DECSTBM_LF.text | 0 .../escape_sequence_files/t0071-DECSTBM_IND.in | 0 .../escape_sequence_files/t0071-DECSTBM_IND.text | 0 .../escape_sequence_files/t0072-DECSTBM_NEL.in | 0 .../escape_sequence_files/t0072-DECSTBM_NEL.text | 0 .../escape_sequence_files/t0073-DECSTBM_RI.in | 0 .../escape_sequence_files/t0073-DECSTBM_RI.text | 0 .../escape_sequence_files/t0074-DECSTBM_SU_SD.in | 0 .../escape_sequence_files/t0074-DECSTBM_SU_SD.text | 0 .../escape_sequence_files/t0075-DECSTBM_CUU_CUD.in | 0 .../escape_sequence_files/t0075-DECSTBM_CUU_CUD.text | 0 .../escape_sequence_files/t0076-DECSTBM_IL_DL.in | 0 .../escape_sequence_files/t0076-DECSTBM_IL_DL.text | 0 .../escape_sequence_files/t0077-DECSTBM_quirks.in | 0 .../escape_sequence_files/t0077-DECSTBM_quirks.text | 0 {src/test => fixtures}/escape_sequence_files/t0080-HT.in | 0 .../escape_sequence_files/t0080-HT.text | 0 .../test => fixtures}/escape_sequence_files/t0081-TBC.in | 0 .../escape_sequence_files/t0081-TBC.text | 0 .../test => fixtures}/escape_sequence_files/t0082-HTS.in | 0 .../escape_sequence_files/t0082-HTS.text | 0 .../test => fixtures}/escape_sequence_files/t0083-CHT.in | 0 .../escape_sequence_files/t0083-CHT.text | 0 .../test => fixtures}/escape_sequence_files/t0084-CBT.in | 0 .../escape_sequence_files/t0084-CBT.text | 0 .../escape_sequence_files/t0084-CBT.text-xterm | 0 .../escape_sequence_files/t0090-alt_screen.in | 0 .../escape_sequence_files/t0090-alt_screen.text | 0 .../escape_sequence_files/t0091-alt_screen_ED3.in | 0 .../escape_sequence_files/t0091-alt_screen_ED3.text | 0 .../escape_sequence_files/t0092-alt_screen_DECSC.in | 0 .../escape_sequence_files/t0092-alt_screen_DECSC.text | 0 .../test => fixtures}/escape_sequence_files/t0100-IRM.in | 0 .../escape_sequence_files/t0100-IRM.text | 0 .../test => fixtures}/escape_sequence_files/t0101-NLM.in | 0 .../escape_sequence_files/t0101-NLM.text | 0 .../escape_sequence_files/t0102-DECAWM.in | 0 .../escape_sequence_files/t0102-DECAWM.text | 0 .../escape_sequence_files/t0103-reverse_wrap.in | 0 .../escape_sequence_files/t0103-reverse_wrap.text | 0 .../escape_sequence_files/t0200-SGR.html | 0 .../escape_sequence_files/t0200-SGR.in_ | 0 .../escape_sequence_files/t0220-SGR_inverse.html | 0 .../escape_sequence_files/t0220-SGR_inverse.in_ | 0 .../escape_sequence_files/t0500-bash_long_line.in | 0 .../escape_sequence_files/t0500-bash_long_line.text | 0 .../escape_sequence_files/t0501-bash_ls.in | 0 .../escape_sequence_files/t0501-bash_ls.text | 0 .../escape_sequence_files/t0502-bash_ls_color.in | 0 .../escape_sequence_files/t0502-bash_ls_color.text | 0 .../escape_sequence_files/t0503-zsh_ls_color.in | 0 .../escape_sequence_files/t0503-zsh_ls_color.text | 0 .../test => fixtures}/escape_sequence_files/t0504-vim.in | 0 .../escape_sequence_files/t0504-vim.text | 0 gulpfile.js | 7 ++++++- .../escape-sequences-test.ts => Terminal.integration.ts} | 9 +++++---- 149 files changed, 11 insertions(+), 5 deletions(-) rename {src/test => fixtures}/escape_sequence_files/NOTES (100%) rename {src/test => fixtures}/escape_sequence_files/t0001-all_printable.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0001-all_printable.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0002-history.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0002-history.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0002j-simple_string.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0002j-simple_string.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0003-line_wrap.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0003-line_wrap.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0003j-LF.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0003j-LF.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0004-LF.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0004-LF.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0004j-CR.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0004j-CR.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0005-CR.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0005-CR.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0006-IND.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0006-IND.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0007-space_at_end.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0007-space_at_end.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0008-BS.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0008-BS.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0009-NEL.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0009-NEL.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0010-RI.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0010-RI.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0011-RI_scroll.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0011-RI_scroll.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0012-VT.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0012-VT.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0013-FF.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0013-FF.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0014-CAN.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0014-CAN.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0015-SUB.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0015-SUB.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0016-SU.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0016-SU.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0017-SD.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0017-SD.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0020-CUF.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0020-CUF.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0021-CUB.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0021-CUB.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0022-CUU.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0022-CUU.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0023-CUU_scroll.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0023-CUU_scroll.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0024-CUD.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0024-CUD.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0025-CUP.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0025-CUP.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0026-CNL.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0026-CNL.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0027-CPL.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0027-CPL.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0030-HPR.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0030-HPR.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0031-HPB.in_ (100%) rename {src/test => fixtures}/escape_sequence_files/t0031-HPB.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0032-VPB.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0032-VPB.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0033-VPB_scroll.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0033-VPB_scroll.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0034-VPR.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0034-VPR.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0035-HVP.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0035-HVP.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0040-REP.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0040-REP.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0050-ICH.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0050-ICH.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0051-IL.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0051-IL.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0052-DL.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0052-DL.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0053-DCH.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0053-DCH.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0054-ECH.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0054-ECH.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0055-EL.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0055-EL.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0056-ED.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0056-ED.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0057-ED3.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0057-ED3.note (100%) rename {src/test => fixtures}/escape_sequence_files/t0057-ED3.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0060-DECSC.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0060-DECSC.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0061-CSI_s.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0061-CSI_s.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0070-DECSTBM_LF.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0070-DECSTBM_LF.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0071-DECSTBM_IND.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0071-DECSTBM_IND.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0072-DECSTBM_NEL.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0072-DECSTBM_NEL.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0073-DECSTBM_RI.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0073-DECSTBM_RI.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0074-DECSTBM_SU_SD.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0074-DECSTBM_SU_SD.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0075-DECSTBM_CUU_CUD.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0075-DECSTBM_CUU_CUD.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0076-DECSTBM_IL_DL.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0076-DECSTBM_IL_DL.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0077-DECSTBM_quirks.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0077-DECSTBM_quirks.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0080-HT.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0080-HT.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0081-TBC.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0081-TBC.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0082-HTS.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0082-HTS.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0083-CHT.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0083-CHT.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0084-CBT.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0084-CBT.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0084-CBT.text-xterm (100%) rename {src/test => fixtures}/escape_sequence_files/t0090-alt_screen.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0090-alt_screen.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0091-alt_screen_ED3.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0091-alt_screen_ED3.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0092-alt_screen_DECSC.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0092-alt_screen_DECSC.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0100-IRM.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0100-IRM.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0101-NLM.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0101-NLM.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0102-DECAWM.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0102-DECAWM.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0103-reverse_wrap.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0103-reverse_wrap.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0200-SGR.html (100%) rename {src/test => fixtures}/escape_sequence_files/t0200-SGR.in_ (100%) rename {src/test => fixtures}/escape_sequence_files/t0220-SGR_inverse.html (100%) rename {src/test => fixtures}/escape_sequence_files/t0220-SGR_inverse.in_ (100%) rename {src/test => fixtures}/escape_sequence_files/t0500-bash_long_line.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0500-bash_long_line.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0501-bash_ls.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0501-bash_ls.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0502-bash_ls_color.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0502-bash_ls_color.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0503-zsh_ls_color.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0503-zsh_ls_color.text (100%) rename {src/test => fixtures}/escape_sequence_files/t0504-vim.in (100%) rename {src/test => fixtures}/escape_sequence_files/t0504-vim.text (100%) rename src/{test/escape-sequences-test.ts => Terminal.integration.ts} (94%) diff --git a/src/test/escape_sequence_files/NOTES b/fixtures/escape_sequence_files/NOTES similarity index 100% rename from src/test/escape_sequence_files/NOTES rename to fixtures/escape_sequence_files/NOTES diff --git a/src/test/escape_sequence_files/t0001-all_printable.in b/fixtures/escape_sequence_files/t0001-all_printable.in similarity index 100% rename from src/test/escape_sequence_files/t0001-all_printable.in rename to fixtures/escape_sequence_files/t0001-all_printable.in diff --git a/src/test/escape_sequence_files/t0001-all_printable.text b/fixtures/escape_sequence_files/t0001-all_printable.text similarity index 100% rename from src/test/escape_sequence_files/t0001-all_printable.text rename to fixtures/escape_sequence_files/t0001-all_printable.text diff --git a/src/test/escape_sequence_files/t0002-history.in b/fixtures/escape_sequence_files/t0002-history.in similarity index 100% rename from src/test/escape_sequence_files/t0002-history.in rename to fixtures/escape_sequence_files/t0002-history.in diff --git a/src/test/escape_sequence_files/t0002-history.text b/fixtures/escape_sequence_files/t0002-history.text similarity index 100% rename from src/test/escape_sequence_files/t0002-history.text rename to fixtures/escape_sequence_files/t0002-history.text diff --git a/src/test/escape_sequence_files/t0002j-simple_string.in b/fixtures/escape_sequence_files/t0002j-simple_string.in similarity index 100% rename from src/test/escape_sequence_files/t0002j-simple_string.in rename to fixtures/escape_sequence_files/t0002j-simple_string.in diff --git a/src/test/escape_sequence_files/t0002j-simple_string.text b/fixtures/escape_sequence_files/t0002j-simple_string.text similarity index 100% rename from src/test/escape_sequence_files/t0002j-simple_string.text rename to fixtures/escape_sequence_files/t0002j-simple_string.text diff --git a/src/test/escape_sequence_files/t0003-line_wrap.in b/fixtures/escape_sequence_files/t0003-line_wrap.in similarity index 100% rename from src/test/escape_sequence_files/t0003-line_wrap.in rename to fixtures/escape_sequence_files/t0003-line_wrap.in diff --git a/src/test/escape_sequence_files/t0003-line_wrap.text b/fixtures/escape_sequence_files/t0003-line_wrap.text similarity index 100% rename from src/test/escape_sequence_files/t0003-line_wrap.text rename to fixtures/escape_sequence_files/t0003-line_wrap.text diff --git a/src/test/escape_sequence_files/t0003j-LF.in b/fixtures/escape_sequence_files/t0003j-LF.in similarity index 100% rename from src/test/escape_sequence_files/t0003j-LF.in rename to fixtures/escape_sequence_files/t0003j-LF.in diff --git a/src/test/escape_sequence_files/t0003j-LF.text b/fixtures/escape_sequence_files/t0003j-LF.text similarity index 100% rename from src/test/escape_sequence_files/t0003j-LF.text rename to fixtures/escape_sequence_files/t0003j-LF.text diff --git a/src/test/escape_sequence_files/t0004-LF.in b/fixtures/escape_sequence_files/t0004-LF.in similarity index 100% rename from src/test/escape_sequence_files/t0004-LF.in rename to fixtures/escape_sequence_files/t0004-LF.in diff --git a/src/test/escape_sequence_files/t0004-LF.text b/fixtures/escape_sequence_files/t0004-LF.text similarity index 100% rename from src/test/escape_sequence_files/t0004-LF.text rename to fixtures/escape_sequence_files/t0004-LF.text diff --git a/src/test/escape_sequence_files/t0004j-CR.in b/fixtures/escape_sequence_files/t0004j-CR.in similarity index 100% rename from src/test/escape_sequence_files/t0004j-CR.in rename to fixtures/escape_sequence_files/t0004j-CR.in diff --git a/src/test/escape_sequence_files/t0004j-CR.text b/fixtures/escape_sequence_files/t0004j-CR.text similarity index 100% rename from src/test/escape_sequence_files/t0004j-CR.text rename to fixtures/escape_sequence_files/t0004j-CR.text diff --git a/src/test/escape_sequence_files/t0005-CR.in b/fixtures/escape_sequence_files/t0005-CR.in similarity index 100% rename from src/test/escape_sequence_files/t0005-CR.in rename to fixtures/escape_sequence_files/t0005-CR.in diff --git a/src/test/escape_sequence_files/t0005-CR.text b/fixtures/escape_sequence_files/t0005-CR.text similarity index 100% rename from src/test/escape_sequence_files/t0005-CR.text rename to fixtures/escape_sequence_files/t0005-CR.text diff --git a/src/test/escape_sequence_files/t0006-IND.in b/fixtures/escape_sequence_files/t0006-IND.in similarity index 100% rename from src/test/escape_sequence_files/t0006-IND.in rename to fixtures/escape_sequence_files/t0006-IND.in diff --git a/src/test/escape_sequence_files/t0006-IND.text b/fixtures/escape_sequence_files/t0006-IND.text similarity index 100% rename from src/test/escape_sequence_files/t0006-IND.text rename to fixtures/escape_sequence_files/t0006-IND.text diff --git a/src/test/escape_sequence_files/t0007-space_at_end.in b/fixtures/escape_sequence_files/t0007-space_at_end.in similarity index 100% rename from src/test/escape_sequence_files/t0007-space_at_end.in rename to fixtures/escape_sequence_files/t0007-space_at_end.in diff --git a/src/test/escape_sequence_files/t0007-space_at_end.text b/fixtures/escape_sequence_files/t0007-space_at_end.text similarity index 100% rename from src/test/escape_sequence_files/t0007-space_at_end.text rename to fixtures/escape_sequence_files/t0007-space_at_end.text diff --git a/src/test/escape_sequence_files/t0008-BS.in b/fixtures/escape_sequence_files/t0008-BS.in similarity index 100% rename from src/test/escape_sequence_files/t0008-BS.in rename to fixtures/escape_sequence_files/t0008-BS.in diff --git a/src/test/escape_sequence_files/t0008-BS.text b/fixtures/escape_sequence_files/t0008-BS.text similarity index 100% rename from src/test/escape_sequence_files/t0008-BS.text rename to fixtures/escape_sequence_files/t0008-BS.text diff --git a/src/test/escape_sequence_files/t0009-NEL.in b/fixtures/escape_sequence_files/t0009-NEL.in similarity index 100% rename from src/test/escape_sequence_files/t0009-NEL.in rename to fixtures/escape_sequence_files/t0009-NEL.in diff --git a/src/test/escape_sequence_files/t0009-NEL.text b/fixtures/escape_sequence_files/t0009-NEL.text similarity index 100% rename from src/test/escape_sequence_files/t0009-NEL.text rename to fixtures/escape_sequence_files/t0009-NEL.text diff --git a/src/test/escape_sequence_files/t0010-RI.in b/fixtures/escape_sequence_files/t0010-RI.in similarity index 100% rename from src/test/escape_sequence_files/t0010-RI.in rename to fixtures/escape_sequence_files/t0010-RI.in diff --git a/src/test/escape_sequence_files/t0010-RI.text b/fixtures/escape_sequence_files/t0010-RI.text similarity index 100% rename from src/test/escape_sequence_files/t0010-RI.text rename to fixtures/escape_sequence_files/t0010-RI.text diff --git a/src/test/escape_sequence_files/t0011-RI_scroll.in b/fixtures/escape_sequence_files/t0011-RI_scroll.in similarity index 100% rename from src/test/escape_sequence_files/t0011-RI_scroll.in rename to fixtures/escape_sequence_files/t0011-RI_scroll.in diff --git a/src/test/escape_sequence_files/t0011-RI_scroll.text b/fixtures/escape_sequence_files/t0011-RI_scroll.text similarity index 100% rename from src/test/escape_sequence_files/t0011-RI_scroll.text rename to fixtures/escape_sequence_files/t0011-RI_scroll.text diff --git a/src/test/escape_sequence_files/t0012-VT.in b/fixtures/escape_sequence_files/t0012-VT.in similarity index 100% rename from src/test/escape_sequence_files/t0012-VT.in rename to fixtures/escape_sequence_files/t0012-VT.in diff --git a/src/test/escape_sequence_files/t0012-VT.text b/fixtures/escape_sequence_files/t0012-VT.text similarity index 100% rename from src/test/escape_sequence_files/t0012-VT.text rename to fixtures/escape_sequence_files/t0012-VT.text diff --git a/src/test/escape_sequence_files/t0013-FF.in b/fixtures/escape_sequence_files/t0013-FF.in similarity index 100% rename from src/test/escape_sequence_files/t0013-FF.in rename to fixtures/escape_sequence_files/t0013-FF.in diff --git a/src/test/escape_sequence_files/t0013-FF.text b/fixtures/escape_sequence_files/t0013-FF.text similarity index 100% rename from src/test/escape_sequence_files/t0013-FF.text rename to fixtures/escape_sequence_files/t0013-FF.text diff --git a/src/test/escape_sequence_files/t0014-CAN.in b/fixtures/escape_sequence_files/t0014-CAN.in similarity index 100% rename from src/test/escape_sequence_files/t0014-CAN.in rename to fixtures/escape_sequence_files/t0014-CAN.in diff --git a/src/test/escape_sequence_files/t0014-CAN.text b/fixtures/escape_sequence_files/t0014-CAN.text similarity index 100% rename from src/test/escape_sequence_files/t0014-CAN.text rename to fixtures/escape_sequence_files/t0014-CAN.text diff --git a/src/test/escape_sequence_files/t0015-SUB.in b/fixtures/escape_sequence_files/t0015-SUB.in similarity index 100% rename from src/test/escape_sequence_files/t0015-SUB.in rename to fixtures/escape_sequence_files/t0015-SUB.in diff --git a/src/test/escape_sequence_files/t0015-SUB.text b/fixtures/escape_sequence_files/t0015-SUB.text similarity index 100% rename from src/test/escape_sequence_files/t0015-SUB.text rename to fixtures/escape_sequence_files/t0015-SUB.text diff --git a/src/test/escape_sequence_files/t0016-SU.in b/fixtures/escape_sequence_files/t0016-SU.in similarity index 100% rename from src/test/escape_sequence_files/t0016-SU.in rename to fixtures/escape_sequence_files/t0016-SU.in diff --git a/src/test/escape_sequence_files/t0016-SU.text b/fixtures/escape_sequence_files/t0016-SU.text similarity index 100% rename from src/test/escape_sequence_files/t0016-SU.text rename to fixtures/escape_sequence_files/t0016-SU.text diff --git a/src/test/escape_sequence_files/t0017-SD.in b/fixtures/escape_sequence_files/t0017-SD.in similarity index 100% rename from src/test/escape_sequence_files/t0017-SD.in rename to fixtures/escape_sequence_files/t0017-SD.in diff --git a/src/test/escape_sequence_files/t0017-SD.text b/fixtures/escape_sequence_files/t0017-SD.text similarity index 100% rename from src/test/escape_sequence_files/t0017-SD.text rename to fixtures/escape_sequence_files/t0017-SD.text diff --git a/src/test/escape_sequence_files/t0020-CUF.in b/fixtures/escape_sequence_files/t0020-CUF.in similarity index 100% rename from src/test/escape_sequence_files/t0020-CUF.in rename to fixtures/escape_sequence_files/t0020-CUF.in diff --git a/src/test/escape_sequence_files/t0020-CUF.text b/fixtures/escape_sequence_files/t0020-CUF.text similarity index 100% rename from src/test/escape_sequence_files/t0020-CUF.text rename to fixtures/escape_sequence_files/t0020-CUF.text diff --git a/src/test/escape_sequence_files/t0021-CUB.in b/fixtures/escape_sequence_files/t0021-CUB.in similarity index 100% rename from src/test/escape_sequence_files/t0021-CUB.in rename to fixtures/escape_sequence_files/t0021-CUB.in diff --git a/src/test/escape_sequence_files/t0021-CUB.text b/fixtures/escape_sequence_files/t0021-CUB.text similarity index 100% rename from src/test/escape_sequence_files/t0021-CUB.text rename to fixtures/escape_sequence_files/t0021-CUB.text diff --git a/src/test/escape_sequence_files/t0022-CUU.in b/fixtures/escape_sequence_files/t0022-CUU.in similarity index 100% rename from src/test/escape_sequence_files/t0022-CUU.in rename to fixtures/escape_sequence_files/t0022-CUU.in diff --git a/src/test/escape_sequence_files/t0022-CUU.text b/fixtures/escape_sequence_files/t0022-CUU.text similarity index 100% rename from src/test/escape_sequence_files/t0022-CUU.text rename to fixtures/escape_sequence_files/t0022-CUU.text diff --git a/src/test/escape_sequence_files/t0023-CUU_scroll.in b/fixtures/escape_sequence_files/t0023-CUU_scroll.in similarity index 100% rename from src/test/escape_sequence_files/t0023-CUU_scroll.in rename to fixtures/escape_sequence_files/t0023-CUU_scroll.in diff --git a/src/test/escape_sequence_files/t0023-CUU_scroll.text b/fixtures/escape_sequence_files/t0023-CUU_scroll.text similarity index 100% rename from src/test/escape_sequence_files/t0023-CUU_scroll.text rename to fixtures/escape_sequence_files/t0023-CUU_scroll.text diff --git a/src/test/escape_sequence_files/t0024-CUD.in b/fixtures/escape_sequence_files/t0024-CUD.in similarity index 100% rename from src/test/escape_sequence_files/t0024-CUD.in rename to fixtures/escape_sequence_files/t0024-CUD.in diff --git a/src/test/escape_sequence_files/t0024-CUD.text b/fixtures/escape_sequence_files/t0024-CUD.text similarity index 100% rename from src/test/escape_sequence_files/t0024-CUD.text rename to fixtures/escape_sequence_files/t0024-CUD.text diff --git a/src/test/escape_sequence_files/t0025-CUP.in b/fixtures/escape_sequence_files/t0025-CUP.in similarity index 100% rename from src/test/escape_sequence_files/t0025-CUP.in rename to fixtures/escape_sequence_files/t0025-CUP.in diff --git a/src/test/escape_sequence_files/t0025-CUP.text b/fixtures/escape_sequence_files/t0025-CUP.text similarity index 100% rename from src/test/escape_sequence_files/t0025-CUP.text rename to fixtures/escape_sequence_files/t0025-CUP.text diff --git a/src/test/escape_sequence_files/t0026-CNL.in b/fixtures/escape_sequence_files/t0026-CNL.in similarity index 100% rename from src/test/escape_sequence_files/t0026-CNL.in rename to fixtures/escape_sequence_files/t0026-CNL.in diff --git a/src/test/escape_sequence_files/t0026-CNL.text b/fixtures/escape_sequence_files/t0026-CNL.text similarity index 100% rename from src/test/escape_sequence_files/t0026-CNL.text rename to fixtures/escape_sequence_files/t0026-CNL.text diff --git a/src/test/escape_sequence_files/t0027-CPL.in b/fixtures/escape_sequence_files/t0027-CPL.in similarity index 100% rename from src/test/escape_sequence_files/t0027-CPL.in rename to fixtures/escape_sequence_files/t0027-CPL.in diff --git a/src/test/escape_sequence_files/t0027-CPL.text b/fixtures/escape_sequence_files/t0027-CPL.text similarity index 100% rename from src/test/escape_sequence_files/t0027-CPL.text rename to fixtures/escape_sequence_files/t0027-CPL.text diff --git a/src/test/escape_sequence_files/t0030-HPR.in b/fixtures/escape_sequence_files/t0030-HPR.in similarity index 100% rename from src/test/escape_sequence_files/t0030-HPR.in rename to fixtures/escape_sequence_files/t0030-HPR.in diff --git a/src/test/escape_sequence_files/t0030-HPR.text b/fixtures/escape_sequence_files/t0030-HPR.text similarity index 100% rename from src/test/escape_sequence_files/t0030-HPR.text rename to fixtures/escape_sequence_files/t0030-HPR.text diff --git a/src/test/escape_sequence_files/t0031-HPB.in_ b/fixtures/escape_sequence_files/t0031-HPB.in_ similarity index 100% rename from src/test/escape_sequence_files/t0031-HPB.in_ rename to fixtures/escape_sequence_files/t0031-HPB.in_ diff --git a/src/test/escape_sequence_files/t0031-HPB.text b/fixtures/escape_sequence_files/t0031-HPB.text similarity index 100% rename from src/test/escape_sequence_files/t0031-HPB.text rename to fixtures/escape_sequence_files/t0031-HPB.text diff --git a/src/test/escape_sequence_files/t0032-VPB.in b/fixtures/escape_sequence_files/t0032-VPB.in similarity index 100% rename from src/test/escape_sequence_files/t0032-VPB.in rename to fixtures/escape_sequence_files/t0032-VPB.in diff --git a/src/test/escape_sequence_files/t0032-VPB.text b/fixtures/escape_sequence_files/t0032-VPB.text similarity index 100% rename from src/test/escape_sequence_files/t0032-VPB.text rename to fixtures/escape_sequence_files/t0032-VPB.text diff --git a/src/test/escape_sequence_files/t0033-VPB_scroll.in b/fixtures/escape_sequence_files/t0033-VPB_scroll.in similarity index 100% rename from src/test/escape_sequence_files/t0033-VPB_scroll.in rename to fixtures/escape_sequence_files/t0033-VPB_scroll.in diff --git a/src/test/escape_sequence_files/t0033-VPB_scroll.text b/fixtures/escape_sequence_files/t0033-VPB_scroll.text similarity index 100% rename from src/test/escape_sequence_files/t0033-VPB_scroll.text rename to fixtures/escape_sequence_files/t0033-VPB_scroll.text diff --git a/src/test/escape_sequence_files/t0034-VPR.in b/fixtures/escape_sequence_files/t0034-VPR.in similarity index 100% rename from src/test/escape_sequence_files/t0034-VPR.in rename to fixtures/escape_sequence_files/t0034-VPR.in diff --git a/src/test/escape_sequence_files/t0034-VPR.text b/fixtures/escape_sequence_files/t0034-VPR.text similarity index 100% rename from src/test/escape_sequence_files/t0034-VPR.text rename to fixtures/escape_sequence_files/t0034-VPR.text diff --git a/src/test/escape_sequence_files/t0035-HVP.in b/fixtures/escape_sequence_files/t0035-HVP.in similarity index 100% rename from src/test/escape_sequence_files/t0035-HVP.in rename to fixtures/escape_sequence_files/t0035-HVP.in diff --git a/src/test/escape_sequence_files/t0035-HVP.text b/fixtures/escape_sequence_files/t0035-HVP.text similarity index 100% rename from src/test/escape_sequence_files/t0035-HVP.text rename to fixtures/escape_sequence_files/t0035-HVP.text diff --git a/src/test/escape_sequence_files/t0040-REP.in b/fixtures/escape_sequence_files/t0040-REP.in similarity index 100% rename from src/test/escape_sequence_files/t0040-REP.in rename to fixtures/escape_sequence_files/t0040-REP.in diff --git a/src/test/escape_sequence_files/t0040-REP.text b/fixtures/escape_sequence_files/t0040-REP.text similarity index 100% rename from src/test/escape_sequence_files/t0040-REP.text rename to fixtures/escape_sequence_files/t0040-REP.text diff --git a/src/test/escape_sequence_files/t0050-ICH.in b/fixtures/escape_sequence_files/t0050-ICH.in similarity index 100% rename from src/test/escape_sequence_files/t0050-ICH.in rename to fixtures/escape_sequence_files/t0050-ICH.in diff --git a/src/test/escape_sequence_files/t0050-ICH.text b/fixtures/escape_sequence_files/t0050-ICH.text similarity index 100% rename from src/test/escape_sequence_files/t0050-ICH.text rename to fixtures/escape_sequence_files/t0050-ICH.text diff --git a/src/test/escape_sequence_files/t0051-IL.in b/fixtures/escape_sequence_files/t0051-IL.in similarity index 100% rename from src/test/escape_sequence_files/t0051-IL.in rename to fixtures/escape_sequence_files/t0051-IL.in diff --git a/src/test/escape_sequence_files/t0051-IL.text b/fixtures/escape_sequence_files/t0051-IL.text similarity index 100% rename from src/test/escape_sequence_files/t0051-IL.text rename to fixtures/escape_sequence_files/t0051-IL.text diff --git a/src/test/escape_sequence_files/t0052-DL.in b/fixtures/escape_sequence_files/t0052-DL.in similarity index 100% rename from src/test/escape_sequence_files/t0052-DL.in rename to fixtures/escape_sequence_files/t0052-DL.in diff --git a/src/test/escape_sequence_files/t0052-DL.text b/fixtures/escape_sequence_files/t0052-DL.text similarity index 100% rename from src/test/escape_sequence_files/t0052-DL.text rename to fixtures/escape_sequence_files/t0052-DL.text diff --git a/src/test/escape_sequence_files/t0053-DCH.in b/fixtures/escape_sequence_files/t0053-DCH.in similarity index 100% rename from src/test/escape_sequence_files/t0053-DCH.in rename to fixtures/escape_sequence_files/t0053-DCH.in diff --git a/src/test/escape_sequence_files/t0053-DCH.text b/fixtures/escape_sequence_files/t0053-DCH.text similarity index 100% rename from src/test/escape_sequence_files/t0053-DCH.text rename to fixtures/escape_sequence_files/t0053-DCH.text diff --git a/src/test/escape_sequence_files/t0054-ECH.in b/fixtures/escape_sequence_files/t0054-ECH.in similarity index 100% rename from src/test/escape_sequence_files/t0054-ECH.in rename to fixtures/escape_sequence_files/t0054-ECH.in diff --git a/src/test/escape_sequence_files/t0054-ECH.text b/fixtures/escape_sequence_files/t0054-ECH.text similarity index 100% rename from src/test/escape_sequence_files/t0054-ECH.text rename to fixtures/escape_sequence_files/t0054-ECH.text diff --git a/src/test/escape_sequence_files/t0055-EL.in b/fixtures/escape_sequence_files/t0055-EL.in similarity index 100% rename from src/test/escape_sequence_files/t0055-EL.in rename to fixtures/escape_sequence_files/t0055-EL.in diff --git a/src/test/escape_sequence_files/t0055-EL.text b/fixtures/escape_sequence_files/t0055-EL.text similarity index 100% rename from src/test/escape_sequence_files/t0055-EL.text rename to fixtures/escape_sequence_files/t0055-EL.text diff --git a/src/test/escape_sequence_files/t0056-ED.in b/fixtures/escape_sequence_files/t0056-ED.in similarity index 100% rename from src/test/escape_sequence_files/t0056-ED.in rename to fixtures/escape_sequence_files/t0056-ED.in diff --git a/src/test/escape_sequence_files/t0056-ED.text b/fixtures/escape_sequence_files/t0056-ED.text similarity index 100% rename from src/test/escape_sequence_files/t0056-ED.text rename to fixtures/escape_sequence_files/t0056-ED.text diff --git a/src/test/escape_sequence_files/t0057-ED3.in b/fixtures/escape_sequence_files/t0057-ED3.in similarity index 100% rename from src/test/escape_sequence_files/t0057-ED3.in rename to fixtures/escape_sequence_files/t0057-ED3.in diff --git a/src/test/escape_sequence_files/t0057-ED3.note b/fixtures/escape_sequence_files/t0057-ED3.note similarity index 100% rename from src/test/escape_sequence_files/t0057-ED3.note rename to fixtures/escape_sequence_files/t0057-ED3.note diff --git a/src/test/escape_sequence_files/t0057-ED3.text b/fixtures/escape_sequence_files/t0057-ED3.text similarity index 100% rename from src/test/escape_sequence_files/t0057-ED3.text rename to fixtures/escape_sequence_files/t0057-ED3.text diff --git a/src/test/escape_sequence_files/t0060-DECSC.in b/fixtures/escape_sequence_files/t0060-DECSC.in similarity index 100% rename from src/test/escape_sequence_files/t0060-DECSC.in rename to fixtures/escape_sequence_files/t0060-DECSC.in diff --git a/src/test/escape_sequence_files/t0060-DECSC.text b/fixtures/escape_sequence_files/t0060-DECSC.text similarity index 100% rename from src/test/escape_sequence_files/t0060-DECSC.text rename to fixtures/escape_sequence_files/t0060-DECSC.text diff --git a/src/test/escape_sequence_files/t0061-CSI_s.in b/fixtures/escape_sequence_files/t0061-CSI_s.in similarity index 100% rename from src/test/escape_sequence_files/t0061-CSI_s.in rename to fixtures/escape_sequence_files/t0061-CSI_s.in diff --git a/src/test/escape_sequence_files/t0061-CSI_s.text b/fixtures/escape_sequence_files/t0061-CSI_s.text similarity index 100% rename from src/test/escape_sequence_files/t0061-CSI_s.text rename to fixtures/escape_sequence_files/t0061-CSI_s.text diff --git a/src/test/escape_sequence_files/t0070-DECSTBM_LF.in b/fixtures/escape_sequence_files/t0070-DECSTBM_LF.in similarity index 100% rename from src/test/escape_sequence_files/t0070-DECSTBM_LF.in rename to fixtures/escape_sequence_files/t0070-DECSTBM_LF.in diff --git a/src/test/escape_sequence_files/t0070-DECSTBM_LF.text b/fixtures/escape_sequence_files/t0070-DECSTBM_LF.text similarity index 100% rename from src/test/escape_sequence_files/t0070-DECSTBM_LF.text rename to fixtures/escape_sequence_files/t0070-DECSTBM_LF.text diff --git a/src/test/escape_sequence_files/t0071-DECSTBM_IND.in b/fixtures/escape_sequence_files/t0071-DECSTBM_IND.in similarity index 100% rename from src/test/escape_sequence_files/t0071-DECSTBM_IND.in rename to fixtures/escape_sequence_files/t0071-DECSTBM_IND.in diff --git a/src/test/escape_sequence_files/t0071-DECSTBM_IND.text b/fixtures/escape_sequence_files/t0071-DECSTBM_IND.text similarity index 100% rename from src/test/escape_sequence_files/t0071-DECSTBM_IND.text rename to fixtures/escape_sequence_files/t0071-DECSTBM_IND.text diff --git a/src/test/escape_sequence_files/t0072-DECSTBM_NEL.in b/fixtures/escape_sequence_files/t0072-DECSTBM_NEL.in similarity index 100% rename from src/test/escape_sequence_files/t0072-DECSTBM_NEL.in rename to fixtures/escape_sequence_files/t0072-DECSTBM_NEL.in diff --git a/src/test/escape_sequence_files/t0072-DECSTBM_NEL.text b/fixtures/escape_sequence_files/t0072-DECSTBM_NEL.text similarity index 100% rename from src/test/escape_sequence_files/t0072-DECSTBM_NEL.text rename to fixtures/escape_sequence_files/t0072-DECSTBM_NEL.text diff --git a/src/test/escape_sequence_files/t0073-DECSTBM_RI.in b/fixtures/escape_sequence_files/t0073-DECSTBM_RI.in similarity index 100% rename from src/test/escape_sequence_files/t0073-DECSTBM_RI.in rename to fixtures/escape_sequence_files/t0073-DECSTBM_RI.in diff --git a/src/test/escape_sequence_files/t0073-DECSTBM_RI.text b/fixtures/escape_sequence_files/t0073-DECSTBM_RI.text similarity index 100% rename from src/test/escape_sequence_files/t0073-DECSTBM_RI.text rename to fixtures/escape_sequence_files/t0073-DECSTBM_RI.text diff --git a/src/test/escape_sequence_files/t0074-DECSTBM_SU_SD.in b/fixtures/escape_sequence_files/t0074-DECSTBM_SU_SD.in similarity index 100% rename from src/test/escape_sequence_files/t0074-DECSTBM_SU_SD.in rename to fixtures/escape_sequence_files/t0074-DECSTBM_SU_SD.in diff --git a/src/test/escape_sequence_files/t0074-DECSTBM_SU_SD.text b/fixtures/escape_sequence_files/t0074-DECSTBM_SU_SD.text similarity index 100% rename from src/test/escape_sequence_files/t0074-DECSTBM_SU_SD.text rename to fixtures/escape_sequence_files/t0074-DECSTBM_SU_SD.text diff --git a/src/test/escape_sequence_files/t0075-DECSTBM_CUU_CUD.in b/fixtures/escape_sequence_files/t0075-DECSTBM_CUU_CUD.in similarity index 100% rename from src/test/escape_sequence_files/t0075-DECSTBM_CUU_CUD.in rename to fixtures/escape_sequence_files/t0075-DECSTBM_CUU_CUD.in diff --git a/src/test/escape_sequence_files/t0075-DECSTBM_CUU_CUD.text b/fixtures/escape_sequence_files/t0075-DECSTBM_CUU_CUD.text similarity index 100% rename from src/test/escape_sequence_files/t0075-DECSTBM_CUU_CUD.text rename to fixtures/escape_sequence_files/t0075-DECSTBM_CUU_CUD.text diff --git a/src/test/escape_sequence_files/t0076-DECSTBM_IL_DL.in b/fixtures/escape_sequence_files/t0076-DECSTBM_IL_DL.in similarity index 100% rename from src/test/escape_sequence_files/t0076-DECSTBM_IL_DL.in rename to fixtures/escape_sequence_files/t0076-DECSTBM_IL_DL.in diff --git a/src/test/escape_sequence_files/t0076-DECSTBM_IL_DL.text b/fixtures/escape_sequence_files/t0076-DECSTBM_IL_DL.text similarity index 100% rename from src/test/escape_sequence_files/t0076-DECSTBM_IL_DL.text rename to fixtures/escape_sequence_files/t0076-DECSTBM_IL_DL.text diff --git a/src/test/escape_sequence_files/t0077-DECSTBM_quirks.in b/fixtures/escape_sequence_files/t0077-DECSTBM_quirks.in similarity index 100% rename from src/test/escape_sequence_files/t0077-DECSTBM_quirks.in rename to fixtures/escape_sequence_files/t0077-DECSTBM_quirks.in diff --git a/src/test/escape_sequence_files/t0077-DECSTBM_quirks.text b/fixtures/escape_sequence_files/t0077-DECSTBM_quirks.text similarity index 100% rename from src/test/escape_sequence_files/t0077-DECSTBM_quirks.text rename to fixtures/escape_sequence_files/t0077-DECSTBM_quirks.text diff --git a/src/test/escape_sequence_files/t0080-HT.in b/fixtures/escape_sequence_files/t0080-HT.in similarity index 100% rename from src/test/escape_sequence_files/t0080-HT.in rename to fixtures/escape_sequence_files/t0080-HT.in diff --git a/src/test/escape_sequence_files/t0080-HT.text b/fixtures/escape_sequence_files/t0080-HT.text similarity index 100% rename from src/test/escape_sequence_files/t0080-HT.text rename to fixtures/escape_sequence_files/t0080-HT.text diff --git a/src/test/escape_sequence_files/t0081-TBC.in b/fixtures/escape_sequence_files/t0081-TBC.in similarity index 100% rename from src/test/escape_sequence_files/t0081-TBC.in rename to fixtures/escape_sequence_files/t0081-TBC.in diff --git a/src/test/escape_sequence_files/t0081-TBC.text b/fixtures/escape_sequence_files/t0081-TBC.text similarity index 100% rename from src/test/escape_sequence_files/t0081-TBC.text rename to fixtures/escape_sequence_files/t0081-TBC.text diff --git a/src/test/escape_sequence_files/t0082-HTS.in b/fixtures/escape_sequence_files/t0082-HTS.in similarity index 100% rename from src/test/escape_sequence_files/t0082-HTS.in rename to fixtures/escape_sequence_files/t0082-HTS.in diff --git a/src/test/escape_sequence_files/t0082-HTS.text b/fixtures/escape_sequence_files/t0082-HTS.text similarity index 100% rename from src/test/escape_sequence_files/t0082-HTS.text rename to fixtures/escape_sequence_files/t0082-HTS.text diff --git a/src/test/escape_sequence_files/t0083-CHT.in b/fixtures/escape_sequence_files/t0083-CHT.in similarity index 100% rename from src/test/escape_sequence_files/t0083-CHT.in rename to fixtures/escape_sequence_files/t0083-CHT.in diff --git a/src/test/escape_sequence_files/t0083-CHT.text b/fixtures/escape_sequence_files/t0083-CHT.text similarity index 100% rename from src/test/escape_sequence_files/t0083-CHT.text rename to fixtures/escape_sequence_files/t0083-CHT.text diff --git a/src/test/escape_sequence_files/t0084-CBT.in b/fixtures/escape_sequence_files/t0084-CBT.in similarity index 100% rename from src/test/escape_sequence_files/t0084-CBT.in rename to fixtures/escape_sequence_files/t0084-CBT.in diff --git a/src/test/escape_sequence_files/t0084-CBT.text b/fixtures/escape_sequence_files/t0084-CBT.text similarity index 100% rename from src/test/escape_sequence_files/t0084-CBT.text rename to fixtures/escape_sequence_files/t0084-CBT.text diff --git a/src/test/escape_sequence_files/t0084-CBT.text-xterm b/fixtures/escape_sequence_files/t0084-CBT.text-xterm similarity index 100% rename from src/test/escape_sequence_files/t0084-CBT.text-xterm rename to fixtures/escape_sequence_files/t0084-CBT.text-xterm diff --git a/src/test/escape_sequence_files/t0090-alt_screen.in b/fixtures/escape_sequence_files/t0090-alt_screen.in similarity index 100% rename from src/test/escape_sequence_files/t0090-alt_screen.in rename to fixtures/escape_sequence_files/t0090-alt_screen.in diff --git a/src/test/escape_sequence_files/t0090-alt_screen.text b/fixtures/escape_sequence_files/t0090-alt_screen.text similarity index 100% rename from src/test/escape_sequence_files/t0090-alt_screen.text rename to fixtures/escape_sequence_files/t0090-alt_screen.text diff --git a/src/test/escape_sequence_files/t0091-alt_screen_ED3.in b/fixtures/escape_sequence_files/t0091-alt_screen_ED3.in similarity index 100% rename from src/test/escape_sequence_files/t0091-alt_screen_ED3.in rename to fixtures/escape_sequence_files/t0091-alt_screen_ED3.in diff --git a/src/test/escape_sequence_files/t0091-alt_screen_ED3.text b/fixtures/escape_sequence_files/t0091-alt_screen_ED3.text similarity index 100% rename from src/test/escape_sequence_files/t0091-alt_screen_ED3.text rename to fixtures/escape_sequence_files/t0091-alt_screen_ED3.text diff --git a/src/test/escape_sequence_files/t0092-alt_screen_DECSC.in b/fixtures/escape_sequence_files/t0092-alt_screen_DECSC.in similarity index 100% rename from src/test/escape_sequence_files/t0092-alt_screen_DECSC.in rename to fixtures/escape_sequence_files/t0092-alt_screen_DECSC.in diff --git a/src/test/escape_sequence_files/t0092-alt_screen_DECSC.text b/fixtures/escape_sequence_files/t0092-alt_screen_DECSC.text similarity index 100% rename from src/test/escape_sequence_files/t0092-alt_screen_DECSC.text rename to fixtures/escape_sequence_files/t0092-alt_screen_DECSC.text diff --git a/src/test/escape_sequence_files/t0100-IRM.in b/fixtures/escape_sequence_files/t0100-IRM.in similarity index 100% rename from src/test/escape_sequence_files/t0100-IRM.in rename to fixtures/escape_sequence_files/t0100-IRM.in diff --git a/src/test/escape_sequence_files/t0100-IRM.text b/fixtures/escape_sequence_files/t0100-IRM.text similarity index 100% rename from src/test/escape_sequence_files/t0100-IRM.text rename to fixtures/escape_sequence_files/t0100-IRM.text diff --git a/src/test/escape_sequence_files/t0101-NLM.in b/fixtures/escape_sequence_files/t0101-NLM.in similarity index 100% rename from src/test/escape_sequence_files/t0101-NLM.in rename to fixtures/escape_sequence_files/t0101-NLM.in diff --git a/src/test/escape_sequence_files/t0101-NLM.text b/fixtures/escape_sequence_files/t0101-NLM.text similarity index 100% rename from src/test/escape_sequence_files/t0101-NLM.text rename to fixtures/escape_sequence_files/t0101-NLM.text diff --git a/src/test/escape_sequence_files/t0102-DECAWM.in b/fixtures/escape_sequence_files/t0102-DECAWM.in similarity index 100% rename from src/test/escape_sequence_files/t0102-DECAWM.in rename to fixtures/escape_sequence_files/t0102-DECAWM.in diff --git a/src/test/escape_sequence_files/t0102-DECAWM.text b/fixtures/escape_sequence_files/t0102-DECAWM.text similarity index 100% rename from src/test/escape_sequence_files/t0102-DECAWM.text rename to fixtures/escape_sequence_files/t0102-DECAWM.text diff --git a/src/test/escape_sequence_files/t0103-reverse_wrap.in b/fixtures/escape_sequence_files/t0103-reverse_wrap.in similarity index 100% rename from src/test/escape_sequence_files/t0103-reverse_wrap.in rename to fixtures/escape_sequence_files/t0103-reverse_wrap.in diff --git a/src/test/escape_sequence_files/t0103-reverse_wrap.text b/fixtures/escape_sequence_files/t0103-reverse_wrap.text similarity index 100% rename from src/test/escape_sequence_files/t0103-reverse_wrap.text rename to fixtures/escape_sequence_files/t0103-reverse_wrap.text diff --git a/src/test/escape_sequence_files/t0200-SGR.html b/fixtures/escape_sequence_files/t0200-SGR.html similarity index 100% rename from src/test/escape_sequence_files/t0200-SGR.html rename to fixtures/escape_sequence_files/t0200-SGR.html diff --git a/src/test/escape_sequence_files/t0200-SGR.in_ b/fixtures/escape_sequence_files/t0200-SGR.in_ similarity index 100% rename from src/test/escape_sequence_files/t0200-SGR.in_ rename to fixtures/escape_sequence_files/t0200-SGR.in_ diff --git a/src/test/escape_sequence_files/t0220-SGR_inverse.html b/fixtures/escape_sequence_files/t0220-SGR_inverse.html similarity index 100% rename from src/test/escape_sequence_files/t0220-SGR_inverse.html rename to fixtures/escape_sequence_files/t0220-SGR_inverse.html diff --git a/src/test/escape_sequence_files/t0220-SGR_inverse.in_ b/fixtures/escape_sequence_files/t0220-SGR_inverse.in_ similarity index 100% rename from src/test/escape_sequence_files/t0220-SGR_inverse.in_ rename to fixtures/escape_sequence_files/t0220-SGR_inverse.in_ diff --git a/src/test/escape_sequence_files/t0500-bash_long_line.in b/fixtures/escape_sequence_files/t0500-bash_long_line.in similarity index 100% rename from src/test/escape_sequence_files/t0500-bash_long_line.in rename to fixtures/escape_sequence_files/t0500-bash_long_line.in diff --git a/src/test/escape_sequence_files/t0500-bash_long_line.text b/fixtures/escape_sequence_files/t0500-bash_long_line.text similarity index 100% rename from src/test/escape_sequence_files/t0500-bash_long_line.text rename to fixtures/escape_sequence_files/t0500-bash_long_line.text diff --git a/src/test/escape_sequence_files/t0501-bash_ls.in b/fixtures/escape_sequence_files/t0501-bash_ls.in similarity index 100% rename from src/test/escape_sequence_files/t0501-bash_ls.in rename to fixtures/escape_sequence_files/t0501-bash_ls.in diff --git a/src/test/escape_sequence_files/t0501-bash_ls.text b/fixtures/escape_sequence_files/t0501-bash_ls.text similarity index 100% rename from src/test/escape_sequence_files/t0501-bash_ls.text rename to fixtures/escape_sequence_files/t0501-bash_ls.text diff --git a/src/test/escape_sequence_files/t0502-bash_ls_color.in b/fixtures/escape_sequence_files/t0502-bash_ls_color.in similarity index 100% rename from src/test/escape_sequence_files/t0502-bash_ls_color.in rename to fixtures/escape_sequence_files/t0502-bash_ls_color.in diff --git a/src/test/escape_sequence_files/t0502-bash_ls_color.text b/fixtures/escape_sequence_files/t0502-bash_ls_color.text similarity index 100% rename from src/test/escape_sequence_files/t0502-bash_ls_color.text rename to fixtures/escape_sequence_files/t0502-bash_ls_color.text diff --git a/src/test/escape_sequence_files/t0503-zsh_ls_color.in b/fixtures/escape_sequence_files/t0503-zsh_ls_color.in similarity index 100% rename from src/test/escape_sequence_files/t0503-zsh_ls_color.in rename to fixtures/escape_sequence_files/t0503-zsh_ls_color.in diff --git a/src/test/escape_sequence_files/t0503-zsh_ls_color.text b/fixtures/escape_sequence_files/t0503-zsh_ls_color.text similarity index 100% rename from src/test/escape_sequence_files/t0503-zsh_ls_color.text rename to fixtures/escape_sequence_files/t0503-zsh_ls_color.text diff --git a/src/test/escape_sequence_files/t0504-vim.in b/fixtures/escape_sequence_files/t0504-vim.in similarity index 100% rename from src/test/escape_sequence_files/t0504-vim.in rename to fixtures/escape_sequence_files/t0504-vim.in diff --git a/src/test/escape_sequence_files/t0504-vim.text b/fixtures/escape_sequence_files/t0504-vim.text similarity index 100% rename from src/test/escape_sequence_files/t0504-vim.text rename to fixtures/escape_sequence_files/t0504-vim.text diff --git a/gulpfile.js b/gulpfile.js index 6e211023..8469c66c 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -121,7 +121,12 @@ gulp.task('instrument-test', function () { }); gulp.task('mocha', ['instrument-test'], function () { - return gulp.src([`${outDir}/*test.js`, `${outDir}/**/*test.js`], {read: false}) + return gulp.src([ + `${outDir}/*test.js`, + `${outDir}/**/*test.js`, + `${outDir}/*integration.js`, + `${outDir}/**/*integration.js` + ], {read: false}) .pipe(mocha()) .once('error', () => process.exit(1)) .pipe(istanbul.writeReports()); diff --git a/src/test/escape-sequences-test.ts b/src/Terminal.integration.ts similarity index 94% rename from src/test/escape-sequences-test.ts rename to src/Terminal.integration.ts index 554298d0..2155d0e0 100644 --- a/src/test/escape-sequences-test.ts +++ b/src/Terminal.integration.ts @@ -1,15 +1,16 @@ /** * @license MIT * - * This file contains integration tests for xterm.js + * This file contains integration tests for xterm.js. */ import * as glob from 'glob'; import * as fs from 'fs'; import * as os from 'os'; +import * as path from 'path'; import * as pty from 'node-pty'; -import { Terminal } from '../Terminal'; -import { CHAR_DATA_CHAR_INDEX } from '../Buffer'; +import { Terminal } from './Terminal'; +import { CHAR_DATA_CHAR_INDEX } from './Buffer'; let primitive_pty: any; @@ -95,7 +96,7 @@ if (os.platform() !== 'win32') { // omit stack trace for escape sequence files Error.stackTraceLimit = 0; - let files = glob.sync('**/escape_sequence_files/*.in'); + let files = glob.sync('**/escape_sequence_files/*.in', { cwd: path.join(__dirname, '..')}); // only successful tests for now let skip = [ 10, 16, 17, 19, 32, 33, 34, 35, 36, 39, From b42a9039cccabfa84797a915817523dda7f567e2 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 7 Aug 2017 22:40:15 -0700 Subject: [PATCH 037/378] Rename TestUtils.ts to TestUtils.test.ts This will allow it to be excluded when we do #863 --- src/Buffer.test.ts | 2 +- src/BufferSet.test.ts | 2 +- src/InputHandler.test.ts | 2 +- src/SelectionManager.test.ts | 2 +- src/SelectionModel.test.ts | 2 +- src/Terminal.test.ts | 2 +- src/utils/{TestUtils.ts => TestUtils.test.ts} | 0 7 files changed, 6 insertions(+), 6 deletions(-) rename src/utils/{TestUtils.ts => TestUtils.test.ts} (100%) diff --git a/src/Buffer.test.ts b/src/Buffer.test.ts index 99bd6d0f..5ac71996 100644 --- a/src/Buffer.test.ts +++ b/src/Buffer.test.ts @@ -6,7 +6,7 @@ import { assert } from 'chai'; import { ITerminal } from './Interfaces'; import { Buffer } from './Buffer'; import { CircularList } from './utils/CircularList'; -import { MockTerminal } from './utils/TestUtils'; +import { MockTerminal } from './utils/TestUtils.test'; const INIT_COLS = 80; const INIT_ROWS = 24; diff --git a/src/BufferSet.test.ts b/src/BufferSet.test.ts index 97c07995..320241ca 100644 --- a/src/BufferSet.test.ts +++ b/src/BufferSet.test.ts @@ -6,7 +6,7 @@ import { assert } from 'chai'; import { ITerminal } from './Interfaces'; import { BufferSet } from './BufferSet'; import { Buffer } from './Buffer'; -import { MockTerminal } from './utils/TestUtils'; +import { MockTerminal } from './utils/TestUtils.test'; describe('BufferSet', () => { let terminal: ITerminal; diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index 043b7919..f6e07f7e 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -5,7 +5,7 @@ import { assert } from 'chai'; import { InputHandler } from './InputHandler'; import { wcwidth } from './InputHandler'; -import { MockInputHandlingTerminal } from './utils/TestUtils'; +import { MockInputHandlingTerminal } from './utils/TestUtils.test'; describe('InputHandler', () => { describe('save and restore cursor', () => { diff --git a/src/SelectionManager.test.ts b/src/SelectionManager.test.ts index c7fd9aef..4774b3bb 100644 --- a/src/SelectionManager.test.ts +++ b/src/SelectionManager.test.ts @@ -10,7 +10,7 @@ import { CircularList } from './utils/CircularList'; import { SelectionManager } from './SelectionManager'; import { SelectionModel } from './SelectionModel'; import { BufferSet } from './BufferSet'; -import { MockTerminal } from './utils/TestUtils'; +import { MockTerminal } from './utils/TestUtils.test'; import { LineData } from './Types'; class TestSelectionManager extends SelectionManager { diff --git a/src/SelectionModel.test.ts b/src/SelectionModel.test.ts index 9da84478..6c750ed2 100644 --- a/src/SelectionModel.test.ts +++ b/src/SelectionModel.test.ts @@ -6,7 +6,7 @@ import { assert } from 'chai'; import { ITerminal } from './Interfaces'; import { SelectionModel } from './SelectionModel'; import {BufferSet} from './BufferSet'; -import { MockTerminal } from './utils/TestUtils'; +import { MockTerminal } from './utils/TestUtils.test'; class TestSelectionModel extends SelectionModel { constructor( diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index 753733c2..f045a37e 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -1,6 +1,6 @@ import { assert, expect } from 'chai'; import { Terminal } from './Terminal'; -import { MockViewport, MockCompositionHelper } from './utils/TestUtils'; +import { MockViewport, MockCompositionHelper } from './utils/TestUtils.test'; import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from './Buffer'; class TestTerminal extends Terminal { diff --git a/src/utils/TestUtils.ts b/src/utils/TestUtils.test.ts similarity index 100% rename from src/utils/TestUtils.ts rename to src/utils/TestUtils.test.ts From a0ce9a79a9daf8bca5038d68be46ec8f8bdb35b9 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 7 Aug 2017 23:14:14 -0700 Subject: [PATCH 038/378] Build and export TS declaration files Fixes #866 --- gulpfile.js | 5 ++++- package.json | 5 ++++- src/Parser.ts | 2 +- tsconfig.json | 4 ++-- typings.json | 6 ------ 5 files changed, 11 insertions(+), 11 deletions(-) delete mode 100644 typings.json diff --git a/gulpfile.js b/gulpfile.js index 6e211023..72c74651 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -39,7 +39,10 @@ gulp.task('tsc', function () { // Build all TypeScript files (including tests) to ${outDir}/, based on the configuration defined in // `tsconfig.json`. let tsResult = tsProject.src().pipe(sourcemaps.init()).pipe(tsProject()); - let tsc = tsResult.js.pipe(sourcemaps.write('.', {includeContent: false, sourceRoot: ''})).pipe(gulp.dest(outDir)); + let tsc = merge( + tsResult.js.pipe(sourcemaps.write('.', {includeContent: false, sourceRoot: ''})).pipe(gulp.dest(outDir)), + tsResult.dts.pipe(sourcemaps.write('.', {includeContent: false, sourceRoot: ''})).pipe(gulp.dest(outDir)) + ); fs.emptyDirSync(`${outDir}/addons/search`); let tsResultSearchAddon = tsProjectSearchAddon.src().pipe(sourcemaps.init()).pipe(tsProjectSearchAddon()); diff --git a/package.json b/package.json index d0ba5c01..a5f0bd75 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "test", ".gitignore" ], - "main": "lib/xterm.js", + "main": "lib/Terminal.js", + "types": "lib/Terminal.d.ts", "repository": "https://github.com/sourcelair/xterm.js", "license": "MIT", "files": [ @@ -21,8 +22,10 @@ "dist/**/*.js.map", "lib/*.css", "lib/**/*.css", + "lib/*.d.ts", "lib/*.js", "lib/*.js.map", + "lib/**/*.d.ts", "lib/**/*.js", "lib/**/*.js.map", "src/*.css", diff --git a/src/Parser.ts b/src/Parser.ts index 90d413a7..3f05992b 100644 --- a/src/Parser.ts +++ b/src/Parser.ts @@ -148,7 +148,7 @@ csiStateHandler['s'] = (handler, params) => handler.saveCursor(params); csiStateHandler['u'] = (handler, params) => handler.restoreCursor(params); csiStateHandler[C0.CAN] = (handler, params, prefix, postfix, parser) => parser.setState(ParserState.NORMAL); -enum ParserState { +export enum ParserState { NORMAL = 0, ESCAPED = 1, CSI_PARAM = 2, diff --git a/tsconfig.json b/tsconfig.json index 2e9703dd..a3aa790c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,10 +3,10 @@ "module": "commonjs", "target": "es5", "rootDir": "src", - "allowJs": true, "outDir": "lib", "sourceMap": true, - "removeComments": true + "removeComments": true, + "declaration": true }, "include": [ "src/**/*" diff --git a/typings.json b/typings.json deleted file mode 100644 index 8210c007..00000000 --- a/typings.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "xterm", - "globalDependencies": { - "node": "registry:env/node#6.0.0+20160918225031" - } -} From 3b787824d37280a8e8e03c3df3b8be513173880b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 8 Aug 2017 00:08:04 -0700 Subject: [PATCH 039/378] Fix import for CommonJS consumers CommonJS now imports the Terminal object directly as the trick in xterm.ts wasn't working properly. Fixes #865 --- README.md | 12 ++++++++++++ src/Terminal.ts | 9 +++++++++ src/addons/attach/attach.js | 12 ++++++------ src/addons/fit/fit.js | 8 ++++---- src/addons/fullscreen/fullscreen.js | 8 ++++---- src/addons/search/search.ts | 2 +- src/addons/terminado/terminado.js | 10 +++++----- src/handlers/Clipboard.test.ts | 2 +- src/xterm.ts | 13 ++----------- 9 files changed, 44 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 5a5f8e51..e1217bcd 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,18 @@ var xterm = new Terminal(); xterm.fit(); ``` +## CommonJS + +Importing xterm.js in a CommonJS environment (eg. [Electron](https://electron.atom.io/)) can be done like so: + +```ts +// JavaScript +var Terminal = require('xterm').Terminal; + +// TypeScript +import { Terminal } from 'xterm'; +``` + ## Releases Xterm.js follows a monthly release cycle roughly. diff --git a/src/Terminal.ts b/src/Terminal.ts index 31eaef8a..d8e88b2d 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -8,6 +8,15 @@ * has been extended to include xterm CSI codes, among * other features. * @license MIT + * + * Terminal Emulation References: + * http://vt100.net/ + * http://invisible-island.net/xterm/ctlseqs/ctlseqs.txt + * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html + * http://invisible-island.net/vttest/ + * http://www.inwap.com/pdp10/ansicode.txt + * http://linux.die.net/man/4/console_codes + * http://linux.die.net/man/7/urxvt */ import { BufferSet } from './BufferSet'; diff --git a/src/addons/attach/attach.js b/src/addons/attach/attach.js index c2a7989b..34dc4f68 100644 --- a/src/addons/attach/attach.js +++ b/src/addons/attach/attach.js @@ -9,7 +9,7 @@ /* * CommonJS environment */ - module.exports = attach(require('../../xterm')); + module.exports = attach(require('../../Terminal').Terminal); } else if (typeof define == 'function') { /* * Require.js is available @@ -21,7 +21,7 @@ */ attach(window.Terminal); } -})(function (Xterm) { +})(function (Terminal) { 'use strict'; var exports = {}; @@ -29,7 +29,7 @@ /** * Attaches the given terminal to the given socket. * - * @param {Xterm} term - The terminal to be attached to the given socket. + * @param {Terminal} term - The terminal to be attached to the given socket. * @param {WebSocket} socket - The socket to attach the current terminal. * @param {boolean} bidirectional - Whether the terminal should send data * to the socket as well. @@ -82,7 +82,7 @@ /** * Detaches the given terminal from the given socket * - * @param {Xterm} term - The terminal to be detached from the given socket. + * @param {Terminal} term - The terminal to be detached from the given socket. * @param {WebSocket} socket - The socket from which to detach the current * terminal. */ @@ -108,7 +108,7 @@ * should happen instantly or at a maximum * frequency of 1 rendering per 10ms. */ - Xterm.prototype.attach = function (socket, bidirectional, buffered) { + Terminal.prototype.attach = function (socket, bidirectional, buffered) { return exports.attach(this, socket, bidirectional, buffered); }; @@ -118,7 +118,7 @@ * @param {WebSocket} socket - The socket from which to detach the current * terminal. */ - Xterm.prototype.detach = function (socket) { + Terminal.prototype.detach = function (socket) { return exports.detach(this, socket); }; diff --git a/src/addons/fit/fit.js b/src/addons/fit/fit.js index 46b79e9b..da66802d 100644 --- a/src/addons/fit/fit.js +++ b/src/addons/fit/fit.js @@ -16,7 +16,7 @@ /* * CommonJS environment */ - module.exports = fit(require('../../xterm')); + module.exports = fit(require('../../Terminal').Terminal); } else if (typeof define == 'function') { /* * Require.js is available @@ -28,7 +28,7 @@ */ fit(window.Terminal); } -})(function (Xterm) { +})(function (Terminal) { var exports = {}; exports.proposeGeometry = function (term) { @@ -74,11 +74,11 @@ } }; - Xterm.prototype.proposeGeometry = function () { + Terminal.prototype.proposeGeometry = function () { return exports.proposeGeometry(this); }; - Xterm.prototype.fit = function () { + Terminal.prototype.fit = function () { return exports.fit(this); }; diff --git a/src/addons/fullscreen/fullscreen.js b/src/addons/fullscreen/fullscreen.js index e8e34ea3..344669f6 100644 --- a/src/addons/fullscreen/fullscreen.js +++ b/src/addons/fullscreen/fullscreen.js @@ -8,7 +8,7 @@ /* * CommonJS environment */ - module.exports = fullscreen(require('../../xterm')); + module.exports = fullscreen(require('../../Terminal').Terminal); } else if (typeof define == 'function') { /* * Require.js is available @@ -20,12 +20,12 @@ */ fullscreen(window.Terminal); } -})(function (Xterm) { +})(function (Terminal) { var exports = {}; /** * Toggle the given terminal's fullscreen mode. - * @param {Xterm} term - The terminal to toggle full screen mode + * @param {Terminal} term - The terminal to toggle full screen mode * @param {boolean} fullscreen - Toggle fullscreen on (true) or off (false) */ exports.toggleFullScreen = function (term, fullscreen) { @@ -42,7 +42,7 @@ term.element.classList[fn]('fullscreen'); }; - Xterm.prototype.toggleFullscreen = function (fullscreen) { + Terminal.prototype.toggleFullscreen = function (fullscreen) { exports.toggleFullScreen(this, fullscreen); }; diff --git a/src/addons/search/search.ts b/src/addons/search/search.ts index 0b506b69..879ca4ee 100644 --- a/src/addons/search/search.ts +++ b/src/addons/search/search.ts @@ -20,7 +20,7 @@ declare var window: any; /** * CommonJS environment */ - module.exports = addon(require('../../xterm')); + module.exports = addon(require('../../Terminal').Terminal); } else if (typeof define === 'function') { /** * Require.js is available diff --git a/src/addons/terminado/terminado.js b/src/addons/terminado/terminado.js index 86e6ea2e..d1414c86 100644 --- a/src/addons/terminado/terminado.js +++ b/src/addons/terminado/terminado.js @@ -10,7 +10,7 @@ /* * CommonJS environment */ - module.exports = attach(require('../../xterm')); + module.exports = attach(require('../../Terminal').Terminal); } else if (typeof define == 'function') { /* * Require.js is available @@ -22,7 +22,7 @@ */ attach(window.Terminal); } -})(function (Xterm) { +})(function (Terminal) { 'use strict'; var exports = {}; @@ -30,7 +30,7 @@ /** * Attaches the given terminal to the given socket. * - * @param {Xterm} term - The terminal to be attached to the given socket. + * @param {Terminal} term - The terminal to be attached to the given socket. * @param {WebSocket} socket - The socket to attach the current terminal. * @param {boolean} bidirectional - Whether the terminal should send data * to the socket as well. @@ -117,7 +117,7 @@ * should happen instantly or at a maximum * frequency of 1 rendering per 10ms. */ - Xterm.prototype.terminadoAttach = function (socket, bidirectional, buffered) { + Terminal.prototype.terminadoAttach = function (socket, bidirectional, buffered) { return exports.terminadoAttach(this, socket, bidirectional, buffered); }; @@ -127,7 +127,7 @@ * @param {WebSocket} socket - The socket from which to detach the current * terminal. */ - Xterm.prototype.terminadoDetach = function (socket) { + Terminal.prototype.terminadoDetach = function (socket) { return exports.terminadoDetach(this, socket); }; diff --git a/src/handlers/Clipboard.test.ts b/src/handlers/Clipboard.test.ts index e5bc3581..bd2cea03 100644 --- a/src/handlers/Clipboard.test.ts +++ b/src/handlers/Clipboard.test.ts @@ -1,5 +1,5 @@ import { assert } from 'chai'; -import * as Terminal from '../xterm'; +import * as Terminal from '../Terminal'; import * as Clipboard from './Clipboard'; describe('evaluatePastedTextProcessing', () => { diff --git a/src/xterm.ts b/src/xterm.ts index 49e44151..50ac5592 100644 --- a/src/xterm.ts +++ b/src/xterm.ts @@ -1,18 +1,9 @@ /** * @license MIT + * + * This file is the entry point for browserify. */ import { Terminal } from './Terminal'; -/** - * Terminal Emulation References: - * http://vt100.net/ - * http://invisible-island.net/xterm/ctlseqs/ctlseqs.txt - * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html - * http://invisible-island.net/vttest/ - * http://www.inwap.com/pdp10/ansicode.txt - * http://linux.die.net/man/4/console_codes - * http://linux.die.net/man/7/urxvt - */ - module.exports = Terminal; From f21d5f25ecaa6c1c843ee6b2761c7958e1eb16ed Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 8 Aug 2017 00:29:22 -0700 Subject: [PATCH 040/378] Fix search addon after TS refactor Fixes #867 --- src/addons/search/SearchHelper.ts | 5 ++--- src/addons/search/search.ts | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/addons/search/SearchHelper.ts b/src/addons/search/SearchHelper.ts index 43d6dc18..26e4e8a7 100644 --- a/src/addons/search/SearchHelper.ts +++ b/src/addons/search/SearchHelper.ts @@ -15,7 +15,7 @@ interface ISearchResult { * A class that knows how to search the terminal and how to display the results. */ export class SearchHelper { - constructor(private _terminal: any, private _translateBufferLineToString: any) { + constructor(private _terminal: any) { // TODO: Search for multiple instances on 1 line // TODO: Don't use the actual selection, instead use a "find selection" so multiple instances can be highlighted // TODO: Highlight other instances in the viewport @@ -111,8 +111,7 @@ export class SearchHelper { * @return The search result if it was found. */ private _findInLine(term: string, y: number): ISearchResult { - const bufferLine = this._terminal.buffer.lines.get(y); - const lowerStringLine = this._translateBufferLineToString(bufferLine, true).toLowerCase(); + const lowerStringLine = this._terminal.buffer.translateBufferLineToString(y, true).toLowerCase(); const lowerTerm = term.toLowerCase(); const searchIndex = lowerStringLine.indexOf(lowerTerm); if (searchIndex >= 0) { diff --git a/src/addons/search/search.ts b/src/addons/search/search.ts index 879ca4ee..34223c4e 100644 --- a/src/addons/search/search.ts +++ b/src/addons/search/search.ts @@ -36,7 +36,7 @@ declare var window: any; */ Terminal.prototype.findNext = function(term: string): boolean { if (!this._searchHelper) { - this.searchHelper = new SearchHelper(this, Terminal.translateBufferLineToString); + this.searchHelper = new SearchHelper(this); } return (this.searchHelper).findNext(term); }; @@ -49,7 +49,7 @@ declare var window: any; */ Terminal.prototype.findPrevious = function(term: string): boolean { if (!this._searchHelper) { - this.searchHelper = new SearchHelper(this, Terminal.translateBufferLineToString); + this.searchHelper = new SearchHelper(this); } return (this.searchHelper).findPrevious(term); }; From 9659f29e5015df417be211806a63c23e649bacb1 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 8 Aug 2017 00:59:14 -0700 Subject: [PATCH 041/378] Remove Terminal.open focus param --- src/Terminal.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 493e48bc..58fde2f9 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -649,9 +649,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * Opens the terminal within an element. * * @param {HTMLElement} parent The element to create the terminal within. - * @param {boolean} focus Focus the terminal, after it gets instantiated in the DOM */ - private open(parent: HTMLElement, focus?: boolean): void { + public open(parent: HTMLElement): void { let i = 0; let div; @@ -759,10 +758,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT // Initialize global actions that need to be taken on the document. this.initGlobal(); - if (focus) { - this.focus(); - } - // Listen for mouse events and translate // them into terminal mouse protocols. this.bindMouse(); From 0902b92f79c712efb3bb6151373d85882046bb5c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 8 Aug 2017 01:05:24 -0700 Subject: [PATCH 042/378] Revert "Disable selection override on Windows/Linux" This reverts commit 3479614c732305b4174bf438e1ac3022aa3b3aa4. --- src/SelectionManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index ee03f79a..f3aa5f82 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -327,7 +327,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager // Allow selection when using a specific modifier key, even when disabled if (!this._enabled) { - const shouldForceSelection = Browser.isMac && event.altKey; + const shouldForceSelection = Browser.isMac ? event.altKey : event.shiftKey; if (!shouldForceSelection) { return; From 184f49d9094d9f65a8e79e14c7d06cf0578bcbea Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 12 Aug 2017 13:54:34 -0700 Subject: [PATCH 043/378] Remove dist folder Part of #406 --- .gitignore | 5 + dist/addons/attach/attach.js | 126 - dist/addons/fit/fit.js | 86 - dist/addons/fullscreen/fullscreen.css | 10 - dist/addons/fullscreen/fullscreen.js | 50 - dist/addons/linkify/linkify.js | 207 - dist/addons/terminado/terminado.js | 135 - dist/xterm.css | 2261 ----------- dist/xterm.js | 5131 ------------------------- dist/xterm.js.map | 1 - 10 files changed, 5 insertions(+), 8007 deletions(-) delete mode 100644 dist/addons/attach/attach.js delete mode 100644 dist/addons/fit/fit.js delete mode 100644 dist/addons/fullscreen/fullscreen.css delete mode 100644 dist/addons/fullscreen/fullscreen.js delete mode 100644 dist/addons/linkify/linkify.js delete mode 100644 dist/addons/terminado/terminado.js delete mode 100644 dist/xterm.css delete mode 100644 dist/xterm.js delete mode 100644 dist/xterm.js.map diff --git a/.gitignore b/.gitignore index cb7551a6..f9811493 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,8 @@ build/ # Directories needed for code coverage /coverage/ + +# Keep legacy files out of the repo, this can be removed when we merge v3 into master +dist/ +src/utils/TestUtils.ts +src/xterm.js diff --git a/dist/addons/attach/attach.js b/dist/addons/attach/attach.js deleted file mode 100644 index c2a7989b..00000000 --- a/dist/addons/attach/attach.js +++ /dev/null @@ -1,126 +0,0 @@ -/** - * Implements the attach method, that attaches the terminal to a WebSocket stream. - * @module xterm/addons/attach/attach - * @license MIT - */ - -(function (attach) { - if (typeof exports === 'object' && typeof module === 'object') { - /* - * CommonJS environment - */ - module.exports = attach(require('../../xterm')); - } else if (typeof define == 'function') { - /* - * Require.js is available - */ - define(['../../xterm'], attach); - } else { - /* - * Plain browser environment - */ - attach(window.Terminal); - } -})(function (Xterm) { - 'use strict'; - - var exports = {}; - - /** - * Attaches the given terminal to the given socket. - * - * @param {Xterm} term - The terminal to be attached to the given socket. - * @param {WebSocket} socket - The socket to attach the current terminal. - * @param {boolean} bidirectional - Whether the terminal should send data - * to the socket as well. - * @param {boolean} buffered - Whether the rendering of incoming data - * should happen instantly or at a maximum - * frequency of 1 rendering per 10ms. - */ - exports.attach = function (term, socket, bidirectional, buffered) { - bidirectional = (typeof bidirectional == 'undefined') ? true : bidirectional; - term.socket = socket; - - term._flushBuffer = function () { - term.write(term._attachSocketBuffer); - term._attachSocketBuffer = null; - clearTimeout(term._attachSocketBufferTimer); - term._attachSocketBufferTimer = null; - }; - - term._pushToBuffer = function (data) { - if (term._attachSocketBuffer) { - term._attachSocketBuffer += data; - } else { - term._attachSocketBuffer = data; - setTimeout(term._flushBuffer, 10); - } - }; - - term._getMessage = function (ev) { - if (buffered) { - term._pushToBuffer(ev.data); - } else { - term.write(ev.data); - } - }; - - term._sendData = function (data) { - socket.send(data); - }; - - socket.addEventListener('message', term._getMessage); - - if (bidirectional) { - term.on('data', term._sendData); - } - - socket.addEventListener('close', term.detach.bind(term, socket)); - socket.addEventListener('error', term.detach.bind(term, socket)); - }; - - /** - * Detaches the given terminal from the given socket - * - * @param {Xterm} term - The terminal to be detached from the given socket. - * @param {WebSocket} socket - The socket from which to detach the current - * terminal. - */ - exports.detach = function (term, socket) { - term.off('data', term._sendData); - - socket = (typeof socket == 'undefined') ? term.socket : socket; - - if (socket) { - socket.removeEventListener('message', term._getMessage); - } - - delete term.socket; - }; - - /** - * Attaches the current terminal to the given socket - * - * @param {WebSocket} socket - The socket to attach the current terminal. - * @param {boolean} bidirectional - Whether the terminal should send data - * to the socket as well. - * @param {boolean} buffered - Whether the rendering of incoming data - * should happen instantly or at a maximum - * frequency of 1 rendering per 10ms. - */ - Xterm.prototype.attach = function (socket, bidirectional, buffered) { - return exports.attach(this, socket, bidirectional, buffered); - }; - - /** - * Detaches the current terminal from the given socket. - * - * @param {WebSocket} socket - The socket from which to detach the current - * terminal. - */ - Xterm.prototype.detach = function (socket) { - return exports.detach(this, socket); - }; - - return exports; -}); diff --git a/dist/addons/fit/fit.js b/dist/addons/fit/fit.js deleted file mode 100644 index 46b79e9b..00000000 --- a/dist/addons/fit/fit.js +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Fit terminal columns and rows to the dimensions of its DOM element. - * - * ## Approach - * - Rows: Truncate the division of the terminal parent element height by the terminal row height. - * - * - Columns: Truncate the division of the terminal parent element width by the terminal character - * width (apply display: inline at the terminal row and truncate its width with the current - * number of columns). - * @module xterm/addons/fit/fit - * @license MIT - */ - -(function (fit) { - if (typeof exports === 'object' && typeof module === 'object') { - /* - * CommonJS environment - */ - module.exports = fit(require('../../xterm')); - } else if (typeof define == 'function') { - /* - * Require.js is available - */ - define(['../../xterm'], fit); - } else { - /* - * Plain browser environment - */ - fit(window.Terminal); - } -})(function (Xterm) { - var exports = {}; - - exports.proposeGeometry = function (term) { - if (!term.element.parentElement) { - return null; - } - var parentElementStyle = window.getComputedStyle(term.element.parentElement), - parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height')), - parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')) - 17), - elementStyle = window.getComputedStyle(term.element), - elementPaddingVer = parseInt(elementStyle.getPropertyValue('padding-top')) + parseInt(elementStyle.getPropertyValue('padding-bottom')), - elementPaddingHor = parseInt(elementStyle.getPropertyValue('padding-right')) + parseInt(elementStyle.getPropertyValue('padding-left')), - availableHeight = parentElementHeight - elementPaddingVer, - availableWidth = parentElementWidth - elementPaddingHor, - container = term.rowContainer, - subjectRow = term.rowContainer.firstElementChild, - contentBuffer = subjectRow.innerHTML, - characterHeight, - rows, - characterWidth, - cols, - geometry; - - subjectRow.style.display = 'inline'; - subjectRow.innerHTML = 'W'; // Common character for measuring width, although on monospace - characterWidth = subjectRow.getBoundingClientRect().width; - subjectRow.style.display = ''; // Revert style before calculating height, since they differ. - characterHeight = subjectRow.getBoundingClientRect().height; - subjectRow.innerHTML = contentBuffer; - - rows = parseInt(availableHeight / characterHeight); - cols = parseInt(availableWidth / characterWidth); - - geometry = {cols: cols, rows: rows}; - return geometry; - }; - - exports.fit = function (term) { - var geometry = exports.proposeGeometry(term); - - if (geometry) { - term.resize(geometry.cols, geometry.rows); - } - }; - - Xterm.prototype.proposeGeometry = function () { - return exports.proposeGeometry(this); - }; - - Xterm.prototype.fit = function () { - return exports.fit(this); - }; - - return exports; -}); diff --git a/dist/addons/fullscreen/fullscreen.css b/dist/addons/fullscreen/fullscreen.css deleted file mode 100644 index 60e8c511..00000000 --- a/dist/addons/fullscreen/fullscreen.css +++ /dev/null @@ -1,10 +0,0 @@ -.xterm.fullscreen { - position: fixed; - top: 0; - bottom: 0; - left: 0; - right: 0; - width: auto; - height: auto; - z-index: 255; -} diff --git a/dist/addons/fullscreen/fullscreen.js b/dist/addons/fullscreen/fullscreen.js deleted file mode 100644 index e8e34ea3..00000000 --- a/dist/addons/fullscreen/fullscreen.js +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Fullscreen addon for xterm.js - * @module xterm/addons/fullscreen/fullscreen - * @license MIT - */ -(function (fullscreen) { - if (typeof exports === 'object' && typeof module === 'object') { - /* - * CommonJS environment - */ - module.exports = fullscreen(require('../../xterm')); - } else if (typeof define == 'function') { - /* - * Require.js is available - */ - define(['../../xterm'], fullscreen); - } else { - /* - * Plain browser environment - */ - fullscreen(window.Terminal); - } -})(function (Xterm) { - var exports = {}; - - /** - * Toggle the given terminal's fullscreen mode. - * @param {Xterm} term - The terminal to toggle full screen mode - * @param {boolean} fullscreen - Toggle fullscreen on (true) or off (false) - */ - exports.toggleFullScreen = function (term, fullscreen) { - var fn; - - if (typeof fullscreen == 'undefined') { - fn = (term.element.classList.contains('fullscreen')) ? 'remove' : 'add'; - } else if (!fullscreen) { - fn = 'remove'; - } else { - fn = 'add'; - } - - term.element.classList[fn]('fullscreen'); - }; - - Xterm.prototype.toggleFullscreen = function (fullscreen) { - exports.toggleFullScreen(this, fullscreen); - }; - - return exports; -}); diff --git a/dist/addons/linkify/linkify.js b/dist/addons/linkify/linkify.js deleted file mode 100644 index d2391026..00000000 --- a/dist/addons/linkify/linkify.js +++ /dev/null @@ -1,207 +0,0 @@ -/** - * Methods for turning URL subscrings in the terminal's content into links (`a` DOM elements). - * @module xterm/addons/linkify/linkify - * @license MIT - */ - -(function (linkify) { - if (typeof exports === 'object' && typeof module === 'object') { - /* - * CommonJS environment - */ - module.exports = linkify(require('../../xterm')); - } else if (typeof define == 'function') { - /* - * Require.js is available - */ - define(['../../xterm'], linkify); - } else { - /* - * Plain browser environment - */ - linkify(window.Terminal); - } -})(function (Xterm) { - 'use strict'; - - var exports = {}, - protocolClause = '(https?:\\/\\/)', - domainCharacterSet = '[\\da-z\\.-]+', - negatedDomainCharacterSet = '[^\\da-z\\.-]+', - domainBodyClause = '(' + domainCharacterSet + ')', - tldClause = '([a-z\\.]{2,6})', - ipClause = '((\\d{1,3}\\.){3}\\d{1,3})', - portClause = '(:\\d{1,5})', - hostClause = '((' + domainBodyClause + '\\.' + tldClause + ')|' + ipClause + ')' + portClause + '?', - pathClause = '(\\/[\\/\\w\\.-]*)*', - negatedPathCharacterSet = '[^\\/\\w\\.-]+', - bodyClause = hostClause + pathClause, - start = '(?:^|' + negatedDomainCharacterSet + ')(', - end = ')($|' + negatedPathCharacterSet + ')', - lenientUrlClause = start + protocolClause + '?' + bodyClause + end, - strictUrlClause = start + protocolClause + bodyClause + end, - lenientUrlRegex = new RegExp(lenientUrlClause), - strictUrlRegex = new RegExp(strictUrlClause); - - /** - * Converts all valid URLs found in the given terminal line into - * hyperlinks. The terminal line can be either the HTML element itself - * or the index of the termina line in the children of the terminal - * rows container. - * - * @param {Xterm} terminal - The terminal that owns the given line. - * @param {number|HTMLDivElement} line - The terminal line that should get - * "linkified". - * @param {boolean} lenient - The regex type that will be used to identify links. If lenient is - * false, the regex requires a protocol clause. Defaults to true. - * @param {string} target - Sets target="" attribute with value provided to links. - * Default doesn't set target attribute - * @emits linkify - * @emits linkify:line - */ - exports.linkifyTerminalLine = function (terminal, line, lenient, target) { - if (typeof line == 'number') { - line = terminal.rowContainer.children[line]; - } else if (! (line instanceof HTMLDivElement)) { - var message = 'The "line" argument should be either a number'; - message += ' or an HTMLDivElement'; - - throw new TypeError(message); - } - - if (typeof target === 'undefined') { - target = ''; - } else { - target = 'target="' + target + '"'; - } - - var buffer = document.createElement('span'), - nodes = line.childNodes; - - for (var j=0; j' + url + '', - newHTML = nodeHTML.replace(url, link); - - line.innerHTML = line.innerHTML.replace(nodeHTML, newHTML); - } - - /** - * This event gets emitted when conversion of all URL susbtrings - * to HTML anchor elements (links) has finished, for a specific - * line of the current Xterm instance. - * - * @event linkify:line - */ - terminal.emit('linkify:line', line); - }; - - /** - * Finds a link within a block of text. - * - * @param {string} text - The text to search . - * @param {boolean} lenient - Whether to use the lenient search. - * @return {string} A URL. - */ - exports.findLinkMatch = function (text, lenient) { - var match = text.match(lenient ? lenientUrlRegex : strictUrlRegex); - if (!match || match.length === 0) { - return null; - } - return match[1]; - } - - /** - * Converts all valid URLs found in the terminal view into hyperlinks. - * - * @param {Xterm} terminal - The terminal that should get "linkified". - * @param {boolean} lenient - The regex type that will be used to identify links. If lenient is - * false, the regex requires a protocol clause. Defaults to true. - * @param {string} target - Sets target="" attribute with value provided to links. - * Default doesn't set target attribute - * @emits linkify - * @emits linkify:line - */ - exports.linkify = function (terminal, lenient, target) { - var rows = terminal.rowContainer.children; - - lenient = (typeof lenient == "boolean") ? lenient : true; - for (var i=0; i div { - /* Lines containing spans and text nodes ocassionally wrap despite being the same width (#327) */ - white-space: nowrap; -} - -.terminal .xterm-scroll-area { - visibility: hidden; -} - -.terminal .xterm-char-measure-element { - display: inline-block; - visibility: hidden; - position: absolute; - left: -9999em; -} - -.terminal.enable-mouse-events { - /* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */ - cursor: default; -} - -.terminal .xterm-selection { - position: absolute; - top: 0; - left: 0; - z-index: 1; - opacity: 0.3; - pointer-events: none; -} - -.terminal .xterm-selection div { - position: absolute; - background-color: #fff; -} - -/* - * Determine default colors for xterm.js - */ -.terminal .xterm-bold { - font-weight: bold; -} - -.terminal .xterm-underline { - text-decoration: underline; -} - -.terminal .xterm-blink { - text-decoration: blink; -} - -.terminal .xterm-blink.xterm-underline { - text-decoration: blink underline; -} - -.terminal .xterm-hidden { - visibility: hidden; -} - -.terminal .xterm-color-0 { - color: #2e3436; -} - -.terminal .xterm-bg-color-0 { - background-color: #2e3436; -} - -.terminal .xterm-color-1 { - color: #cc0000; -} - -.terminal .xterm-bg-color-1 { - background-color: #cc0000; -} - -.terminal .xterm-color-2 { - color: #4e9a06; -} - -.terminal .xterm-bg-color-2 { - background-color: #4e9a06; -} - -.terminal .xterm-color-3 { - color: #c4a000; -} - -.terminal .xterm-bg-color-3 { - background-color: #c4a000; -} - -.terminal .xterm-color-4 { - color: #3465a4; -} - -.terminal .xterm-bg-color-4 { - background-color: #3465a4; -} - -.terminal .xterm-color-5 { - color: #75507b; -} - -.terminal .xterm-bg-color-5 { - background-color: #75507b; -} - -.terminal .xterm-color-6 { - color: #06989a; -} - -.terminal .xterm-bg-color-6 { - background-color: #06989a; -} - -.terminal .xterm-color-7 { - color: #d3d7cf; -} - -.terminal .xterm-bg-color-7 { - background-color: #d3d7cf; -} - -.terminal .xterm-color-8 { - color: #555753; -} - -.terminal .xterm-bg-color-8 { - background-color: #555753; -} - -.terminal .xterm-color-9 { - color: #ef2929; -} - -.terminal .xterm-bg-color-9 { - background-color: #ef2929; -} - -.terminal .xterm-color-10 { - color: #8ae234; -} - -.terminal .xterm-bg-color-10 { - background-color: #8ae234; -} - -.terminal .xterm-color-11 { - color: #fce94f; -} - -.terminal .xterm-bg-color-11 { - background-color: #fce94f; -} - -.terminal .xterm-color-12 { - color: #729fcf; -} - -.terminal .xterm-bg-color-12 { - background-color: #729fcf; -} - -.terminal .xterm-color-13 { - color: #ad7fa8; -} - -.terminal .xterm-bg-color-13 { - background-color: #ad7fa8; -} - -.terminal .xterm-color-14 { - color: #34e2e2; -} - -.terminal .xterm-bg-color-14 { - background-color: #34e2e2; -} - -.terminal .xterm-color-15 { - color: #eeeeec; -} - -.terminal .xterm-bg-color-15 { - background-color: #eeeeec; -} - -.terminal .xterm-color-16 { - color: #000000; -} - -.terminal .xterm-bg-color-16 { - background-color: #000000; -} - -.terminal .xterm-color-17 { - color: #00005f; -} - -.terminal .xterm-bg-color-17 { - background-color: #00005f; -} - -.terminal .xterm-color-18 { - color: #000087; -} - -.terminal .xterm-bg-color-18 { - background-color: #000087; -} - -.terminal .xterm-color-19 { - color: #0000af; -} - -.terminal .xterm-bg-color-19 { - background-color: #0000af; -} - -.terminal .xterm-color-20 { - color: #0000d7; -} - -.terminal .xterm-bg-color-20 { - background-color: #0000d7; -} - -.terminal .xterm-color-21 { - color: #0000ff; -} - -.terminal .xterm-bg-color-21 { - background-color: #0000ff; -} - -.terminal .xterm-color-22 { - color: #005f00; -} - -.terminal .xterm-bg-color-22 { - background-color: #005f00; -} - -.terminal .xterm-color-23 { - color: #005f5f; -} - -.terminal .xterm-bg-color-23 { - background-color: #005f5f; -} - -.terminal .xterm-color-24 { - color: #005f87; -} - -.terminal .xterm-bg-color-24 { - background-color: #005f87; -} - -.terminal .xterm-color-25 { - color: #005faf; -} - -.terminal .xterm-bg-color-25 { - background-color: #005faf; -} - -.terminal .xterm-color-26 { - color: #005fd7; -} - -.terminal .xterm-bg-color-26 { - background-color: #005fd7; -} - -.terminal .xterm-color-27 { - color: #005fff; -} - -.terminal .xterm-bg-color-27 { - background-color: #005fff; -} - -.terminal .xterm-color-28 { - color: #008700; -} - -.terminal .xterm-bg-color-28 { - background-color: #008700; -} - -.terminal .xterm-color-29 { - color: #00875f; -} - -.terminal .xterm-bg-color-29 { - background-color: #00875f; -} - -.terminal .xterm-color-30 { - color: #008787; -} - -.terminal .xterm-bg-color-30 { - background-color: #008787; -} - -.terminal .xterm-color-31 { - color: #0087af; -} - -.terminal .xterm-bg-color-31 { - background-color: #0087af; -} - -.terminal .xterm-color-32 { - color: #0087d7; -} - -.terminal .xterm-bg-color-32 { - background-color: #0087d7; -} - -.terminal .xterm-color-33 { - color: #0087ff; -} - -.terminal .xterm-bg-color-33 { - background-color: #0087ff; -} - -.terminal .xterm-color-34 { - color: #00af00; -} - -.terminal .xterm-bg-color-34 { - background-color: #00af00; -} - -.terminal .xterm-color-35 { - color: #00af5f; -} - -.terminal .xterm-bg-color-35 { - background-color: #00af5f; -} - -.terminal .xterm-color-36 { - color: #00af87; -} - -.terminal .xterm-bg-color-36 { - background-color: #00af87; -} - -.terminal .xterm-color-37 { - color: #00afaf; -} - -.terminal .xterm-bg-color-37 { - background-color: #00afaf; -} - -.terminal .xterm-color-38 { - color: #00afd7; -} - -.terminal .xterm-bg-color-38 { - background-color: #00afd7; -} - -.terminal .xterm-color-39 { - color: #00afff; -} - -.terminal .xterm-bg-color-39 { - background-color: #00afff; -} - -.terminal .xterm-color-40 { - color: #00d700; -} - -.terminal .xterm-bg-color-40 { - background-color: #00d700; -} - -.terminal .xterm-color-41 { - color: #00d75f; -} - -.terminal .xterm-bg-color-41 { - background-color: #00d75f; -} - -.terminal .xterm-color-42 { - color: #00d787; -} - -.terminal .xterm-bg-color-42 { - background-color: #00d787; -} - -.terminal .xterm-color-43 { - color: #00d7af; -} - -.terminal .xterm-bg-color-43 { - background-color: #00d7af; -} - -.terminal .xterm-color-44 { - color: #00d7d7; -} - -.terminal .xterm-bg-color-44 { - background-color: #00d7d7; -} - -.terminal .xterm-color-45 { - color: #00d7ff; -} - -.terminal .xterm-bg-color-45 { - background-color: #00d7ff; -} - -.terminal .xterm-color-46 { - color: #00ff00; -} - -.terminal .xterm-bg-color-46 { - background-color: #00ff00; -} - -.terminal .xterm-color-47 { - color: #00ff5f; -} - -.terminal .xterm-bg-color-47 { - background-color: #00ff5f; -} - -.terminal .xterm-color-48 { - color: #00ff87; -} - -.terminal .xterm-bg-color-48 { - background-color: #00ff87; -} - -.terminal .xterm-color-49 { - color: #00ffaf; -} - -.terminal .xterm-bg-color-49 { - background-color: #00ffaf; -} - -.terminal .xterm-color-50 { - color: #00ffd7; -} - -.terminal .xterm-bg-color-50 { - background-color: #00ffd7; -} - -.terminal .xterm-color-51 { - color: #00ffff; -} - -.terminal .xterm-bg-color-51 { - background-color: #00ffff; -} - -.terminal .xterm-color-52 { - color: #5f0000; -} - -.terminal .xterm-bg-color-52 { - background-color: #5f0000; -} - -.terminal .xterm-color-53 { - color: #5f005f; -} - -.terminal .xterm-bg-color-53 { - background-color: #5f005f; -} - -.terminal .xterm-color-54 { - color: #5f0087; -} - -.terminal .xterm-bg-color-54 { - background-color: #5f0087; -} - -.terminal .xterm-color-55 { - color: #5f00af; -} - -.terminal .xterm-bg-color-55 { - background-color: #5f00af; -} - -.terminal .xterm-color-56 { - color: #5f00d7; -} - -.terminal .xterm-bg-color-56 { - background-color: #5f00d7; -} - -.terminal .xterm-color-57 { - color: #5f00ff; -} - -.terminal .xterm-bg-color-57 { - background-color: #5f00ff; -} - -.terminal .xterm-color-58 { - color: #5f5f00; -} - -.terminal .xterm-bg-color-58 { - background-color: #5f5f00; -} - -.terminal .xterm-color-59 { - color: #5f5f5f; -} - -.terminal .xterm-bg-color-59 { - background-color: #5f5f5f; -} - -.terminal .xterm-color-60 { - color: #5f5f87; -} - -.terminal .xterm-bg-color-60 { - background-color: #5f5f87; -} - -.terminal .xterm-color-61 { - color: #5f5faf; -} - -.terminal .xterm-bg-color-61 { - background-color: #5f5faf; -} - -.terminal .xterm-color-62 { - color: #5f5fd7; -} - -.terminal .xterm-bg-color-62 { - background-color: #5f5fd7; -} - -.terminal .xterm-color-63 { - color: #5f5fff; -} - -.terminal .xterm-bg-color-63 { - background-color: #5f5fff; -} - -.terminal .xterm-color-64 { - color: #5f8700; -} - -.terminal .xterm-bg-color-64 { - background-color: #5f8700; -} - -.terminal .xterm-color-65 { - color: #5f875f; -} - -.terminal .xterm-bg-color-65 { - background-color: #5f875f; -} - -.terminal .xterm-color-66 { - color: #5f8787; -} - -.terminal .xterm-bg-color-66 { - background-color: #5f8787; -} - -.terminal .xterm-color-67 { - color: #5f87af; -} - -.terminal .xterm-bg-color-67 { - background-color: #5f87af; -} - -.terminal .xterm-color-68 { - color: #5f87d7; -} - -.terminal .xterm-bg-color-68 { - background-color: #5f87d7; -} - -.terminal .xterm-color-69 { - color: #5f87ff; -} - -.terminal .xterm-bg-color-69 { - background-color: #5f87ff; -} - -.terminal .xterm-color-70 { - color: #5faf00; -} - -.terminal .xterm-bg-color-70 { - background-color: #5faf00; -} - -.terminal .xterm-color-71 { - color: #5faf5f; -} - -.terminal .xterm-bg-color-71 { - background-color: #5faf5f; -} - -.terminal .xterm-color-72 { - color: #5faf87; -} - -.terminal .xterm-bg-color-72 { - background-color: #5faf87; -} - -.terminal .xterm-color-73 { - color: #5fafaf; -} - -.terminal .xterm-bg-color-73 { - background-color: #5fafaf; -} - -.terminal .xterm-color-74 { - color: #5fafd7; -} - -.terminal .xterm-bg-color-74 { - background-color: #5fafd7; -} - -.terminal .xterm-color-75 { - color: #5fafff; -} - -.terminal .xterm-bg-color-75 { - background-color: #5fafff; -} - -.terminal .xterm-color-76 { - color: #5fd700; -} - -.terminal .xterm-bg-color-76 { - background-color: #5fd700; -} - -.terminal .xterm-color-77 { - color: #5fd75f; -} - -.terminal .xterm-bg-color-77 { - background-color: #5fd75f; -} - -.terminal .xterm-color-78 { - color: #5fd787; -} - -.terminal .xterm-bg-color-78 { - background-color: #5fd787; -} - -.terminal .xterm-color-79 { - color: #5fd7af; -} - -.terminal .xterm-bg-color-79 { - background-color: #5fd7af; -} - -.terminal .xterm-color-80 { - color: #5fd7d7; -} - -.terminal .xterm-bg-color-80 { - background-color: #5fd7d7; -} - -.terminal .xterm-color-81 { - color: #5fd7ff; -} - -.terminal .xterm-bg-color-81 { - background-color: #5fd7ff; -} - -.terminal .xterm-color-82 { - color: #5fff00; -} - -.terminal .xterm-bg-color-82 { - background-color: #5fff00; -} - -.terminal .xterm-color-83 { - color: #5fff5f; -} - -.terminal .xterm-bg-color-83 { - background-color: #5fff5f; -} - -.terminal .xterm-color-84 { - color: #5fff87; -} - -.terminal .xterm-bg-color-84 { - background-color: #5fff87; -} - -.terminal .xterm-color-85 { - color: #5fffaf; -} - -.terminal .xterm-bg-color-85 { - background-color: #5fffaf; -} - -.terminal .xterm-color-86 { - color: #5fffd7; -} - -.terminal .xterm-bg-color-86 { - background-color: #5fffd7; -} - -.terminal .xterm-color-87 { - color: #5fffff; -} - -.terminal .xterm-bg-color-87 { - background-color: #5fffff; -} - -.terminal .xterm-color-88 { - color: #870000; -} - -.terminal .xterm-bg-color-88 { - background-color: #870000; -} - -.terminal .xterm-color-89 { - color: #87005f; -} - -.terminal .xterm-bg-color-89 { - background-color: #87005f; -} - -.terminal .xterm-color-90 { - color: #870087; -} - -.terminal .xterm-bg-color-90 { - background-color: #870087; -} - -.terminal .xterm-color-91 { - color: #8700af; -} - -.terminal .xterm-bg-color-91 { - background-color: #8700af; -} - -.terminal .xterm-color-92 { - color: #8700d7; -} - -.terminal .xterm-bg-color-92 { - background-color: #8700d7; -} - -.terminal .xterm-color-93 { - color: #8700ff; -} - -.terminal .xterm-bg-color-93 { - background-color: #8700ff; -} - -.terminal .xterm-color-94 { - color: #875f00; -} - -.terminal .xterm-bg-color-94 { - background-color: #875f00; -} - -.terminal .xterm-color-95 { - color: #875f5f; -} - -.terminal .xterm-bg-color-95 { - background-color: #875f5f; -} - -.terminal .xterm-color-96 { - color: #875f87; -} - -.terminal .xterm-bg-color-96 { - background-color: #875f87; -} - -.terminal .xterm-color-97 { - color: #875faf; -} - -.terminal .xterm-bg-color-97 { - background-color: #875faf; -} - -.terminal .xterm-color-98 { - color: #875fd7; -} - -.terminal .xterm-bg-color-98 { - background-color: #875fd7; -} - -.terminal .xterm-color-99 { - color: #875fff; -} - -.terminal .xterm-bg-color-99 { - background-color: #875fff; -} - -.terminal .xterm-color-100 { - color: #878700; -} - -.terminal .xterm-bg-color-100 { - background-color: #878700; -} - -.terminal .xterm-color-101 { - color: #87875f; -} - -.terminal .xterm-bg-color-101 { - background-color: #87875f; -} - -.terminal .xterm-color-102 { - color: #878787; -} - -.terminal .xterm-bg-color-102 { - background-color: #878787; -} - -.terminal .xterm-color-103 { - color: #8787af; -} - -.terminal .xterm-bg-color-103 { - background-color: #8787af; -} - -.terminal .xterm-color-104 { - color: #8787d7; -} - -.terminal .xterm-bg-color-104 { - background-color: #8787d7; -} - -.terminal .xterm-color-105 { - color: #8787ff; -} - -.terminal .xterm-bg-color-105 { - background-color: #8787ff; -} - -.terminal .xterm-color-106 { - color: #87af00; -} - -.terminal .xterm-bg-color-106 { - background-color: #87af00; -} - -.terminal .xterm-color-107 { - color: #87af5f; -} - -.terminal .xterm-bg-color-107 { - background-color: #87af5f; -} - -.terminal .xterm-color-108 { - color: #87af87; -} - -.terminal .xterm-bg-color-108 { - background-color: #87af87; -} - -.terminal .xterm-color-109 { - color: #87afaf; -} - -.terminal .xterm-bg-color-109 { - background-color: #87afaf; -} - -.terminal .xterm-color-110 { - color: #87afd7; -} - -.terminal .xterm-bg-color-110 { - background-color: #87afd7; -} - -.terminal .xterm-color-111 { - color: #87afff; -} - -.terminal .xterm-bg-color-111 { - background-color: #87afff; -} - -.terminal .xterm-color-112 { - color: #87d700; -} - -.terminal .xterm-bg-color-112 { - background-color: #87d700; -} - -.terminal .xterm-color-113 { - color: #87d75f; -} - -.terminal .xterm-bg-color-113 { - background-color: #87d75f; -} - -.terminal .xterm-color-114 { - color: #87d787; -} - -.terminal .xterm-bg-color-114 { - background-color: #87d787; -} - -.terminal .xterm-color-115 { - color: #87d7af; -} - -.terminal .xterm-bg-color-115 { - background-color: #87d7af; -} - -.terminal .xterm-color-116 { - color: #87d7d7; -} - -.terminal .xterm-bg-color-116 { - background-color: #87d7d7; -} - -.terminal .xterm-color-117 { - color: #87d7ff; -} - -.terminal .xterm-bg-color-117 { - background-color: #87d7ff; -} - -.terminal .xterm-color-118 { - color: #87ff00; -} - -.terminal .xterm-bg-color-118 { - background-color: #87ff00; -} - -.terminal .xterm-color-119 { - color: #87ff5f; -} - -.terminal .xterm-bg-color-119 { - background-color: #87ff5f; -} - -.terminal .xterm-color-120 { - color: #87ff87; -} - -.terminal .xterm-bg-color-120 { - background-color: #87ff87; -} - -.terminal .xterm-color-121 { - color: #87ffaf; -} - -.terminal .xterm-bg-color-121 { - background-color: #87ffaf; -} - -.terminal .xterm-color-122 { - color: #87ffd7; -} - -.terminal .xterm-bg-color-122 { - background-color: #87ffd7; -} - -.terminal .xterm-color-123 { - color: #87ffff; -} - -.terminal .xterm-bg-color-123 { - background-color: #87ffff; -} - -.terminal .xterm-color-124 { - color: #af0000; -} - -.terminal .xterm-bg-color-124 { - background-color: #af0000; -} - -.terminal .xterm-color-125 { - color: #af005f; -} - -.terminal .xterm-bg-color-125 { - background-color: #af005f; -} - -.terminal .xterm-color-126 { - color: #af0087; -} - -.terminal .xterm-bg-color-126 { - background-color: #af0087; -} - -.terminal .xterm-color-127 { - color: #af00af; -} - -.terminal .xterm-bg-color-127 { - background-color: #af00af; -} - -.terminal .xterm-color-128 { - color: #af00d7; -} - -.terminal .xterm-bg-color-128 { - background-color: #af00d7; -} - -.terminal .xterm-color-129 { - color: #af00ff; -} - -.terminal .xterm-bg-color-129 { - background-color: #af00ff; -} - -.terminal .xterm-color-130 { - color: #af5f00; -} - -.terminal .xterm-bg-color-130 { - background-color: #af5f00; -} - -.terminal .xterm-color-131 { - color: #af5f5f; -} - -.terminal .xterm-bg-color-131 { - background-color: #af5f5f; -} - -.terminal .xterm-color-132 { - color: #af5f87; -} - -.terminal .xterm-bg-color-132 { - background-color: #af5f87; -} - -.terminal .xterm-color-133 { - color: #af5faf; -} - -.terminal .xterm-bg-color-133 { - background-color: #af5faf; -} - -.terminal .xterm-color-134 { - color: #af5fd7; -} - -.terminal .xterm-bg-color-134 { - background-color: #af5fd7; -} - -.terminal .xterm-color-135 { - color: #af5fff; -} - -.terminal .xterm-bg-color-135 { - background-color: #af5fff; -} - -.terminal .xterm-color-136 { - color: #af8700; -} - -.terminal .xterm-bg-color-136 { - background-color: #af8700; -} - -.terminal .xterm-color-137 { - color: #af875f; -} - -.terminal .xterm-bg-color-137 { - background-color: #af875f; -} - -.terminal .xterm-color-138 { - color: #af8787; -} - -.terminal .xterm-bg-color-138 { - background-color: #af8787; -} - -.terminal .xterm-color-139 { - color: #af87af; -} - -.terminal .xterm-bg-color-139 { - background-color: #af87af; -} - -.terminal .xterm-color-140 { - color: #af87d7; -} - -.terminal .xterm-bg-color-140 { - background-color: #af87d7; -} - -.terminal .xterm-color-141 { - color: #af87ff; -} - -.terminal .xterm-bg-color-141 { - background-color: #af87ff; -} - -.terminal .xterm-color-142 { - color: #afaf00; -} - -.terminal .xterm-bg-color-142 { - background-color: #afaf00; -} - -.terminal .xterm-color-143 { - color: #afaf5f; -} - -.terminal .xterm-bg-color-143 { - background-color: #afaf5f; -} - -.terminal .xterm-color-144 { - color: #afaf87; -} - -.terminal .xterm-bg-color-144 { - background-color: #afaf87; -} - -.terminal .xterm-color-145 { - color: #afafaf; -} - -.terminal .xterm-bg-color-145 { - background-color: #afafaf; -} - -.terminal .xterm-color-146 { - color: #afafd7; -} - -.terminal .xterm-bg-color-146 { - background-color: #afafd7; -} - -.terminal .xterm-color-147 { - color: #afafff; -} - -.terminal .xterm-bg-color-147 { - background-color: #afafff; -} - -.terminal .xterm-color-148 { - color: #afd700; -} - -.terminal .xterm-bg-color-148 { - background-color: #afd700; -} - -.terminal .xterm-color-149 { - color: #afd75f; -} - -.terminal .xterm-bg-color-149 { - background-color: #afd75f; -} - -.terminal .xterm-color-150 { - color: #afd787; -} - -.terminal .xterm-bg-color-150 { - background-color: #afd787; -} - -.terminal .xterm-color-151 { - color: #afd7af; -} - -.terminal .xterm-bg-color-151 { - background-color: #afd7af; -} - -.terminal .xterm-color-152 { - color: #afd7d7; -} - -.terminal .xterm-bg-color-152 { - background-color: #afd7d7; -} - -.terminal .xterm-color-153 { - color: #afd7ff; -} - -.terminal .xterm-bg-color-153 { - background-color: #afd7ff; -} - -.terminal .xterm-color-154 { - color: #afff00; -} - -.terminal .xterm-bg-color-154 { - background-color: #afff00; -} - -.terminal .xterm-color-155 { - color: #afff5f; -} - -.terminal .xterm-bg-color-155 { - background-color: #afff5f; -} - -.terminal .xterm-color-156 { - color: #afff87; -} - -.terminal .xterm-bg-color-156 { - background-color: #afff87; -} - -.terminal .xterm-color-157 { - color: #afffaf; -} - -.terminal .xterm-bg-color-157 { - background-color: #afffaf; -} - -.terminal .xterm-color-158 { - color: #afffd7; -} - -.terminal .xterm-bg-color-158 { - background-color: #afffd7; -} - -.terminal .xterm-color-159 { - color: #afffff; -} - -.terminal .xterm-bg-color-159 { - background-color: #afffff; -} - -.terminal .xterm-color-160 { - color: #d70000; -} - -.terminal .xterm-bg-color-160 { - background-color: #d70000; -} - -.terminal .xterm-color-161 { - color: #d7005f; -} - -.terminal .xterm-bg-color-161 { - background-color: #d7005f; -} - -.terminal .xterm-color-162 { - color: #d70087; -} - -.terminal .xterm-bg-color-162 { - background-color: #d70087; -} - -.terminal .xterm-color-163 { - color: #d700af; -} - -.terminal .xterm-bg-color-163 { - background-color: #d700af; -} - -.terminal .xterm-color-164 { - color: #d700d7; -} - -.terminal .xterm-bg-color-164 { - background-color: #d700d7; -} - -.terminal .xterm-color-165 { - color: #d700ff; -} - -.terminal .xterm-bg-color-165 { - background-color: #d700ff; -} - -.terminal .xterm-color-166 { - color: #d75f00; -} - -.terminal .xterm-bg-color-166 { - background-color: #d75f00; -} - -.terminal .xterm-color-167 { - color: #d75f5f; -} - -.terminal .xterm-bg-color-167 { - background-color: #d75f5f; -} - -.terminal .xterm-color-168 { - color: #d75f87; -} - -.terminal .xterm-bg-color-168 { - background-color: #d75f87; -} - -.terminal .xterm-color-169 { - color: #d75faf; -} - -.terminal .xterm-bg-color-169 { - background-color: #d75faf; -} - -.terminal .xterm-color-170 { - color: #d75fd7; -} - -.terminal .xterm-bg-color-170 { - background-color: #d75fd7; -} - -.terminal .xterm-color-171 { - color: #d75fff; -} - -.terminal .xterm-bg-color-171 { - background-color: #d75fff; -} - -.terminal .xterm-color-172 { - color: #d78700; -} - -.terminal .xterm-bg-color-172 { - background-color: #d78700; -} - -.terminal .xterm-color-173 { - color: #d7875f; -} - -.terminal .xterm-bg-color-173 { - background-color: #d7875f; -} - -.terminal .xterm-color-174 { - color: #d78787; -} - -.terminal .xterm-bg-color-174 { - background-color: #d78787; -} - -.terminal .xterm-color-175 { - color: #d787af; -} - -.terminal .xterm-bg-color-175 { - background-color: #d787af; -} - -.terminal .xterm-color-176 { - color: #d787d7; -} - -.terminal .xterm-bg-color-176 { - background-color: #d787d7; -} - -.terminal .xterm-color-177 { - color: #d787ff; -} - -.terminal .xterm-bg-color-177 { - background-color: #d787ff; -} - -.terminal .xterm-color-178 { - color: #d7af00; -} - -.terminal .xterm-bg-color-178 { - background-color: #d7af00; -} - -.terminal .xterm-color-179 { - color: #d7af5f; -} - -.terminal .xterm-bg-color-179 { - background-color: #d7af5f; -} - -.terminal .xterm-color-180 { - color: #d7af87; -} - -.terminal .xterm-bg-color-180 { - background-color: #d7af87; -} - -.terminal .xterm-color-181 { - color: #d7afaf; -} - -.terminal .xterm-bg-color-181 { - background-color: #d7afaf; -} - -.terminal .xterm-color-182 { - color: #d7afd7; -} - -.terminal .xterm-bg-color-182 { - background-color: #d7afd7; -} - -.terminal .xterm-color-183 { - color: #d7afff; -} - -.terminal .xterm-bg-color-183 { - background-color: #d7afff; -} - -.terminal .xterm-color-184 { - color: #d7d700; -} - -.terminal .xterm-bg-color-184 { - background-color: #d7d700; -} - -.terminal .xterm-color-185 { - color: #d7d75f; -} - -.terminal .xterm-bg-color-185 { - background-color: #d7d75f; -} - -.terminal .xterm-color-186 { - color: #d7d787; -} - -.terminal .xterm-bg-color-186 { - background-color: #d7d787; -} - -.terminal .xterm-color-187 { - color: #d7d7af; -} - -.terminal .xterm-bg-color-187 { - background-color: #d7d7af; -} - -.terminal .xterm-color-188 { - color: #d7d7d7; -} - -.terminal .xterm-bg-color-188 { - background-color: #d7d7d7; -} - -.terminal .xterm-color-189 { - color: #d7d7ff; -} - -.terminal .xterm-bg-color-189 { - background-color: #d7d7ff; -} - -.terminal .xterm-color-190 { - color: #d7ff00; -} - -.terminal .xterm-bg-color-190 { - background-color: #d7ff00; -} - -.terminal .xterm-color-191 { - color: #d7ff5f; -} - -.terminal .xterm-bg-color-191 { - background-color: #d7ff5f; -} - -.terminal .xterm-color-192 { - color: #d7ff87; -} - -.terminal .xterm-bg-color-192 { - background-color: #d7ff87; -} - -.terminal .xterm-color-193 { - color: #d7ffaf; -} - -.terminal .xterm-bg-color-193 { - background-color: #d7ffaf; -} - -.terminal .xterm-color-194 { - color: #d7ffd7; -} - -.terminal .xterm-bg-color-194 { - background-color: #d7ffd7; -} - -.terminal .xterm-color-195 { - color: #d7ffff; -} - -.terminal .xterm-bg-color-195 { - background-color: #d7ffff; -} - -.terminal .xterm-color-196 { - color: #ff0000; -} - -.terminal .xterm-bg-color-196 { - background-color: #ff0000; -} - -.terminal .xterm-color-197 { - color: #ff005f; -} - -.terminal .xterm-bg-color-197 { - background-color: #ff005f; -} - -.terminal .xterm-color-198 { - color: #ff0087; -} - -.terminal .xterm-bg-color-198 { - background-color: #ff0087; -} - -.terminal .xterm-color-199 { - color: #ff00af; -} - -.terminal .xterm-bg-color-199 { - background-color: #ff00af; -} - -.terminal .xterm-color-200 { - color: #ff00d7; -} - -.terminal .xterm-bg-color-200 { - background-color: #ff00d7; -} - -.terminal .xterm-color-201 { - color: #ff00ff; -} - -.terminal .xterm-bg-color-201 { - background-color: #ff00ff; -} - -.terminal .xterm-color-202 { - color: #ff5f00; -} - -.terminal .xterm-bg-color-202 { - background-color: #ff5f00; -} - -.terminal .xterm-color-203 { - color: #ff5f5f; -} - -.terminal .xterm-bg-color-203 { - background-color: #ff5f5f; -} - -.terminal .xterm-color-204 { - color: #ff5f87; -} - -.terminal .xterm-bg-color-204 { - background-color: #ff5f87; -} - -.terminal .xterm-color-205 { - color: #ff5faf; -} - -.terminal .xterm-bg-color-205 { - background-color: #ff5faf; -} - -.terminal .xterm-color-206 { - color: #ff5fd7; -} - -.terminal .xterm-bg-color-206 { - background-color: #ff5fd7; -} - -.terminal .xterm-color-207 { - color: #ff5fff; -} - -.terminal .xterm-bg-color-207 { - background-color: #ff5fff; -} - -.terminal .xterm-color-208 { - color: #ff8700; -} - -.terminal .xterm-bg-color-208 { - background-color: #ff8700; -} - -.terminal .xterm-color-209 { - color: #ff875f; -} - -.terminal .xterm-bg-color-209 { - background-color: #ff875f; -} - -.terminal .xterm-color-210 { - color: #ff8787; -} - -.terminal .xterm-bg-color-210 { - background-color: #ff8787; -} - -.terminal .xterm-color-211 { - color: #ff87af; -} - -.terminal .xterm-bg-color-211 { - background-color: #ff87af; -} - -.terminal .xterm-color-212 { - color: #ff87d7; -} - -.terminal .xterm-bg-color-212 { - background-color: #ff87d7; -} - -.terminal .xterm-color-213 { - color: #ff87ff; -} - -.terminal .xterm-bg-color-213 { - background-color: #ff87ff; -} - -.terminal .xterm-color-214 { - color: #ffaf00; -} - -.terminal .xterm-bg-color-214 { - background-color: #ffaf00; -} - -.terminal .xterm-color-215 { - color: #ffaf5f; -} - -.terminal .xterm-bg-color-215 { - background-color: #ffaf5f; -} - -.terminal .xterm-color-216 { - color: #ffaf87; -} - -.terminal .xterm-bg-color-216 { - background-color: #ffaf87; -} - -.terminal .xterm-color-217 { - color: #ffafaf; -} - -.terminal .xterm-bg-color-217 { - background-color: #ffafaf; -} - -.terminal .xterm-color-218 { - color: #ffafd7; -} - -.terminal .xterm-bg-color-218 { - background-color: #ffafd7; -} - -.terminal .xterm-color-219 { - color: #ffafff; -} - -.terminal .xterm-bg-color-219 { - background-color: #ffafff; -} - -.terminal .xterm-color-220 { - color: #ffd700; -} - -.terminal .xterm-bg-color-220 { - background-color: #ffd700; -} - -.terminal .xterm-color-221 { - color: #ffd75f; -} - -.terminal .xterm-bg-color-221 { - background-color: #ffd75f; -} - -.terminal .xterm-color-222 { - color: #ffd787; -} - -.terminal .xterm-bg-color-222 { - background-color: #ffd787; -} - -.terminal .xterm-color-223 { - color: #ffd7af; -} - -.terminal .xterm-bg-color-223 { - background-color: #ffd7af; -} - -.terminal .xterm-color-224 { - color: #ffd7d7; -} - -.terminal .xterm-bg-color-224 { - background-color: #ffd7d7; -} - -.terminal .xterm-color-225 { - color: #ffd7ff; -} - -.terminal .xterm-bg-color-225 { - background-color: #ffd7ff; -} - -.terminal .xterm-color-226 { - color: #ffff00; -} - -.terminal .xterm-bg-color-226 { - background-color: #ffff00; -} - -.terminal .xterm-color-227 { - color: #ffff5f; -} - -.terminal .xterm-bg-color-227 { - background-color: #ffff5f; -} - -.terminal .xterm-color-228 { - color: #ffff87; -} - -.terminal .xterm-bg-color-228 { - background-color: #ffff87; -} - -.terminal .xterm-color-229 { - color: #ffffaf; -} - -.terminal .xterm-bg-color-229 { - background-color: #ffffaf; -} - -.terminal .xterm-color-230 { - color: #ffffd7; -} - -.terminal .xterm-bg-color-230 { - background-color: #ffffd7; -} - -.terminal .xterm-color-231 { - color: #ffffff; -} - -.terminal .xterm-bg-color-231 { - background-color: #ffffff; -} - -.terminal .xterm-color-232 { - color: #080808; -} - -.terminal .xterm-bg-color-232 { - background-color: #080808; -} - -.terminal .xterm-color-233 { - color: #121212; -} - -.terminal .xterm-bg-color-233 { - background-color: #121212; -} - -.terminal .xterm-color-234 { - color: #1c1c1c; -} - -.terminal .xterm-bg-color-234 { - background-color: #1c1c1c; -} - -.terminal .xterm-color-235 { - color: #262626; -} - -.terminal .xterm-bg-color-235 { - background-color: #262626; -} - -.terminal .xterm-color-236 { - color: #303030; -} - -.terminal .xterm-bg-color-236 { - background-color: #303030; -} - -.terminal .xterm-color-237 { - color: #3a3a3a; -} - -.terminal .xterm-bg-color-237 { - background-color: #3a3a3a; -} - -.terminal .xterm-color-238 { - color: #444444; -} - -.terminal .xterm-bg-color-238 { - background-color: #444444; -} - -.terminal .xterm-color-239 { - color: #4e4e4e; -} - -.terminal .xterm-bg-color-239 { - background-color: #4e4e4e; -} - -.terminal .xterm-color-240 { - color: #585858; -} - -.terminal .xterm-bg-color-240 { - background-color: #585858; -} - -.terminal .xterm-color-241 { - color: #626262; -} - -.terminal .xterm-bg-color-241 { - background-color: #626262; -} - -.terminal .xterm-color-242 { - color: #6c6c6c; -} - -.terminal .xterm-bg-color-242 { - background-color: #6c6c6c; -} - -.terminal .xterm-color-243 { - color: #767676; -} - -.terminal .xterm-bg-color-243 { - background-color: #767676; -} - -.terminal .xterm-color-244 { - color: #808080; -} - -.terminal .xterm-bg-color-244 { - background-color: #808080; -} - -.terminal .xterm-color-245 { - color: #8a8a8a; -} - -.terminal .xterm-bg-color-245 { - background-color: #8a8a8a; -} - -.terminal .xterm-color-246 { - color: #949494; -} - -.terminal .xterm-bg-color-246 { - background-color: #949494; -} - -.terminal .xterm-color-247 { - color: #9e9e9e; -} - -.terminal .xterm-bg-color-247 { - background-color: #9e9e9e; -} - -.terminal .xterm-color-248 { - color: #a8a8a8; -} - -.terminal .xterm-bg-color-248 { - background-color: #a8a8a8; -} - -.terminal .xterm-color-249 { - color: #b2b2b2; -} - -.terminal .xterm-bg-color-249 { - background-color: #b2b2b2; -} - -.terminal .xterm-color-250 { - color: #bcbcbc; -} - -.terminal .xterm-bg-color-250 { - background-color: #bcbcbc; -} - -.terminal .xterm-color-251 { - color: #c6c6c6; -} - -.terminal .xterm-bg-color-251 { - background-color: #c6c6c6; -} - -.terminal .xterm-color-252 { - color: #d0d0d0; -} - -.terminal .xterm-bg-color-252 { - background-color: #d0d0d0; -} - -.terminal .xterm-color-253 { - color: #dadada; -} - -.terminal .xterm-bg-color-253 { - background-color: #dadada; -} - -.terminal .xterm-color-254 { - color: #e4e4e4; -} - -.terminal .xterm-bg-color-254 { - background-color: #e4e4e4; -} - -.terminal .xterm-color-255 { - color: #eeeeee; -} - -.terminal .xterm-bg-color-255 { - background-color: #eeeeee; -} diff --git a/dist/xterm.js b/dist/xterm.js deleted file mode 100644 index 8380c762..00000000 --- a/dist/xterm.js +++ /dev/null @@ -1,5131 +0,0 @@ -(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Terminal = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0 && this._lines.length <= this.ybase + this.y + addToY + 1) { - this.ybase--; - addToY++; - if (this.ydisp > 0) { - this.ydisp--; - } - } - else { - this._lines.push(this._terminal.blankLine()); - } - } - } - } - else { - for (var y = this._terminal.rows; y > newRows; y--) { - if (this._lines.length > newRows + this.ybase) { - if (this._lines.length > this.ybase + this.y + 1) { - this._lines.pop(); - } - else { - this.ybase++; - this.ydisp++; - } - } - } - } - if (this.y >= newRows) { - this.y = newRows - 1; - } - if (addToY) { - this.y += addToY; - } - if (this.x >= newCols) { - this.x = newCols - 1; - } - this.scrollTop = 0; - this.scrollBottom = newRows - 1; - }; - return Buffer; -}()); -exports.Buffer = Buffer; - - - -},{"./utils/CircularList":18}],2:[function(require,module,exports){ -"use strict"; -var __extends = (this && this.__extends) || (function () { - var extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -Object.defineProperty(exports, "__esModule", { value: true }); -var Buffer_1 = require("./Buffer"); -var EventEmitter_1 = require("./EventEmitter"); -var BufferSet = (function (_super) { - __extends(BufferSet, _super); - function BufferSet(_terminal) { - var _this = _super.call(this) || this; - _this._terminal = _terminal; - _this._normal = new Buffer_1.Buffer(_this._terminal); - _this._normal.fillViewportRows(); - _this._alt = new Buffer_1.Buffer(_this._terminal); - _this._activeBuffer = _this._normal; - return _this; - } - Object.defineProperty(BufferSet.prototype, "alt", { - get: function () { - return this._alt; - }, - enumerable: true, - configurable: true - }); - Object.defineProperty(BufferSet.prototype, "active", { - get: function () { - return this._activeBuffer; - }, - enumerable: true, - configurable: true - }); - Object.defineProperty(BufferSet.prototype, "normal", { - get: function () { - return this._normal; - }, - enumerable: true, - configurable: true - }); - BufferSet.prototype.activateNormalBuffer = function () { - this._alt.clear(); - this._activeBuffer = this._normal; - this.emit('activate', this._normal); - }; - BufferSet.prototype.activateAltBuffer = function () { - this._alt.fillViewportRows(); - this._activeBuffer = this._alt; - this.emit('activate', this._alt); - }; - BufferSet.prototype.resize = function (newCols, newRows) { - this._normal.resize(newCols, newRows); - this._alt.resize(newCols, newRows); - }; - return BufferSet; -}(EventEmitter_1.EventEmitter)); -exports.BufferSet = BufferSet; - - - -},{"./Buffer":1,"./EventEmitter":6}],3:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.CHARSETS = {}; -exports.DEFAULT_CHARSET = exports.CHARSETS['B']; -exports.CHARSETS['0'] = { - '`': '\u25c6', - 'a': '\u2592', - 'b': '\u0009', - 'c': '\u000c', - 'd': '\u000d', - 'e': '\u000a', - 'f': '\u00b0', - 'g': '\u00b1', - 'h': '\u2424', - 'i': '\u000b', - 'j': '\u2518', - 'k': '\u2510', - 'l': '\u250c', - 'm': '\u2514', - 'n': '\u253c', - 'o': '\u23ba', - 'p': '\u23bb', - 'q': '\u2500', - 'r': '\u23bc', - 's': '\u23bd', - 't': '\u251c', - 'u': '\u2524', - 'v': '\u2534', - 'w': '\u252c', - 'x': '\u2502', - 'y': '\u2264', - 'z': '\u2265', - '{': '\u03c0', - '|': '\u2260', - '}': '\u00a3', - '~': '\u00b7' -}; -exports.CHARSETS['A'] = { - '#': '£' -}; -exports.CHARSETS['B'] = null; -exports.CHARSETS['4'] = { - '#': '£', - '@': '¾', - '[': 'ij', - '\\': '½', - ']': '|', - '{': '¨', - '|': 'f', - '}': '¼', - '~': '´' -}; -exports.CHARSETS['C'] = - exports.CHARSETS['5'] = { - '[': 'Ä', - '\\': 'Ö', - ']': 'Å', - '^': 'Ü', - '`': 'é', - '{': 'ä', - '|': 'ö', - '}': 'å', - '~': 'ü' - }; -exports.CHARSETS['R'] = { - '#': '£', - '@': 'à', - '[': '°', - '\\': 'ç', - ']': '§', - '{': 'é', - '|': 'ù', - '}': 'è', - '~': '¨' -}; -exports.CHARSETS['Q'] = { - '@': 'à', - '[': 'â', - '\\': 'ç', - ']': 'ê', - '^': 'î', - '`': 'ô', - '{': 'é', - '|': 'ù', - '}': 'è', - '~': 'û' -}; -exports.CHARSETS['K'] = { - '@': '§', - '[': 'Ä', - '\\': 'Ö', - ']': 'Ü', - '{': 'ä', - '|': 'ö', - '}': 'ü', - '~': 'ß' -}; -exports.CHARSETS['Y'] = { - '#': '£', - '@': '§', - '[': '°', - '\\': 'ç', - ']': 'é', - '`': 'ù', - '{': 'à', - '|': 'ò', - '}': 'è', - '~': 'ì' -}; -exports.CHARSETS['E'] = - exports.CHARSETS['6'] = { - '@': 'Ä', - '[': 'Æ', - '\\': 'Ø', - ']': 'Å', - '^': 'Ü', - '`': 'ä', - '{': 'æ', - '|': 'ø', - '}': 'å', - '~': 'ü' - }; -exports.CHARSETS['Z'] = { - '#': '£', - '@': '§', - '[': '¡', - '\\': 'Ñ', - ']': '¿', - '{': '°', - '|': 'ñ', - '}': 'ç' -}; -exports.CHARSETS['H'] = - exports.CHARSETS['7'] = { - '@': 'É', - '[': 'Ä', - '\\': 'Ö', - ']': 'Å', - '^': 'Ü', - '`': 'é', - '{': 'ä', - '|': 'ö', - '}': 'å', - '~': 'ü' - }; -exports.CHARSETS['='] = { - '#': 'ù', - '@': 'à', - '[': 'é', - '\\': 'ç', - ']': 'ê', - '^': 'î', - '_': 'è', - '`': 'ô', - '{': 'ä', - '|': 'ö', - '}': 'ü', - '~': 'û' -}; - - - -},{}],4:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -var CompositionHelper = (function () { - function CompositionHelper(textarea, compositionView, terminal) { - this.textarea = textarea; - this.compositionView = compositionView; - this.terminal = terminal; - this.isComposing = false; - this.isSendingComposition = false; - this.compositionPosition = { start: null, end: null }; - } - CompositionHelper.prototype.compositionstart = function () { - this.isComposing = true; - this.compositionPosition.start = this.textarea.value.length; - this.compositionView.textContent = ''; - this.compositionView.classList.add('active'); - }; - CompositionHelper.prototype.compositionupdate = function (ev) { - var _this = this; - this.compositionView.textContent = ev.data; - this.updateCompositionElements(); - setTimeout(function () { - _this.compositionPosition.end = _this.textarea.value.length; - }, 0); - }; - CompositionHelper.prototype.compositionend = function () { - this.finalizeComposition(true); - }; - CompositionHelper.prototype.keydown = function (ev) { - if (this.isComposing || this.isSendingComposition) { - if (ev.keyCode === 229) { - return false; - } - else if (ev.keyCode === 16 || ev.keyCode === 17 || ev.keyCode === 18) { - return false; - } - else { - this.finalizeComposition(false); - } - } - if (ev.keyCode === 229) { - this.handleAnyTextareaChanges(); - return false; - } - return true; - }; - CompositionHelper.prototype.finalizeComposition = function (waitForPropogation) { - var _this = this; - this.compositionView.classList.remove('active'); - this.isComposing = false; - this.clearTextareaPosition(); - if (!waitForPropogation) { - this.isSendingComposition = false; - var input = this.textarea.value.substring(this.compositionPosition.start, this.compositionPosition.end); - this.terminal.handler(input); - } - else { - var currentCompositionPosition_1 = { - start: this.compositionPosition.start, - end: this.compositionPosition.end, - }; - this.isSendingComposition = true; - setTimeout(function () { - if (_this.isSendingComposition) { - _this.isSendingComposition = false; - var input = void 0; - if (_this.isComposing) { - input = _this.textarea.value.substring(currentCompositionPosition_1.start, currentCompositionPosition_1.end); - } - else { - input = _this.textarea.value.substring(currentCompositionPosition_1.start); - } - _this.terminal.handler(input); - } - }, 0); - } - }; - CompositionHelper.prototype.handleAnyTextareaChanges = function () { - var _this = this; - var oldValue = this.textarea.value; - setTimeout(function () { - if (!_this.isComposing) { - var newValue = _this.textarea.value; - var diff = newValue.replace(oldValue, ''); - if (diff.length > 0) { - _this.terminal.handler(diff); - } - } - }, 0); - }; - CompositionHelper.prototype.updateCompositionElements = function (dontRecurse) { - var _this = this; - if (!this.isComposing) { - return; - } - var cursor = this.terminal.element.querySelector('.terminal-cursor'); - if (cursor) { - var xtermRows = this.terminal.element.querySelector('.xterm-rows'); - var cursorTop = xtermRows.offsetTop + cursor.offsetTop; - this.compositionView.style.left = cursor.offsetLeft + 'px'; - this.compositionView.style.top = cursorTop + 'px'; - this.compositionView.style.height = cursor.offsetHeight + 'px'; - this.compositionView.style.lineHeight = cursor.offsetHeight + 'px'; - var compositionViewBounds = this.compositionView.getBoundingClientRect(); - this.textarea.style.left = cursor.offsetLeft + 'px'; - this.textarea.style.top = cursorTop + 'px'; - this.textarea.style.width = compositionViewBounds.width + 'px'; - this.textarea.style.height = compositionViewBounds.height + 'px'; - this.textarea.style.lineHeight = compositionViewBounds.height + 'px'; - } - if (!dontRecurse) { - setTimeout(function () { return _this.updateCompositionElements(true); }, 0); - } - }; - ; - CompositionHelper.prototype.clearTextareaPosition = function () { - this.textarea.style.left = ''; - this.textarea.style.top = ''; - }; - ; - return CompositionHelper; -}()); -exports.CompositionHelper = CompositionHelper; - - - -},{}],5:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -var C0; -(function (C0) { - C0.NUL = '\x00'; - C0.SOH = '\x01'; - C0.STX = '\x02'; - C0.ETX = '\x03'; - C0.EOT = '\x04'; - C0.ENQ = '\x05'; - C0.ACK = '\x06'; - C0.BEL = '\x07'; - C0.BS = '\x08'; - C0.HT = '\x09'; - C0.LF = '\x0a'; - C0.VT = '\x0b'; - C0.FF = '\x0c'; - C0.CR = '\x0d'; - C0.SO = '\x0e'; - C0.SI = '\x0f'; - C0.DLE = '\x10'; - C0.DC1 = '\x11'; - C0.DC2 = '\x12'; - C0.DC3 = '\x13'; - C0.DC4 = '\x14'; - C0.NAK = '\x15'; - C0.SYN = '\x16'; - C0.ETB = '\x17'; - C0.CAN = '\x18'; - C0.EM = '\x19'; - C0.SUB = '\x1a'; - C0.ESC = '\x1b'; - C0.FS = '\x1c'; - C0.GS = '\x1d'; - C0.RS = '\x1e'; - C0.US = '\x1f'; - C0.SP = '\x20'; - C0.DEL = '\x7f'; -})(C0 = exports.C0 || (exports.C0 = {})); -; - - - -},{}],6:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -; -var EventEmitter = (function () { - function EventEmitter() { - this._events = this._events || {}; - } - EventEmitter.prototype.on = function (type, listener) { - this._events[type] = this._events[type] || []; - this._events[type].push(listener); - }; - EventEmitter.prototype.off = function (type, listener) { - if (!this._events[type]) { - return; - } - var obj = this._events[type]; - var i = obj.length; - while (i--) { - if (obj[i] === listener || obj[i].listener === listener) { - obj.splice(i, 1); - return; - } - } - }; - EventEmitter.prototype.removeAllListeners = function (type) { - if (this._events[type]) { - delete this._events[type]; - } - }; - EventEmitter.prototype.once = function (type, listener) { - function on() { - var args = Array.prototype.slice.call(arguments); - this.off(type, on); - return listener.apply(this, args); - } - on.listener = listener; - return this.on(type, on); - }; - EventEmitter.prototype.emit = function (type) { - var args = []; - for (var _i = 1; _i < arguments.length; _i++) { - args[_i - 1] = arguments[_i]; - } - if (!this._events[type]) { - return; - } - var obj = this._events[type]; - for (var i = 0; i < obj.length; i++) { - obj[i].apply(this, args); - } - }; - EventEmitter.prototype.listeners = function (type) { - return this._events[type] || []; - }; - return EventEmitter; -}()); -exports.EventEmitter = EventEmitter; - - - -},{}],7:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -var EscapeSequences_1 = require("./EscapeSequences"); -var Charsets_1 = require("./Charsets"); -var InputHandler = (function () { - function InputHandler(_terminal) { - this._terminal = _terminal; - } - InputHandler.prototype.addChar = function (char, code) { - if (char >= ' ') { - var ch_width = exports.wcwidth(code); - if (this._terminal.charset && this._terminal.charset[char]) { - char = this._terminal.charset[char]; - } - var row = this._terminal.buffer.y + this._terminal.buffer.ybase; - if (!ch_width && this._terminal.buffer.x) { - if (this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1]) { - if (!this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][2]) { - if (this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 2]) - this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 2][1] += char; - } - else { - this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][1] += char; - } - this._terminal.updateRange(this._terminal.buffer.y); - } - return; - } - if (this._terminal.buffer.x + ch_width - 1 >= this._terminal.cols) { - if (this._terminal.wraparoundMode) { - this._terminal.buffer.x = 0; - this._terminal.buffer.y++; - if (this._terminal.buffer.y > this._terminal.buffer.scrollBottom) { - this._terminal.buffer.y--; - this._terminal.scroll(true); - } - else { - this._terminal.buffer.lines.get(this._terminal.buffer.y).isWrapped = true; - } - } - else { - if (ch_width === 2) - return; - } - } - row = this._terminal.buffer.y + this._terminal.buffer.ybase; - if (this._terminal.insertMode) { - for (var moves = 0; moves < ch_width; ++moves) { - var removed = this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).pop(); - if (removed[2] === 0 - && this._terminal.buffer.lines.get(row)[this._terminal.cols - 2] - && this._terminal.buffer.lines.get(row)[this._terminal.cols - 2][2] === 2) { - this._terminal.buffer.lines.get(row)[this._terminal.cols - 2] = [this._terminal.curAttr, ' ', 1]; - } - this._terminal.buffer.lines.get(row).splice(this._terminal.buffer.x, 0, [this._terminal.curAttr, ' ', 1]); - } - } - this._terminal.buffer.lines.get(row)[this._terminal.buffer.x] = [this._terminal.curAttr, char, ch_width]; - this._terminal.buffer.x++; - this._terminal.updateRange(this._terminal.buffer.y); - if (ch_width === 2) { - this._terminal.buffer.lines.get(row)[this._terminal.buffer.x] = [this._terminal.curAttr, '', 0]; - this._terminal.buffer.x++; - } - } - }; - InputHandler.prototype.bell = function () { - var _this = this; - if (!this._terminal.visualBell) { - return; - } - this._terminal.element.style.borderColor = 'white'; - setTimeout(function () { return _this._terminal.element.style.borderColor = ''; }, 10); - if (this._terminal.popOnBell) { - this._terminal.focus(); - } - }; - InputHandler.prototype.lineFeed = function () { - if (this._terminal.convertEol) { - this._terminal.buffer.x = 0; - } - this._terminal.buffer.y++; - if (this._terminal.buffer.y > this._terminal.buffer.scrollBottom) { - this._terminal.buffer.y--; - this._terminal.scroll(); - } - if (this._terminal.buffer.x >= this._terminal.cols) { - this._terminal.buffer.x--; - } - this._terminal.emit('lineFeed'); - }; - InputHandler.prototype.carriageReturn = function () { - this._terminal.buffer.x = 0; - }; - InputHandler.prototype.backspace = function () { - if (this._terminal.buffer.x > 0) { - this._terminal.buffer.x--; - } - }; - InputHandler.prototype.tab = function () { - this._terminal.buffer.x = this._terminal.nextStop(); - }; - InputHandler.prototype.shiftOut = function () { - this._terminal.setgLevel(1); - }; - InputHandler.prototype.shiftIn = function () { - this._terminal.setgLevel(0); - }; - InputHandler.prototype.insertChars = function (params) { - var param, row, j, ch; - param = params[0]; - if (param < 1) - param = 1; - row = this._terminal.buffer.y + this._terminal.buffer.ybase; - j = this._terminal.buffer.x; - ch = [this._terminal.eraseAttr(), ' ', 1]; - while (param-- && j < this._terminal.cols) { - this._terminal.buffer.lines.get(row).splice(j++, 0, ch); - this._terminal.buffer.lines.get(row).pop(); - } - }; - InputHandler.prototype.cursorUp = function (params) { - var param = params[0]; - if (param < 1) { - param = 1; - } - this._terminal.buffer.y -= param; - if (this._terminal.buffer.y < 0) { - this._terminal.buffer.y = 0; - } - }; - InputHandler.prototype.cursorDown = function (params) { - var param = params[0]; - if (param < 1) { - param = 1; - } - this._terminal.buffer.y += param; - if (this._terminal.buffer.y >= this._terminal.rows) { - this._terminal.buffer.y = this._terminal.rows - 1; - } - if (this._terminal.buffer.x >= this._terminal.cols) { - this._terminal.buffer.x--; - } - }; - InputHandler.prototype.cursorForward = function (params) { - var param = params[0]; - if (param < 1) { - param = 1; - } - this._terminal.buffer.x += param; - if (this._terminal.buffer.x >= this._terminal.cols) { - this._terminal.buffer.x = this._terminal.cols - 1; - } - }; - InputHandler.prototype.cursorBackward = function (params) { - var param = params[0]; - if (param < 1) { - param = 1; - } - if (this._terminal.buffer.x >= this._terminal.cols) { - this._terminal.buffer.x--; - } - this._terminal.buffer.x -= param; - if (this._terminal.buffer.x < 0) { - this._terminal.buffer.x = 0; - } - }; - InputHandler.prototype.cursorNextLine = function (params) { - var param = params[0]; - if (param < 1) { - param = 1; - } - this._terminal.buffer.y += param; - if (this._terminal.buffer.y >= this._terminal.rows) { - this._terminal.buffer.y = this._terminal.rows - 1; - } - this._terminal.buffer.x = 0; - }; - InputHandler.prototype.cursorPrecedingLine = function (params) { - var param = params[0]; - if (param < 1) { - param = 1; - } - this._terminal.buffer.y -= param; - if (this._terminal.buffer.y < 0) { - this._terminal.buffer.y = 0; - } - this._terminal.buffer.x = 0; - }; - InputHandler.prototype.cursorCharAbsolute = function (params) { - var param = params[0]; - if (param < 1) { - param = 1; - } - this._terminal.buffer.x = param - 1; - }; - InputHandler.prototype.cursorPosition = function (params) { - var row, col; - row = params[0] - 1; - if (params.length >= 2) { - col = params[1] - 1; - } - else { - col = 0; - } - if (row < 0) { - row = 0; - } - else if (row >= this._terminal.rows) { - row = this._terminal.rows - 1; - } - if (col < 0) { - col = 0; - } - else if (col >= this._terminal.cols) { - col = this._terminal.cols - 1; - } - this._terminal.buffer.x = col; - this._terminal.buffer.y = row; - }; - InputHandler.prototype.cursorForwardTab = function (params) { - var param = params[0] || 1; - while (param--) { - this._terminal.buffer.x = this._terminal.nextStop(); - } - }; - InputHandler.prototype.eraseInDisplay = function (params) { - var j; - switch (params[0]) { - case 0: - this._terminal.eraseRight(this._terminal.buffer.x, this._terminal.buffer.y); - j = this._terminal.buffer.y + 1; - for (; j < this._terminal.rows; j++) { - this._terminal.eraseLine(j); - } - break; - case 1: - this._terminal.eraseLeft(this._terminal.buffer.x, this._terminal.buffer.y); - j = this._terminal.buffer.y; - while (j--) { - this._terminal.eraseLine(j); - } - break; - case 2: - j = this._terminal.rows; - while (j--) - this._terminal.eraseLine(j); - break; - case 3: - var scrollBackSize = this._terminal.buffer.lines.length - this._terminal.rows; - if (scrollBackSize > 0) { - this._terminal.buffer.lines.trimStart(scrollBackSize); - this._terminal.buffer.ybase = Math.max(this._terminal.buffer.ybase - scrollBackSize, 0); - this._terminal.buffer.ydisp = Math.max(this._terminal.buffer.ydisp - scrollBackSize, 0); - this._terminal.emit('scroll', 0); - } - break; - } - }; - InputHandler.prototype.eraseInLine = function (params) { - switch (params[0]) { - case 0: - this._terminal.eraseRight(this._terminal.buffer.x, this._terminal.buffer.y); - break; - case 1: - this._terminal.eraseLeft(this._terminal.buffer.x, this._terminal.buffer.y); - break; - case 2: - this._terminal.eraseLine(this._terminal.buffer.y); - break; - } - }; - InputHandler.prototype.insertLines = function (params) { - var param, row, j; - param = params[0]; - if (param < 1) { - param = 1; - } - row = this._terminal.buffer.y + this._terminal.buffer.ybase; - j = this._terminal.rows - 1 - this._terminal.buffer.scrollBottom; - j = this._terminal.rows - 1 + this._terminal.buffer.ybase - j + 1; - while (param--) { - if (this._terminal.buffer.lines.length === this._terminal.buffer.lines.maxLength) { - this._terminal.buffer.lines.trimStart(1); - this._terminal.buffer.ybase--; - this._terminal.buffer.ydisp--; - row--; - j--; - } - this._terminal.buffer.lines.splice(row, 0, this._terminal.blankLine(true)); - this._terminal.buffer.lines.splice(j, 1); - } - this._terminal.updateRange(this._terminal.buffer.y); - this._terminal.updateRange(this._terminal.buffer.scrollBottom); - }; - InputHandler.prototype.deleteLines = function (params) { - var param, row, j; - param = params[0]; - if (param < 1) { - param = 1; - } - row = this._terminal.buffer.y + this._terminal.buffer.ybase; - j = this._terminal.rows - 1 - this._terminal.buffer.scrollBottom; - j = this._terminal.rows - 1 + this._terminal.buffer.ybase - j; - while (param--) { - if (this._terminal.buffer.lines.length === this._terminal.buffer.lines.maxLength) { - this._terminal.buffer.lines.trimStart(1); - this._terminal.buffer.ybase -= 1; - this._terminal.buffer.ydisp -= 1; - } - this._terminal.buffer.lines.splice(j + 1, 0, this._terminal.blankLine(true)); - this._terminal.buffer.lines.splice(row, 1); - } - this._terminal.updateRange(this._terminal.buffer.y); - this._terminal.updateRange(this._terminal.buffer.scrollBottom); - }; - InputHandler.prototype.deleteChars = function (params) { - var param, row, ch; - param = params[0]; - if (param < 1) { - param = 1; - } - row = this._terminal.buffer.y + this._terminal.buffer.ybase; - ch = [this._terminal.eraseAttr(), ' ', 1]; - while (param--) { - this._terminal.buffer.lines.get(row).splice(this._terminal.buffer.x, 1); - this._terminal.buffer.lines.get(row).push(ch); - } - }; - InputHandler.prototype.scrollUp = function (params) { - var param = params[0] || 1; - while (param--) { - this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollTop, 1); - this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollBottom, 0, this._terminal.blankLine()); - } - this._terminal.updateRange(this._terminal.buffer.scrollTop); - this._terminal.updateRange(this._terminal.buffer.scrollBottom); - }; - InputHandler.prototype.scrollDown = function (params) { - var param = params[0] || 1; - while (param--) { - this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollBottom, 1); - this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollTop, 0, this._terminal.blankLine()); - } - this._terminal.updateRange(this._terminal.buffer.scrollTop); - this._terminal.updateRange(this._terminal.buffer.scrollBottom); - }; - InputHandler.prototype.eraseChars = function (params) { - var param, row, j, ch; - param = params[0]; - if (param < 1) { - param = 1; - } - row = this._terminal.buffer.y + this._terminal.buffer.ybase; - j = this._terminal.buffer.x; - ch = [this._terminal.eraseAttr(), ' ', 1]; - while (param-- && j < this._terminal.cols) { - this._terminal.buffer.lines.get(row)[j++] = ch; - } - }; - InputHandler.prototype.cursorBackwardTab = function (params) { - var param = params[0] || 1; - while (param--) { - this._terminal.buffer.x = this._terminal.prevStop(); - } - }; - InputHandler.prototype.charPosAbsolute = function (params) { - var param = params[0]; - if (param < 1) { - param = 1; - } - this._terminal.buffer.x = param - 1; - if (this._terminal.buffer.x >= this._terminal.cols) { - this._terminal.buffer.x = this._terminal.cols - 1; - } - }; - InputHandler.prototype.HPositionRelative = function (params) { - var param = params[0]; - if (param < 1) { - param = 1; - } - this._terminal.buffer.x += param; - if (this._terminal.buffer.x >= this._terminal.cols) { - this._terminal.buffer.x = this._terminal.cols - 1; - } - }; - InputHandler.prototype.repeatPrecedingCharacter = function (params) { - var param = params[0] || 1, line = this._terminal.buffer.lines.get(this._terminal.buffer.ybase + this._terminal.buffer.y), ch = line[this._terminal.buffer.x - 1] || [this._terminal.defAttr, ' ', 1]; - while (param--) { - line[this._terminal.buffer.x++] = ch; - } - }; - InputHandler.prototype.sendDeviceAttributes = function (params) { - if (params[0] > 0) { - return; - } - if (!this._terminal.prefix) { - if (this._terminal.is('xterm') || this._terminal.is('rxvt-unicode') || this._terminal.is('screen')) { - this._terminal.send(EscapeSequences_1.C0.ESC + '[?1;2c'); - } - else if (this._terminal.is('linux')) { - this._terminal.send(EscapeSequences_1.C0.ESC + '[?6c'); - } - } - else if (this._terminal.prefix === '>') { - if (this._terminal.is('xterm')) { - this._terminal.send(EscapeSequences_1.C0.ESC + '[>0;276;0c'); - } - else if (this._terminal.is('rxvt-unicode')) { - this._terminal.send(EscapeSequences_1.C0.ESC + '[>85;95;0c'); - } - else if (this._terminal.is('linux')) { - this._terminal.send(params[0] + 'c'); - } - else if (this._terminal.is('screen')) { - this._terminal.send(EscapeSequences_1.C0.ESC + '[>83;40003;0c'); - } - } - }; - InputHandler.prototype.linePosAbsolute = function (params) { - var param = params[0]; - if (param < 1) { - param = 1; - } - this._terminal.buffer.y = param - 1; - if (this._terminal.buffer.y >= this._terminal.rows) { - this._terminal.buffer.y = this._terminal.rows - 1; - } - }; - InputHandler.prototype.VPositionRelative = function (params) { - var param = params[0]; - if (param < 1) { - param = 1; - } - this._terminal.buffer.y += param; - if (this._terminal.buffer.y >= this._terminal.rows) { - this._terminal.buffer.y = this._terminal.rows - 1; - } - if (this._terminal.buffer.x >= this._terminal.cols) { - this._terminal.buffer.x--; - } - }; - InputHandler.prototype.HVPosition = function (params) { - if (params[0] < 1) - params[0] = 1; - if (params[1] < 1) - params[1] = 1; - this._terminal.buffer.y = params[0] - 1; - if (this._terminal.buffer.y >= this._terminal.rows) { - this._terminal.buffer.y = this._terminal.rows - 1; - } - this._terminal.buffer.x = params[1] - 1; - if (this._terminal.buffer.x >= this._terminal.cols) { - this._terminal.buffer.x = this._terminal.cols - 1; - } - }; - InputHandler.prototype.tabClear = function (params) { - var param = params[0]; - if (param <= 0) { - delete this._terminal.buffer.tabs[this._terminal.buffer.x]; - } - else if (param === 3) { - this._terminal.buffer.tabs = {}; - } - }; - InputHandler.prototype.setMode = function (params) { - if (params.length > 1) { - for (var i = 0; i < params.length; i++) { - this.setMode([params[i]]); - } - return; - } - if (!this._terminal.prefix) { - switch (params[0]) { - case 4: - this._terminal.insertMode = true; - break; - case 20: - break; - } - } - else if (this._terminal.prefix === '?') { - switch (params[0]) { - case 1: - this._terminal.applicationCursor = true; - break; - case 2: - this._terminal.setgCharset(0, Charsets_1.DEFAULT_CHARSET); - this._terminal.setgCharset(1, Charsets_1.DEFAULT_CHARSET); - this._terminal.setgCharset(2, Charsets_1.DEFAULT_CHARSET); - this._terminal.setgCharset(3, Charsets_1.DEFAULT_CHARSET); - break; - case 3: - this._terminal.savedCols = this._terminal.cols; - this._terminal.resize(132, this._terminal.rows); - break; - case 6: - this._terminal.originMode = true; - break; - case 7: - this._terminal.wraparoundMode = true; - break; - case 12: - break; - case 66: - this._terminal.log('Serial port requested application keypad.'); - this._terminal.applicationKeypad = true; - this._terminal.viewport.syncScrollArea(); - break; - case 9: - case 1000: - case 1002: - case 1003: - this._terminal.x10Mouse = params[0] === 9; - this._terminal.vt200Mouse = params[0] === 1000; - this._terminal.normalMouse = params[0] > 1000; - this._terminal.mouseEvents = true; - this._terminal.element.classList.add('enable-mouse-events'); - this._terminal.selectionManager.disable(); - this._terminal.log('Binding to mouse events.'); - break; - case 1004: - this._terminal.sendFocus = true; - break; - case 1005: - this._terminal.utfMouse = true; - break; - case 1006: - this._terminal.sgrMouse = true; - break; - case 1015: - this._terminal.urxvtMouse = true; - break; - case 25: - this._terminal.cursorHidden = false; - break; - case 1049: - case 47: - case 1047: - this._terminal.buffers.activateAltBuffer(); - this._terminal.viewport.syncScrollArea(); - this._terminal.showCursor(); - break; - } - } - }; - InputHandler.prototype.resetMode = function (params) { - if (params.length > 1) { - for (var i = 0; i < params.length; i++) { - this.resetMode([params[i]]); - } - return; - } - if (!this._terminal.prefix) { - switch (params[0]) { - case 4: - this._terminal.insertMode = false; - break; - case 20: - break; - } - } - else if (this._terminal.prefix === '?') { - switch (params[0]) { - case 1: - this._terminal.applicationCursor = false; - break; - case 3: - if (this._terminal.cols === 132 && this._terminal.savedCols) { - this._terminal.resize(this._terminal.savedCols, this._terminal.rows); - } - delete this._terminal.savedCols; - break; - case 6: - this._terminal.originMode = false; - break; - case 7: - this._terminal.wraparoundMode = false; - break; - case 12: - break; - case 66: - this._terminal.log('Switching back to normal keypad.'); - this._terminal.applicationKeypad = false; - this._terminal.viewport.syncScrollArea(); - break; - case 9: - case 1000: - case 1002: - case 1003: - this._terminal.x10Mouse = false; - this._terminal.vt200Mouse = false; - this._terminal.normalMouse = false; - this._terminal.mouseEvents = false; - this._terminal.element.classList.remove('enable-mouse-events'); - this._terminal.selectionManager.enable(); - break; - case 1004: - this._terminal.sendFocus = false; - break; - case 1005: - this._terminal.utfMouse = false; - break; - case 1006: - this._terminal.sgrMouse = false; - break; - case 1015: - this._terminal.urxvtMouse = false; - break; - case 25: - this._terminal.cursorHidden = true; - break; - case 1049: - case 47: - case 1047: - this._terminal.buffers.activateNormalBuffer(); - this._terminal.selectionManager.setBuffer(this._terminal.buffer.lines); - this._terminal.refresh(0, this._terminal.rows - 1); - this._terminal.viewport.syncScrollArea(); - this._terminal.showCursor(); - break; - } - } - }; - InputHandler.prototype.charAttributes = function (params) { - if (params.length === 1 && params[0] === 0) { - this._terminal.curAttr = this._terminal.defAttr; - return; - } - var l = params.length, i = 0, flags = this._terminal.curAttr >> 18, fg = (this._terminal.curAttr >> 9) & 0x1ff, bg = this._terminal.curAttr & 0x1ff, p; - for (; i < l; i++) { - p = params[i]; - if (p >= 30 && p <= 37) { - fg = p - 30; - } - else if (p >= 40 && p <= 47) { - bg = p - 40; - } - else if (p >= 90 && p <= 97) { - p += 8; - fg = p - 90; - } - else if (p >= 100 && p <= 107) { - p += 8; - bg = p - 100; - } - else if (p === 0) { - flags = this._terminal.defAttr >> 18; - fg = (this._terminal.defAttr >> 9) & 0x1ff; - bg = this._terminal.defAttr & 0x1ff; - } - else if (p === 1) { - flags |= 1; - } - else if (p === 4) { - flags |= 2; - } - else if (p === 5) { - flags |= 4; - } - else if (p === 7) { - flags |= 8; - } - else if (p === 8) { - flags |= 16; - } - else if (p === 22) { - flags &= ~1; - } - else if (p === 24) { - flags &= ~2; - } - else if (p === 25) { - flags &= ~4; - } - else if (p === 27) { - flags &= ~8; - } - else if (p === 28) { - flags &= ~16; - } - else if (p === 39) { - fg = (this._terminal.defAttr >> 9) & 0x1ff; - } - else if (p === 49) { - bg = this._terminal.defAttr & 0x1ff; - } - else if (p === 38) { - if (params[i + 1] === 2) { - i += 2; - fg = this._terminal.matchColor(params[i] & 0xff, params[i + 1] & 0xff, params[i + 2] & 0xff); - if (fg === -1) - fg = 0x1ff; - i += 2; - } - else if (params[i + 1] === 5) { - i += 2; - p = params[i] & 0xff; - fg = p; - } - } - else if (p === 48) { - if (params[i + 1] === 2) { - i += 2; - bg = this._terminal.matchColor(params[i] & 0xff, params[i + 1] & 0xff, params[i + 2] & 0xff); - if (bg === -1) - bg = 0x1ff; - i += 2; - } - else if (params[i + 1] === 5) { - i += 2; - p = params[i] & 0xff; - bg = p; - } - } - else if (p === 100) { - fg = (this._terminal.defAttr >> 9) & 0x1ff; - bg = this._terminal.defAttr & 0x1ff; - } - else { - this._terminal.error('Unknown SGR attribute: %d.', p); - } - } - this._terminal.curAttr = (flags << 18) | (fg << 9) | bg; - }; - InputHandler.prototype.deviceStatus = function (params) { - if (!this._terminal.prefix) { - switch (params[0]) { - case 5: - this._terminal.send(EscapeSequences_1.C0.ESC + '[0n'); - break; - case 6: - this._terminal.send(EscapeSequences_1.C0.ESC + '[' - + (this._terminal.buffer.y + 1) - + ';' - + (this._terminal.buffer.x + 1) - + 'R'); - break; - } - } - else if (this._terminal.prefix === '?') { - switch (params[0]) { - case 6: - this._terminal.send(EscapeSequences_1.C0.ESC + '[?' - + (this._terminal.buffer.y + 1) - + ';' - + (this._terminal.buffer.x + 1) - + 'R'); - break; - case 15: - break; - case 25: - break; - case 26: - break; - case 53: - break; - } - } - }; - InputHandler.prototype.softReset = function (params) { - this._terminal.cursorHidden = false; - this._terminal.insertMode = false; - this._terminal.originMode = false; - this._terminal.wraparoundMode = true; - this._terminal.applicationKeypad = false; - this._terminal.viewport.syncScrollArea(); - this._terminal.applicationCursor = false; - this._terminal.buffer.scrollTop = 0; - this._terminal.buffer.scrollBottom = this._terminal.rows - 1; - this._terminal.curAttr = this._terminal.defAttr; - this._terminal.buffer.x = this._terminal.buffer.y = 0; - this._terminal.charset = null; - this._terminal.glevel = 0; - this._terminal.charsets = [null]; - }; - InputHandler.prototype.setCursorStyle = function (params) { - var param = params[0] < 1 ? 1 : params[0]; - switch (param) { - case 1: - case 2: - this._terminal.setOption('cursorStyle', 'block'); - break; - case 3: - case 4: - this._terminal.setOption('cursorStyle', 'underline'); - break; - case 5: - case 6: - this._terminal.setOption('cursorStyle', 'bar'); - break; - } - var isBlinking = param % 2 === 1; - this._terminal.setOption('cursorBlink', isBlinking); - }; - InputHandler.prototype.setScrollRegion = function (params) { - if (this._terminal.prefix) - return; - this._terminal.buffer.scrollTop = (params[0] || 1) - 1; - this._terminal.buffer.scrollBottom = (params[1] && params[1] <= this._terminal.rows ? params[1] : this._terminal.rows) - 1; - this._terminal.buffer.x = 0; - this._terminal.buffer.y = 0; - }; - InputHandler.prototype.saveCursor = function (params) { - this._terminal.buffer.savedX = this._terminal.buffer.x; - this._terminal.buffer.savedY = this._terminal.buffer.y; - }; - InputHandler.prototype.restoreCursor = function (params) { - this._terminal.buffer.x = this._terminal.buffer.savedX || 0; - this._terminal.buffer.y = this._terminal.buffer.savedY || 0; - }; - return InputHandler; -}()); -exports.InputHandler = InputHandler; -exports.wcwidth = (function (opts) { - var COMBINING_BMP = [ - [0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489], - [0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2], - [0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603], - [0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670], - [0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED], - [0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A], - [0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902], - [0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D], - [0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981], - [0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD], - [0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C], - [0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D], - [0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC], - [0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD], - [0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C], - [0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D], - [0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0], - [0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48], - [0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC], - [0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD], - [0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D], - [0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6], - [0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E], - [0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC], - [0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35], - [0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E], - [0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97], - [0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030], - [0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039], - [0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F], - [0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753], - [0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD], - [0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD], - [0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922], - [0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B], - [0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34], - [0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42], - [0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF], - [0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063], - [0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F], - [0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B], - [0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F], - [0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB], - ]; - var COMBINING_HIGH = [ - [0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F], - [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169], - [0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD], - [0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F], - [0xE0100, 0xE01EF] - ]; - function bisearch(ucs, data) { - var min = 0; - var max = data.length - 1; - var mid; - if (ucs < data[0][0] || ucs > data[max][1]) - return false; - while (max >= min) { - mid = (min + max) >> 1; - if (ucs > data[mid][1]) - min = mid + 1; - else if (ucs < data[mid][0]) - max = mid - 1; - else - return true; - } - return false; - } - function wcwidthBMP(ucs) { - if (ucs === 0) - return opts.nul; - if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) - return opts.control; - if (bisearch(ucs, COMBINING_BMP)) - return 0; - if (isWideBMP(ucs)) { - return 2; - } - return 1; - } - function isWideBMP(ucs) { - return (ucs >= 0x1100 && (ucs <= 0x115f || - ucs === 0x2329 || - ucs === 0x232a || - (ucs >= 0x2e80 && ucs <= 0xa4cf && ucs !== 0x303f) || - (ucs >= 0xac00 && ucs <= 0xd7a3) || - (ucs >= 0xf900 && ucs <= 0xfaff) || - (ucs >= 0xfe10 && ucs <= 0xfe19) || - (ucs >= 0xfe30 && ucs <= 0xfe6f) || - (ucs >= 0xff00 && ucs <= 0xff60) || - (ucs >= 0xffe0 && ucs <= 0xffe6))); - } - function wcwidthHigh(ucs) { - if (bisearch(ucs, COMBINING_HIGH)) - return 0; - if ((ucs >= 0x20000 && ucs <= 0x2fffd) || (ucs >= 0x30000 && ucs <= 0x3fffd)) { - return 2; - } - return 1; - } - var control = opts.control | 0; - var table = null; - function init_table() { - var CODEPOINTS = 65536; - var BITWIDTH = 2; - var ITEMSIZE = 32; - var CONTAINERSIZE = CODEPOINTS * BITWIDTH / ITEMSIZE; - var CODEPOINTS_PER_ITEM = ITEMSIZE / BITWIDTH; - table = (typeof Uint32Array === 'undefined') - ? new Array(CONTAINERSIZE) - : new Uint32Array(CONTAINERSIZE); - for (var i = 0; i < CONTAINERSIZE; ++i) { - var num = 0; - var pos = CODEPOINTS_PER_ITEM; - while (pos--) - num = (num << 2) | wcwidthBMP(CODEPOINTS_PER_ITEM * i + pos); - table[i] = num; - } - return table; - } - return function (num) { - num = num | 0; - if (num < 32) - return control | 0; - if (num < 127) - return 1; - var t = table || init_table(); - if (num < 65536) - return t[num >> 4] >> ((num & 15) << 1) & 3; - return wcwidthHigh(num); - }; -})({ nul: 0, control: 0 }); - - - -},{"./Charsets":3,"./EscapeSequences":5}],8:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -var INVALID_LINK_CLASS = 'xterm-invalid-link'; -var protocolClause = '(https?:\\/\\/)'; -var domainCharacterSet = '[\\da-z\\.-]+'; -var negatedDomainCharacterSet = '[^\\da-z\\.-]+'; -var domainBodyClause = '(' + domainCharacterSet + ')'; -var tldClause = '([a-z\\.]{2,6})'; -var ipClause = '((\\d{1,3}\\.){3}\\d{1,3})'; -var localHostClause = '(localhost)'; -var portClause = '(:\\d{1,5})'; -var hostClause = '((' + domainBodyClause + '\\.' + tldClause + ')|' + ipClause + '|' + localHostClause + ')' + portClause + '?'; -var pathClause = '(\\/[\\/\\w\\.\\-%~]*)*'; -var queryStringHashFragmentCharacterSet = '[0-9\\w\\[\\]\\(\\)\\/\\?\\!#@$%&\'*+,:;~\\=\\.\\-]*'; -var queryStringClause = '(\\?' + queryStringHashFragmentCharacterSet + ')?'; -var hashFragmentClause = '(#' + queryStringHashFragmentCharacterSet + ')?'; -var negatedPathCharacterSet = '[^\\/\\w\\.\\-%]+'; -var bodyClause = hostClause + pathClause + queryStringClause + hashFragmentClause; -var start = '(?:^|' + negatedDomainCharacterSet + ')('; -var end = ')($|' + negatedPathCharacterSet + ')'; -var strictUrlRegex = new RegExp(start + protocolClause + bodyClause + end); -var HYPERTEXT_LINK_MATCHER_ID = 0; -var Linkifier = (function () { - function Linkifier() { - this._nextLinkMatcherId = HYPERTEXT_LINK_MATCHER_ID; - this._rowTimeoutIds = []; - this._linkMatchers = []; - this.registerLinkMatcher(strictUrlRegex, null, { matchIndex: 1 }); - } - Linkifier.prototype.attachToDom = function (document, rows) { - this._document = document; - this._rows = rows; - }; - Linkifier.prototype.linkifyRow = function (rowIndex) { - if (!this._document) { - return; - } - var timeoutId = this._rowTimeoutIds[rowIndex]; - if (timeoutId) { - clearTimeout(timeoutId); - } - this._rowTimeoutIds[rowIndex] = setTimeout(this._linkifyRow.bind(this, rowIndex), Linkifier.TIME_BEFORE_LINKIFY); - }; - Linkifier.prototype.setHypertextLinkHandler = function (handler) { - this._linkMatchers[HYPERTEXT_LINK_MATCHER_ID].handler = handler; - }; - Linkifier.prototype.setHypertextValidationCallback = function (callback) { - this._linkMatchers[HYPERTEXT_LINK_MATCHER_ID].validationCallback = callback; - }; - Linkifier.prototype.registerLinkMatcher = function (regex, handler, options) { - if (options === void 0) { options = {}; } - if (this._nextLinkMatcherId !== HYPERTEXT_LINK_MATCHER_ID && !handler) { - throw new Error('handler must be defined'); - } - var matcher = { - id: this._nextLinkMatcherId++, - regex: regex, - handler: handler, - matchIndex: options.matchIndex, - validationCallback: options.validationCallback, - priority: options.priority || 0 - }; - this._addLinkMatcherToList(matcher); - return matcher.id; - }; - Linkifier.prototype._addLinkMatcherToList = function (matcher) { - if (this._linkMatchers.length === 0) { - this._linkMatchers.push(matcher); - return; - } - for (var i = this._linkMatchers.length - 1; i >= 0; i--) { - if (matcher.priority <= this._linkMatchers[i].priority) { - this._linkMatchers.splice(i + 1, 0, matcher); - return; - } - } - this._linkMatchers.splice(0, 0, matcher); - }; - Linkifier.prototype.deregisterLinkMatcher = function (matcherId) { - for (var i = 1; i < this._linkMatchers.length; i++) { - if (this._linkMatchers[i].id === matcherId) { - this._linkMatchers.splice(i, 1); - return true; - } - } - return false; - }; - Linkifier.prototype._linkifyRow = function (rowIndex) { - var row = this._rows[rowIndex]; - if (!row) { - return; - } - var text = row.textContent; - for (var i = 0; i < this._linkMatchers.length; i++) { - var matcher = this._linkMatchers[i]; - var linkElements = this._doLinkifyRow(row, matcher); - if (linkElements.length > 0) { - if (matcher.validationCallback) { - var _loop_1 = function (j) { - var element = linkElements[j]; - matcher.validationCallback(element.textContent, element, function (isValid) { - if (!isValid) { - element.classList.add(INVALID_LINK_CLASS); - } - }); - }; - for (var j = 0; j < linkElements.length; j++) { - _loop_1(j); - } - } - return; - } - } - }; - Linkifier.prototype._doLinkifyRow = function (row, matcher) { - var result = []; - var isHttpLinkMatcher = matcher.id === HYPERTEXT_LINK_MATCHER_ID; - var nodes = row.childNodes; - var match = row.textContent.match(matcher.regex); - if (!match || match.length === 0) { - return result; - } - var uri = match[typeof matcher.matchIndex !== 'number' ? 0 : matcher.matchIndex]; - var rowStartIndex = match.index + uri.length; - for (var i = 0; i < nodes.length; i++) { - var node = nodes[i]; - var searchIndex = node.textContent.indexOf(uri); - if (searchIndex >= 0) { - var linkElement = this._createAnchorElement(uri, matcher.handler, isHttpLinkMatcher); - if (node.textContent.length === uri.length) { - if (node.nodeType === 3) { - this._replaceNode(node, linkElement); - } - else { - var element = node; - if (element.nodeName === 'A') { - return result; - } - element.innerHTML = ''; - element.appendChild(linkElement); - } - } - else if (node.childNodes.length > 1) { - for (var j = 0; j < node.childNodes.length; j++) { - var childNode = node.childNodes[j]; - var childSearchIndex = childNode.textContent.indexOf(uri); - if (childSearchIndex !== -1) { - this._replaceNodeSubstringWithNode(childNode, linkElement, uri, childSearchIndex); - break; - } - } - } - else { - var nodesAdded = this._replaceNodeSubstringWithNode(node, linkElement, uri, searchIndex); - i += nodesAdded; - } - result.push(linkElement); - match = row.textContent.substring(rowStartIndex).match(matcher.regex); - if (!match || match.length === 0) { - return result; - } - uri = match[typeof matcher.matchIndex !== 'number' ? 0 : matcher.matchIndex]; - rowStartIndex += match.index + uri.length; - } - } - return result; - }; - Linkifier.prototype._createAnchorElement = function (uri, handler, isHypertextLinkHandler) { - var element = this._document.createElement('a'); - element.textContent = uri; - element.draggable = false; - if (isHypertextLinkHandler) { - element.href = uri; - element.target = '_blank'; - element.addEventListener('click', function (event) { - if (handler) { - return handler(event, uri); - } - }); - } - else { - element.addEventListener('click', function (event) { - if (element.classList.contains(INVALID_LINK_CLASS)) { - return; - } - return handler(event, uri); - }); - } - return element; - }; - Linkifier.prototype._replaceNode = function (oldNode) { - var newNodes = []; - for (var _i = 1; _i < arguments.length; _i++) { - newNodes[_i - 1] = arguments[_i]; - } - var parent = oldNode.parentNode; - for (var i = 0; i < newNodes.length; i++) { - parent.insertBefore(newNodes[i], oldNode); - } - parent.removeChild(oldNode); - }; - Linkifier.prototype._replaceNodeSubstringWithNode = function (targetNode, newNode, substring, substringIndex) { - if (targetNode.childNodes.length === 1) { - targetNode = targetNode.childNodes[0]; - } - if (targetNode.nodeType !== 3) { - throw new Error('targetNode must be a text node or only contain a single text node'); - } - var fullText = targetNode.textContent; - if (substringIndex === 0) { - var rightText_1 = fullText.substring(substring.length); - var rightTextNode_1 = this._document.createTextNode(rightText_1); - this._replaceNode(targetNode, newNode, rightTextNode_1); - return 0; - } - if (substringIndex === targetNode.textContent.length - substring.length) { - var leftText_1 = fullText.substring(0, substringIndex); - var leftTextNode_1 = this._document.createTextNode(leftText_1); - this._replaceNode(targetNode, leftTextNode_1, newNode); - return 0; - } - var leftText = fullText.substring(0, substringIndex); - var leftTextNode = this._document.createTextNode(leftText); - var rightText = fullText.substring(substringIndex + substring.length); - var rightTextNode = this._document.createTextNode(rightText); - this._replaceNode(targetNode, leftTextNode, newNode, rightTextNode); - return 1; - }; - return Linkifier; -}()); -Linkifier.TIME_BEFORE_LINKIFY = 200; -exports.Linkifier = Linkifier; - - - -},{}],9:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -var EscapeSequences_1 = require("./EscapeSequences"); -var Charsets_1 = require("./Charsets"); -var normalStateHandler = {}; -normalStateHandler[EscapeSequences_1.C0.BEL] = function (parser, handler) { return handler.bell(); }; -normalStateHandler[EscapeSequences_1.C0.LF] = function (parser, handler) { return handler.lineFeed(); }; -normalStateHandler[EscapeSequences_1.C0.VT] = normalStateHandler[EscapeSequences_1.C0.LF]; -normalStateHandler[EscapeSequences_1.C0.FF] = normalStateHandler[EscapeSequences_1.C0.LF]; -normalStateHandler[EscapeSequences_1.C0.CR] = function (parser, handler) { return handler.carriageReturn(); }; -normalStateHandler[EscapeSequences_1.C0.BS] = function (parser, handler) { return handler.backspace(); }; -normalStateHandler[EscapeSequences_1.C0.HT] = function (parser, handler) { return handler.tab(); }; -normalStateHandler[EscapeSequences_1.C0.SO] = function (parser, handler) { return handler.shiftOut(); }; -normalStateHandler[EscapeSequences_1.C0.SI] = function (parser, handler) { return handler.shiftIn(); }; -normalStateHandler[EscapeSequences_1.C0.ESC] = function (parser, handler) { return parser.setState(ParserState.ESCAPED); }; -var escapedStateHandler = {}; -escapedStateHandler['['] = function (parser, terminal) { - terminal.params = []; - terminal.currentParam = 0; - parser.setState(ParserState.CSI_PARAM); -}; -escapedStateHandler[']'] = function (parser, terminal) { - terminal.params = []; - terminal.currentParam = 0; - parser.setState(ParserState.OSC); -}; -escapedStateHandler['P'] = function (parser, terminal) { - terminal.params = []; - terminal.currentParam = 0; - parser.setState(ParserState.DCS); -}; -escapedStateHandler['_'] = function (parser, terminal) { - parser.setState(ParserState.IGNORE); -}; -escapedStateHandler['^'] = function (parser, terminal) { - parser.setState(ParserState.IGNORE); -}; -escapedStateHandler['c'] = function (parser, terminal) { - terminal.reset(); -}; -escapedStateHandler['E'] = function (parser, terminal) { - terminal.buffer.x = 0; - terminal.index(); - parser.setState(ParserState.NORMAL); -}; -escapedStateHandler['D'] = function (parser, terminal) { - terminal.index(); - parser.setState(ParserState.NORMAL); -}; -escapedStateHandler['M'] = function (parser, terminal) { - terminal.reverseIndex(); - parser.setState(ParserState.NORMAL); -}; -escapedStateHandler['%'] = function (parser, terminal) { - terminal.setgLevel(0); - terminal.setgCharset(0, Charsets_1.DEFAULT_CHARSET); - parser.setState(ParserState.NORMAL); - parser.skipNextChar(); -}; -escapedStateHandler[EscapeSequences_1.C0.CAN] = function (parser) { return parser.setState(ParserState.NORMAL); }; -var csiParamStateHandler = {}; -csiParamStateHandler['?'] = function (parser) { return parser.setPrefix('?'); }; -csiParamStateHandler['>'] = function (parser) { return parser.setPrefix('>'); }; -csiParamStateHandler['!'] = function (parser) { return parser.setPrefix('!'); }; -csiParamStateHandler['0'] = function (parser) { return parser.setParam(parser.getParam() * 10); }; -csiParamStateHandler['1'] = function (parser) { return parser.setParam(parser.getParam() * 10 + 1); }; -csiParamStateHandler['2'] = function (parser) { return parser.setParam(parser.getParam() * 10 + 2); }; -csiParamStateHandler['3'] = function (parser) { return parser.setParam(parser.getParam() * 10 + 3); }; -csiParamStateHandler['4'] = function (parser) { return parser.setParam(parser.getParam() * 10 + 4); }; -csiParamStateHandler['5'] = function (parser) { return parser.setParam(parser.getParam() * 10 + 5); }; -csiParamStateHandler['6'] = function (parser) { return parser.setParam(parser.getParam() * 10 + 6); }; -csiParamStateHandler['7'] = function (parser) { return parser.setParam(parser.getParam() * 10 + 7); }; -csiParamStateHandler['8'] = function (parser) { return parser.setParam(parser.getParam() * 10 + 8); }; -csiParamStateHandler['9'] = function (parser) { return parser.setParam(parser.getParam() * 10 + 9); }; -csiParamStateHandler['$'] = function (parser) { return parser.setPostfix('$'); }; -csiParamStateHandler['"'] = function (parser) { return parser.setPostfix('"'); }; -csiParamStateHandler[' '] = function (parser) { return parser.setPostfix(' '); }; -csiParamStateHandler['\''] = function (parser) { return parser.setPostfix('\''); }; -csiParamStateHandler[';'] = function (parser) { return parser.finalizeParam(); }; -csiParamStateHandler[EscapeSequences_1.C0.CAN] = function (parser) { return parser.setState(ParserState.NORMAL); }; -var csiStateHandler = {}; -csiStateHandler['@'] = function (handler, params, prefix) { return handler.insertChars(params); }; -csiStateHandler['A'] = function (handler, params, prefix) { return handler.cursorUp(params); }; -csiStateHandler['B'] = function (handler, params, prefix) { return handler.cursorDown(params); }; -csiStateHandler['C'] = function (handler, params, prefix) { return handler.cursorForward(params); }; -csiStateHandler['D'] = function (handler, params, prefix) { return handler.cursorBackward(params); }; -csiStateHandler['E'] = function (handler, params, prefix) { return handler.cursorNextLine(params); }; -csiStateHandler['F'] = function (handler, params, prefix) { return handler.cursorPrecedingLine(params); }; -csiStateHandler['G'] = function (handler, params, prefix) { return handler.cursorCharAbsolute(params); }; -csiStateHandler['H'] = function (handler, params, prefix) { return handler.cursorPosition(params); }; -csiStateHandler['I'] = function (handler, params, prefix) { return handler.cursorForwardTab(params); }; -csiStateHandler['J'] = function (handler, params, prefix) { return handler.eraseInDisplay(params); }; -csiStateHandler['K'] = function (handler, params, prefix) { return handler.eraseInLine(params); }; -csiStateHandler['L'] = function (handler, params, prefix) { return handler.insertLines(params); }; -csiStateHandler['M'] = function (handler, params, prefix) { return handler.deleteLines(params); }; -csiStateHandler['P'] = function (handler, params, prefix) { return handler.deleteChars(params); }; -csiStateHandler['S'] = function (handler, params, prefix) { return handler.scrollUp(params); }; -csiStateHandler['T'] = function (handler, params, prefix) { - if (params.length < 2 && !prefix) { - handler.scrollDown(params); - } -}; -csiStateHandler['X'] = function (handler, params, prefix) { return handler.eraseChars(params); }; -csiStateHandler['Z'] = function (handler, params, prefix) { return handler.cursorBackwardTab(params); }; -csiStateHandler['`'] = function (handler, params, prefix) { return handler.charPosAbsolute(params); }; -csiStateHandler['a'] = function (handler, params, prefix) { return handler.HPositionRelative(params); }; -csiStateHandler['b'] = function (handler, params, prefix) { return handler.repeatPrecedingCharacter(params); }; -csiStateHandler['c'] = function (handler, params, prefix) { return handler.sendDeviceAttributes(params); }; -csiStateHandler['d'] = function (handler, params, prefix) { return handler.linePosAbsolute(params); }; -csiStateHandler['e'] = function (handler, params, prefix) { return handler.VPositionRelative(params); }; -csiStateHandler['f'] = function (handler, params, prefix) { return handler.HVPosition(params); }; -csiStateHandler['g'] = function (handler, params, prefix) { return handler.tabClear(params); }; -csiStateHandler['h'] = function (handler, params, prefix) { return handler.setMode(params); }; -csiStateHandler['l'] = function (handler, params, prefix) { return handler.resetMode(params); }; -csiStateHandler['m'] = function (handler, params, prefix) { return handler.charAttributes(params); }; -csiStateHandler['n'] = function (handler, params, prefix) { return handler.deviceStatus(params); }; -csiStateHandler['p'] = function (handler, params, prefix) { - switch (prefix) { - case '!': - handler.softReset(params); - break; - } -}; -csiStateHandler['q'] = function (handler, params, prefix, postfix) { - if (postfix === ' ') { - handler.setCursorStyle(params); - } -}; -csiStateHandler['r'] = function (handler, params) { return handler.setScrollRegion(params); }; -csiStateHandler['s'] = function (handler, params) { return handler.saveCursor(params); }; -csiStateHandler['u'] = function (handler, params) { return handler.restoreCursor(params); }; -csiStateHandler[EscapeSequences_1.C0.CAN] = function (handler, params, prefix, postfix, parser) { return parser.setState(ParserState.NORMAL); }; -var ParserState; -(function (ParserState) { - ParserState[ParserState["NORMAL"] = 0] = "NORMAL"; - ParserState[ParserState["ESCAPED"] = 1] = "ESCAPED"; - ParserState[ParserState["CSI_PARAM"] = 2] = "CSI_PARAM"; - ParserState[ParserState["CSI"] = 3] = "CSI"; - ParserState[ParserState["OSC"] = 4] = "OSC"; - ParserState[ParserState["CHARSET"] = 5] = "CHARSET"; - ParserState[ParserState["DCS"] = 6] = "DCS"; - ParserState[ParserState["IGNORE"] = 7] = "IGNORE"; -})(ParserState || (ParserState = {})); -var Parser = (function () { - function Parser(_inputHandler, _terminal) { - this._inputHandler = _inputHandler; - this._terminal = _terminal; - this._state = ParserState.NORMAL; - } - Parser.prototype.parse = function (data) { - var l = data.length, j, cs, ch, code, low; - if (this._terminal.debug) { - this._terminal.log('data: ' + data); - } - this._position = 0; - if (this._terminal.surrogate_high) { - data = this._terminal.surrogate_high + data; - this._terminal.surrogate_high = ''; - } - for (; this._position < l; this._position++) { - ch = data[this._position]; - code = data.charCodeAt(this._position); - if (0xD800 <= code && code <= 0xDBFF) { - low = data.charCodeAt(this._position + 1); - if (isNaN(low)) { - this._terminal.surrogate_high = ch; - continue; - } - code = ((code - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000; - ch += data.charAt(this._position + 1); - } - if (0xDC00 <= code && code <= 0xDFFF) - continue; - switch (this._state) { - case ParserState.NORMAL: - if (ch in normalStateHandler) { - normalStateHandler[ch](this, this._inputHandler); - } - else { - this._inputHandler.addChar(ch, code); - } - break; - case ParserState.ESCAPED: - if (ch in escapedStateHandler) { - escapedStateHandler[ch](this, this._terminal); - break; - } - switch (ch) { - case '(': - case ')': - case '*': - case '+': - case '-': - case '.': - switch (ch) { - case '(': - this._terminal.gcharset = 0; - break; - case ')': - this._terminal.gcharset = 1; - break; - case '*': - this._terminal.gcharset = 2; - break; - case '+': - this._terminal.gcharset = 3; - break; - case '-': - this._terminal.gcharset = 1; - break; - case '.': - this._terminal.gcharset = 2; - break; - } - this._state = ParserState.CHARSET; - break; - case '/': - this._terminal.gcharset = 3; - this._state = ParserState.CHARSET; - this._position--; - break; - case 'N': - break; - case 'O': - break; - case 'n': - this._terminal.setgLevel(2); - break; - case 'o': - this._terminal.setgLevel(3); - break; - case '|': - this._terminal.setgLevel(3); - break; - case '}': - this._terminal.setgLevel(2); - break; - case '~': - this._terminal.setgLevel(1); - break; - case '7': - this._inputHandler.saveCursor(); - this._state = ParserState.NORMAL; - break; - case '8': - this._inputHandler.restoreCursor(); - this._state = ParserState.NORMAL; - break; - case '#': - this._state = ParserState.NORMAL; - this._position++; - break; - case 'H': - this._terminal.tabSet(); - this._state = ParserState.NORMAL; - break; - case '=': - this._terminal.log('Serial port requested application keypad.'); - this._terminal.applicationKeypad = true; - this._terminal.viewport.syncScrollArea(); - this._state = ParserState.NORMAL; - break; - case '>': - this._terminal.log('Switching back to normal keypad.'); - this._terminal.applicationKeypad = false; - this._terminal.viewport.syncScrollArea(); - this._state = ParserState.NORMAL; - break; - default: - this._state = ParserState.NORMAL; - this._terminal.error('Unknown ESC control: %s.', ch); - break; - } - break; - case ParserState.CHARSET: - if (ch in Charsets_1.CHARSETS) { - cs = Charsets_1.CHARSETS[ch]; - if (ch === '/') { - this.skipNextChar(); - } - } - else { - cs = Charsets_1.DEFAULT_CHARSET; - } - this._terminal.setgCharset(this._terminal.gcharset, cs); - this._terminal.gcharset = null; - this._state = ParserState.NORMAL; - break; - case ParserState.OSC: - if (ch === EscapeSequences_1.C0.ESC || ch === EscapeSequences_1.C0.BEL) { - if (ch === EscapeSequences_1.C0.ESC) - this._position++; - this._terminal.params.push(this._terminal.currentParam); - switch (this._terminal.params[0]) { - case 0: - case 1: - case 2: - if (this._terminal.params[1]) { - this._terminal.title = this._terminal.params[1]; - this._terminal.handleTitle(this._terminal.title); - } - break; - case 3: - break; - case 4: - case 5: - break; - case 10: - case 11: - case 12: - case 13: - case 14: - case 15: - case 16: - case 17: - case 18: - case 19: - break; - case 46: - break; - case 50: - break; - case 51: - break; - case 52: - break; - case 104: - case 105: - case 110: - case 111: - case 112: - case 113: - case 114: - case 115: - case 116: - case 117: - case 118: - break; - } - this._terminal.params = []; - this._terminal.currentParam = 0; - this._state = ParserState.NORMAL; - } - else { - if (!this._terminal.params.length) { - if (ch >= '0' && ch <= '9') { - this._terminal.currentParam = - this._terminal.currentParam * 10 + ch.charCodeAt(0) - 48; - } - else if (ch === ';') { - this._terminal.params.push(this._terminal.currentParam); - this._terminal.currentParam = ''; - } - } - else { - this._terminal.currentParam += ch; - } - } - break; - case ParserState.CSI_PARAM: - if (ch in csiParamStateHandler) { - csiParamStateHandler[ch](this); - break; - } - this.finalizeParam(); - this._state = ParserState.CSI; - case ParserState.CSI: - if (ch in csiStateHandler) { - if (this._terminal.debug) { - this._terminal.log("CSI " + (this._terminal.prefix ? this._terminal.prefix : '') + " " + (this._terminal.params ? this._terminal.params.join(';') : '') + " " + (this._terminal.postfix ? this._terminal.postfix : '') + " " + ch); - } - csiStateHandler[ch](this._inputHandler, this._terminal.params, this._terminal.prefix, this._terminal.postfix, this); - } - else { - this._terminal.error('Unknown CSI code: %s.', ch); - } - this._state = ParserState.NORMAL; - this._terminal.prefix = ''; - this._terminal.postfix = ''; - break; - case ParserState.DCS: - if (ch === EscapeSequences_1.C0.ESC || ch === EscapeSequences_1.C0.BEL) { - if (ch === EscapeSequences_1.C0.ESC) - this._position++; - var pt = void 0; - var valid = void 0; - switch (this._terminal.prefix) { - case '': - break; - case '$q': - pt = this._terminal.currentParam; - valid = false; - switch (pt) { - case '"q': - pt = '0"q'; - break; - case '"p': - pt = '61"p'; - break; - case 'r': - pt = '' - + (this._terminal.buffer.scrollTop + 1) - + ';' - + (this._terminal.buffer.scrollBottom + 1) - + 'r'; - break; - case 'm': - pt = '0m'; - break; - default: - this._terminal.error('Unknown DCS Pt: %s.', pt); - pt = ''; - break; - } - this._terminal.send(EscapeSequences_1.C0.ESC + 'P' + +valid + '$r' + pt + EscapeSequences_1.C0.ESC + '\\'); - break; - case '+p': - break; - case '+q': - pt = this._terminal.currentParam; - valid = false; - this._terminal.send(EscapeSequences_1.C0.ESC + 'P' + +valid + '+r' + pt + EscapeSequences_1.C0.ESC + '\\'); - break; - default: - this._terminal.error('Unknown DCS prefix: %s.', this._terminal.prefix); - break; - } - this._terminal.currentParam = 0; - this._terminal.prefix = ''; - this._state = ParserState.NORMAL; - } - else if (!this._terminal.currentParam) { - if (!this._terminal.prefix && ch !== '$' && ch !== '+') { - this._terminal.currentParam = ch; - } - else if (this._terminal.prefix.length === 2) { - this._terminal.currentParam = ch; - } - else { - this._terminal.prefix += ch; - } - } - else { - this._terminal.currentParam += ch; - } - break; - case ParserState.IGNORE: - if (ch === EscapeSequences_1.C0.ESC || ch === EscapeSequences_1.C0.BEL) { - if (ch === EscapeSequences_1.C0.ESC) - this._position++; - this._state = ParserState.NORMAL; - } - break; - } - } - return this._state; - }; - Parser.prototype.setState = function (state) { - this._state = state; - }; - Parser.prototype.setPrefix = function (prefix) { - this._terminal.prefix = prefix; - }; - Parser.prototype.setPostfix = function (postfix) { - this._terminal.postfix = postfix; - }; - Parser.prototype.setParam = function (param) { - this._terminal.currentParam = param; - }; - Parser.prototype.getParam = function () { - return this._terminal.currentParam; - }; - Parser.prototype.finalizeParam = function () { - this._terminal.params.push(this._terminal.currentParam); - this._terminal.currentParam = 0; - }; - Parser.prototype.skipNextChar = function () { - this._position++; - }; - return Parser; -}()); -exports.Parser = Parser; - - - -},{"./Charsets":3,"./EscapeSequences":5}],10:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -var DomElementObjectPool_1 = require("./utils/DomElementObjectPool"); -var MAX_REFRESH_FRAME_SKIP = 5; -var FLAGS; -(function (FLAGS) { - FLAGS[FLAGS["BOLD"] = 1] = "BOLD"; - FLAGS[FLAGS["UNDERLINE"] = 2] = "UNDERLINE"; - FLAGS[FLAGS["BLINK"] = 4] = "BLINK"; - FLAGS[FLAGS["INVERSE"] = 8] = "INVERSE"; - FLAGS[FLAGS["INVISIBLE"] = 16] = "INVISIBLE"; -})(FLAGS || (FLAGS = {})); -; -var brokenBold = null; -var Renderer = (function () { - function Renderer(_terminal) { - this._terminal = _terminal; - this._refreshRowsQueue = []; - this._refreshFramesSkipped = 0; - this._refreshAnimationFrame = null; - this._spanElementObjectPool = new DomElementObjectPool_1.DomElementObjectPool('span'); - if (brokenBold === null) { - brokenBold = checkBoldBroken(this._terminal.element); - } - this._spanElementObjectPool = new DomElementObjectPool_1.DomElementObjectPool('span'); - } - Renderer.prototype.queueRefresh = function (start, end) { - this._refreshRowsQueue.push({ start: start, end: end }); - if (!this._refreshAnimationFrame) { - this._refreshAnimationFrame = window.requestAnimationFrame(this._refreshLoop.bind(this)); - } - }; - Renderer.prototype._refreshLoop = function () { - var skipFrame = this._terminal.writeBuffer.length > 0 && this._refreshFramesSkipped++ <= MAX_REFRESH_FRAME_SKIP; - if (skipFrame) { - this._refreshAnimationFrame = window.requestAnimationFrame(this._refreshLoop.bind(this)); - return; - } - this._refreshFramesSkipped = 0; - var start; - var end; - if (this._refreshRowsQueue.length > 4) { - start = 0; - end = this._terminal.rows - 1; - } - else { - start = this._refreshRowsQueue[0].start; - end = this._refreshRowsQueue[0].end; - for (var 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; - this._refresh(start, end); - }; - Renderer.prototype._refresh = function (start, end) { - var parent; - if (end - start >= this._terminal.rows / 2) { - parent = this._terminal.element.parentNode; - if (parent) { - this._terminal.element.removeChild(this._terminal.rowContainer); - } - } - var width = this._terminal.cols; - var y = start; - if (end >= this._terminal.rows) { - this._terminal.log('`end` is too large. Most likely a bad CSR.'); - end = this._terminal.rows - 1; - } - for (; y <= end; y++) { - var row = y + this._terminal.buffer.ydisp; - var line = this._terminal.buffer.lines.get(row); - var x = void 0; - if (this._terminal.buffer.y === y - (this._terminal.buffer.ybase - this._terminal.buffer.ydisp) && - this._terminal.cursorState && - !this._terminal.cursorHidden) { - x = this._terminal.buffer.x; - } - else { - x = -1; - } - var attr = this._terminal.defAttr; - var documentFragment = document.createDocumentFragment(); - var innerHTML = ''; - var currentElement = void 0; - while (this._terminal.children[y].children.length) { - var child = this._terminal.children[y].children[0]; - this._terminal.children[y].removeChild(child); - this._spanElementObjectPool.release(child); - } - for (var i = 0; i < width; i++) { - var data = line[i][0]; - var ch = line[i][1]; - var ch_width = line[i][2]; - var isCursor = i === x; - if (!ch_width) { - continue; - } - if (data !== attr || isCursor) { - if (attr !== this._terminal.defAttr && !isCursor) { - if (innerHTML) { - currentElement.innerHTML = innerHTML; - innerHTML = ''; - } - documentFragment.appendChild(currentElement); - currentElement = null; - } - if (data !== this._terminal.defAttr || isCursor) { - if (innerHTML && !currentElement) { - currentElement = this._spanElementObjectPool.acquire(); - } - if (currentElement) { - if (innerHTML) { - currentElement.innerHTML = innerHTML; - innerHTML = ''; - } - documentFragment.appendChild(currentElement); - } - currentElement = this._spanElementObjectPool.acquire(); - var bg = data & 0x1ff; - var fg = (data >> 9) & 0x1ff; - var flags = data >> 18; - if (isCursor) { - currentElement.classList.add('reverse-video'); - currentElement.classList.add('terminal-cursor'); - } - if (flags & FLAGS.BOLD) { - if (!brokenBold) { - currentElement.classList.add('xterm-bold'); - } - if (fg < 8) { - fg += 8; - } - } - if (flags & FLAGS.UNDERLINE) { - currentElement.classList.add('xterm-underline'); - } - if (flags & FLAGS.BLINK) { - currentElement.classList.add('xterm-blink'); - } - if (flags & FLAGS.INVERSE) { - var temp = bg; - bg = fg; - fg = temp; - if ((flags & 1) && fg < 8) { - fg += 8; - } - } - if (flags & FLAGS.INVISIBLE && !isCursor) { - currentElement.classList.add('xterm-hidden'); - } - if (flags & FLAGS.INVERSE) { - if (bg === 257) { - bg = 15; - } - if (fg === 256) { - fg = 0; - } - } - if (bg < 256) { - currentElement.classList.add("xterm-bg-color-" + bg); - } - if (fg < 256) { - currentElement.classList.add("xterm-color-" + fg); - } - } - } - if (ch_width === 2) { - innerHTML += "" + ch + ""; - } - else if (ch.charCodeAt(0) > 255) { - innerHTML += "" + ch + ""; - } - else { - switch (ch) { - case '&': - innerHTML += '&'; - break; - case '<': - innerHTML += '<'; - break; - case '>': - innerHTML += '>'; - break; - default: - if (ch <= ' ') { - innerHTML += ' '; - } - else { - innerHTML += ch; - } - break; - } - } - attr = isCursor ? -1 : data; - } - if (innerHTML && !currentElement) { - currentElement = this._spanElementObjectPool.acquire(); - } - if (currentElement) { - if (innerHTML) { - currentElement.innerHTML = innerHTML; - innerHTML = ''; - } - documentFragment.appendChild(currentElement); - currentElement = null; - } - this._terminal.children[y].appendChild(documentFragment); - } - if (parent) { - this._terminal.element.appendChild(this._terminal.rowContainer); - } - this._terminal.emit('refresh', { element: this._terminal.element, start: start, end: end }); - }; - ; - Renderer.prototype.refreshSelection = function (start, end) { - while (this._terminal.selectionContainer.children.length) { - this._terminal.selectionContainer.removeChild(this._terminal.selectionContainer.children[0]); - } - if (!start || !end) { - return; - } - var viewportStartRow = start[1] - this._terminal.buffer.ydisp; - var viewportEndRow = end[1] - this._terminal.buffer.ydisp; - var viewportCappedStartRow = Math.max(viewportStartRow, 0); - var viewportCappedEndRow = Math.min(viewportEndRow, this._terminal.rows - 1); - if (viewportCappedStartRow >= this._terminal.rows || viewportCappedEndRow < 0) { - return; - } - var documentFragment = document.createDocumentFragment(); - var startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0; - var endCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : this._terminal.cols; - documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow, startCol, endCol)); - var middleRowsCount = viewportCappedEndRow - viewportCappedStartRow - 1; - documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow + 1, 0, this._terminal.cols, middleRowsCount)); - if (viewportCappedStartRow !== viewportCappedEndRow) { - var endCol_1 = viewportEndRow === viewportCappedEndRow ? end[0] : this._terminal.cols; - documentFragment.appendChild(this._createSelectionElement(viewportCappedEndRow, 0, endCol_1)); - } - this._terminal.selectionContainer.appendChild(documentFragment); - }; - Renderer.prototype._createSelectionElement = function (row, colStart, colEnd, rowCount) { - if (rowCount === void 0) { rowCount = 1; } - var element = document.createElement('div'); - element.style.height = rowCount * this._terminal.charMeasure.height + "px"; - element.style.top = row * this._terminal.charMeasure.height + "px"; - element.style.left = colStart * this._terminal.charMeasure.width + "px"; - element.style.width = this._terminal.charMeasure.width * (colEnd - colStart) + "px"; - return element; - }; - return Renderer; -}()); -exports.Renderer = Renderer; -function checkBoldBroken(terminal) { - var document = terminal.ownerDocument; - var el = document.createElement('span'); - el.innerHTML = 'hello world'; - terminal.appendChild(el); - var w1 = el.offsetWidth; - var h1 = el.offsetHeight; - el.style.fontWeight = 'bold'; - var w2 = el.offsetWidth; - var h2 = el.offsetHeight; - terminal.removeChild(el); - return w1 !== w2 || h1 !== h2; -} - - - -},{"./utils/DomElementObjectPool":19}],11:[function(require,module,exports){ -"use strict"; -var __extends = (this && this.__extends) || (function () { - var extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -Object.defineProperty(exports, "__esModule", { value: true }); -var Mouse = require("./utils/Mouse"); -var Browser = require("./utils/Browser"); -var EventEmitter_1 = require("./EventEmitter"); -var SelectionModel_1 = require("./SelectionModel"); -var BufferLine_1 = require("./utils/BufferLine"); -var DRAG_SCROLL_MAX_THRESHOLD = 50; -var DRAG_SCROLL_MAX_SPEED = 15; -var DRAG_SCROLL_INTERVAL = 50; -var WORD_SEPARATORS = ' ()[]{}\'"'; -var LINE_DATA_CHAR_INDEX = 1; -var LINE_DATA_WIDTH_INDEX = 2; -var NON_BREAKING_SPACE_CHAR = String.fromCharCode(160); -var ALL_NON_BREAKING_SPACE_REGEX = new RegExp(NON_BREAKING_SPACE_CHAR, 'g'); -var SelectionMode; -(function (SelectionMode) { - SelectionMode[SelectionMode["NORMAL"] = 0] = "NORMAL"; - SelectionMode[SelectionMode["WORD"] = 1] = "WORD"; - SelectionMode[SelectionMode["LINE"] = 2] = "LINE"; -})(SelectionMode || (SelectionMode = {})); -var SelectionManager = (function (_super) { - __extends(SelectionManager, _super); - function SelectionManager(_terminal, _buffer, _rowContainer, _charMeasure) { - var _this = _super.call(this) || this; - _this._terminal = _terminal; - _this._buffer = _buffer; - _this._rowContainer = _rowContainer; - _this._charMeasure = _charMeasure; - _this._enabled = true; - _this._initListeners(); - _this.enable(); - _this._model = new SelectionModel_1.SelectionModel(_terminal); - _this._activeSelectionMode = SelectionMode.NORMAL; - return _this; - } - SelectionManager.prototype._initListeners = function () { - var _this = this; - this._mouseMoveListener = function (event) { return _this._onMouseMove(event); }; - this._mouseUpListener = function (event) { return _this._onMouseUp(event); }; - this._rowContainer.addEventListener('mousedown', function (event) { return _this._onMouseDown(event); }); - this._buffer.on('trim', function (amount) { return _this._onTrim(amount); }); - }; - SelectionManager.prototype.disable = function () { - this.clearSelection(); - this._enabled = false; - }; - SelectionManager.prototype.enable = function () { - this._enabled = true; - }; - SelectionManager.prototype.setBuffer = function (buffer) { - this._buffer = buffer; - this.clearSelection(); - }; - Object.defineProperty(SelectionManager.prototype, "selectionStart", { - get: function () { return this._model.finalSelectionStart; }, - enumerable: true, - configurable: true - }); - Object.defineProperty(SelectionManager.prototype, "selectionEnd", { - get: function () { return this._model.finalSelectionEnd; }, - enumerable: true, - configurable: true - }); - Object.defineProperty(SelectionManager.prototype, "hasSelection", { - get: function () { - var start = this._model.finalSelectionStart; - var end = this._model.finalSelectionEnd; - if (!start || !end) { - return false; - } - return start[0] !== end[0] || start[1] !== end[1]; - }, - enumerable: true, - configurable: true - }); - Object.defineProperty(SelectionManager.prototype, "selectionText", { - get: function () { - var start = this._model.finalSelectionStart; - var end = this._model.finalSelectionEnd; - if (!start || !end) { - return ''; - } - var startRowEndCol = start[1] === end[1] ? end[0] : null; - var result = []; - result.push(BufferLine_1.translateBufferLineToString(this._buffer.get(start[1]), true, start[0], startRowEndCol)); - for (var i = start[1] + 1; i <= end[1] - 1; i++) { - var bufferLine = this._buffer.get(i); - var lineText = BufferLine_1.translateBufferLineToString(bufferLine, true); - if (bufferLine.isWrapped) { - result[result.length - 1] += lineText; - } - else { - result.push(lineText); - } - } - if (start[1] !== end[1]) { - var bufferLine = this._buffer.get(end[1]); - var lineText = BufferLine_1.translateBufferLineToString(bufferLine, true, 0, end[0]); - if (bufferLine.isWrapped) { - result[result.length - 1] += lineText; - } - else { - result.push(lineText); - } - } - var formattedResult = result.map(function (line) { - return line.replace(ALL_NON_BREAKING_SPACE_REGEX, ' '); - }).join(Browser.isMSWindows ? '\r\n' : '\n'); - return formattedResult; - }, - enumerable: true, - configurable: true - }); - SelectionManager.prototype.clearSelection = function () { - this._model.clearSelection(); - this._removeMouseDownListeners(); - this.refresh(); - }; - SelectionManager.prototype.refresh = function (isNewSelection) { - var _this = this; - if (!this._refreshAnimationFrame) { - this._refreshAnimationFrame = window.requestAnimationFrame(function () { return _this._refresh(); }); - } - if (Browser.isLinux && isNewSelection) { - var selectionText = this.selectionText; - if (selectionText.length) { - this.emit('newselection', this.selectionText); - } - } - }; - SelectionManager.prototype._refresh = function () { - this._refreshAnimationFrame = null; - this.emit('refresh', { start: this._model.finalSelectionStart, end: this._model.finalSelectionEnd }); - }; - SelectionManager.prototype.selectAll = function () { - this._model.isSelectAllActive = true; - this.refresh(); - }; - SelectionManager.prototype._onTrim = function (amount) { - var needsRefresh = this._model.onTrim(amount); - if (needsRefresh) { - this.refresh(); - } - }; - SelectionManager.prototype._getMouseBufferCoords = function (event) { - var coords = Mouse.getCoords(event, this._rowContainer, this._charMeasure, this._terminal.cols, this._terminal.rows, true); - if (!coords) { - return null; - } - coords[0]--; - coords[1]--; - coords[1] += this._terminal.buffer.ydisp; - return coords; - }; - SelectionManager.prototype._getMouseEventScrollAmount = function (event) { - var offset = Mouse.getCoordsRelativeToElement(event, this._rowContainer)[1]; - var terminalHeight = this._terminal.rows * this._charMeasure.height; - if (offset >= 0 && offset <= terminalHeight) { - return 0; - } - if (offset > terminalHeight) { - offset -= terminalHeight; - } - offset = Math.min(Math.max(offset, -DRAG_SCROLL_MAX_THRESHOLD), DRAG_SCROLL_MAX_THRESHOLD); - offset /= DRAG_SCROLL_MAX_THRESHOLD; - return (offset / Math.abs(offset)) + Math.round(offset * (DRAG_SCROLL_MAX_SPEED - 1)); - }; - SelectionManager.prototype._onMouseDown = function (event) { - if (event.button === 2 && this.hasSelection) { - event.stopPropagation(); - return; - } - if (event.button !== 0) { - return; - } - if (!this._enabled) { - var shouldForceSelection = Browser.isMac && event.altKey; - if (!shouldForceSelection) { - return; - } - event.stopPropagation(); - } - event.preventDefault(); - this._dragScrollAmount = 0; - if (this._enabled && event.shiftKey) { - this._onIncrementalClick(event); - } - else { - if (event.detail === 1) { - this._onSingleClick(event); - } - else if (event.detail === 2) { - this._onDoubleClick(event); - } - else if (event.detail === 3) { - this._onTripleClick(event); - } - } - this._addMouseDownListeners(); - this.refresh(true); - }; - SelectionManager.prototype._addMouseDownListeners = function () { - var _this = this; - this._rowContainer.ownerDocument.addEventListener('mousemove', this._mouseMoveListener); - this._rowContainer.ownerDocument.addEventListener('mouseup', this._mouseUpListener); - this._dragScrollIntervalTimer = setInterval(function () { return _this._dragScroll(); }, DRAG_SCROLL_INTERVAL); - }; - SelectionManager.prototype._removeMouseDownListeners = function () { - this._rowContainer.ownerDocument.removeEventListener('mousemove', this._mouseMoveListener); - this._rowContainer.ownerDocument.removeEventListener('mouseup', this._mouseUpListener); - clearInterval(this._dragScrollIntervalTimer); - this._dragScrollIntervalTimer = null; - }; - SelectionManager.prototype._onIncrementalClick = function (event) { - if (this._model.selectionStart) { - this._model.selectionEnd = this._getMouseBufferCoords(event); - } - }; - SelectionManager.prototype._onSingleClick = function (event) { - this._model.selectionStartLength = 0; - this._model.isSelectAllActive = false; - this._activeSelectionMode = SelectionMode.NORMAL; - this._model.selectionStart = this._getMouseBufferCoords(event); - if (!this._model.selectionStart) { - return; - } - this._model.selectionEnd = null; - var line = this._buffer.get(this._model.selectionStart[1]); - if (!line) { - return; - } - var char = line[this._model.selectionStart[0]]; - if (char[LINE_DATA_WIDTH_INDEX] === 0) { - this._model.selectionStart[0]++; - } - }; - SelectionManager.prototype._onDoubleClick = function (event) { - var coords = this._getMouseBufferCoords(event); - if (coords) { - this._activeSelectionMode = SelectionMode.WORD; - this._selectWordAt(coords); - } - }; - SelectionManager.prototype._onTripleClick = function (event) { - var coords = this._getMouseBufferCoords(event); - if (coords) { - this._activeSelectionMode = SelectionMode.LINE; - this._selectLineAt(coords[1]); - } - }; - SelectionManager.prototype._onMouseMove = function (event) { - var previousSelectionEnd = this._model.selectionEnd ? [this._model.selectionEnd[0], this._model.selectionEnd[1]] : null; - this._model.selectionEnd = this._getMouseBufferCoords(event); - if (!this._model.selectionEnd) { - this.refresh(true); - return; - } - if (this._activeSelectionMode === SelectionMode.LINE) { - if (this._model.selectionEnd[1] < this._model.selectionStart[1]) { - this._model.selectionEnd[0] = 0; - } - else { - this._model.selectionEnd[0] = this._terminal.cols; - } - } - else if (this._activeSelectionMode === SelectionMode.WORD) { - this._selectToWordAt(this._model.selectionEnd); - } - this._dragScrollAmount = this._getMouseEventScrollAmount(event); - if (this._dragScrollAmount > 0) { - this._model.selectionEnd[0] = this._terminal.cols - 1; - } - else if (this._dragScrollAmount < 0) { - this._model.selectionEnd[0] = 0; - } - if (this._model.selectionEnd[1] < this._buffer.length) { - var char = this._buffer.get(this._model.selectionEnd[1])[this._model.selectionEnd[0]]; - if (char && char[2] === 0) { - this._model.selectionEnd[0]++; - } - } - if (!previousSelectionEnd || - previousSelectionEnd[0] !== this._model.selectionEnd[0] || - previousSelectionEnd[1] !== this._model.selectionEnd[1]) { - this.refresh(true); - } - }; - SelectionManager.prototype._dragScroll = function () { - if (this._dragScrollAmount) { - this._terminal.scrollDisp(this._dragScrollAmount, false); - if (this._dragScrollAmount > 0) { - this._model.selectionEnd = [this._terminal.cols - 1, this._terminal.buffer.ydisp + this._terminal.rows]; - } - else { - this._model.selectionEnd = [0, this._terminal.buffer.ydisp]; - } - this.refresh(); - } - }; - SelectionManager.prototype._onMouseUp = function (event) { - this._removeMouseDownListeners(); - }; - SelectionManager.prototype._convertViewportColToCharacterIndex = function (bufferLine, coords) { - var charIndex = coords[0]; - for (var i = 0; coords[0] >= i; i++) { - var char = bufferLine[i]; - if (char[LINE_DATA_WIDTH_INDEX] === 0) { - charIndex--; - } - } - return charIndex; - }; - SelectionManager.prototype.setSelection = function (col, row, length) { - this._model.clearSelection(); - this._removeMouseDownListeners(); - this._model.selectionStart = [col, row]; - this._model.selectionStartLength = length; - this.refresh(); - }; - SelectionManager.prototype._getWordAt = function (coords) { - var bufferLine = this._buffer.get(coords[1]); - if (!bufferLine) { - return null; - } - var line = BufferLine_1.translateBufferLineToString(bufferLine, false); - var endIndex = this._convertViewportColToCharacterIndex(bufferLine, coords); - var startIndex = endIndex; - var charOffset = coords[0] - startIndex; - var leftWideCharCount = 0; - var rightWideCharCount = 0; - if (line.charAt(startIndex) === ' ') { - while (startIndex > 0 && line.charAt(startIndex - 1) === ' ') { - startIndex--; - } - while (endIndex < line.length && line.charAt(endIndex + 1) === ' ') { - endIndex++; - } - } - else { - var startCol = coords[0]; - var endCol = coords[0]; - if (bufferLine[startCol][LINE_DATA_WIDTH_INDEX] === 0) { - leftWideCharCount++; - startCol--; - } - if (bufferLine[endCol][LINE_DATA_WIDTH_INDEX] === 2) { - rightWideCharCount++; - endCol++; - } - while (startIndex > 0 && !this._isCharWordSeparator(line.charAt(startIndex - 1))) { - if (bufferLine[startCol - 1][LINE_DATA_WIDTH_INDEX] === 0) { - leftWideCharCount++; - startCol--; - } - startIndex--; - startCol--; - } - while (endIndex + 1 < line.length && !this._isCharWordSeparator(line.charAt(endIndex + 1))) { - if (bufferLine[endCol + 1][LINE_DATA_WIDTH_INDEX] === 2) { - rightWideCharCount++; - endCol++; - } - endIndex++; - endCol++; - } - } - var start = startIndex + charOffset - leftWideCharCount; - var length = Math.min(endIndex - startIndex + leftWideCharCount + rightWideCharCount + 1, this._terminal.cols); - return { start: start, length: length }; - }; - SelectionManager.prototype._selectWordAt = function (coords) { - var wordPosition = this._getWordAt(coords); - if (wordPosition) { - this._model.selectionStart = [wordPosition.start, coords[1]]; - this._model.selectionStartLength = wordPosition.length; - } - }; - SelectionManager.prototype._selectToWordAt = function (coords) { - var wordPosition = this._getWordAt(coords); - if (wordPosition) { - this._model.selectionEnd = [this._model.areSelectionValuesReversed() ? wordPosition.start : (wordPosition.start + wordPosition.length), coords[1]]; - } - }; - SelectionManager.prototype._isCharWordSeparator = function (char) { - return WORD_SEPARATORS.indexOf(char) >= 0; - }; - SelectionManager.prototype._selectLineAt = function (line) { - this._model.selectionStart = [0, line]; - this._model.selectionStartLength = this._terminal.cols; - }; - return SelectionManager; -}(EventEmitter_1.EventEmitter)); -exports.SelectionManager = SelectionManager; - - - -},{"./EventEmitter":6,"./SelectionModel":12,"./utils/Browser":15,"./utils/BufferLine":16,"./utils/Mouse":21}],12:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -var SelectionModel = (function () { - function SelectionModel(_terminal) { - this._terminal = _terminal; - this.clearSelection(); - } - SelectionModel.prototype.clearSelection = function () { - this.selectionStart = null; - this.selectionEnd = null; - this.isSelectAllActive = false; - this.selectionStartLength = 0; - }; - Object.defineProperty(SelectionModel.prototype, "finalSelectionStart", { - get: function () { - if (this.isSelectAllActive) { - return [0, 0]; - } - if (!this.selectionEnd || !this.selectionStart) { - return this.selectionStart; - } - return this.areSelectionValuesReversed() ? this.selectionEnd : this.selectionStart; - }, - enumerable: true, - configurable: true - }); - Object.defineProperty(SelectionModel.prototype, "finalSelectionEnd", { - get: function () { - if (this.isSelectAllActive) { - return [this._terminal.cols, this._terminal.buffer.ybase + this._terminal.rows - 1]; - } - if (!this.selectionStart) { - return null; - } - if (!this.selectionEnd || this.areSelectionValuesReversed()) { - return [this.selectionStart[0] + this.selectionStartLength, this.selectionStart[1]]; - } - if (this.selectionStartLength) { - if (this.selectionEnd[1] === this.selectionStart[1]) { - return [Math.max(this.selectionStart[0] + this.selectionStartLength, this.selectionEnd[0]), this.selectionEnd[1]]; - } - } - return this.selectionEnd; - }, - enumerable: true, - configurable: true - }); - SelectionModel.prototype.areSelectionValuesReversed = function () { - var start = this.selectionStart; - var end = this.selectionEnd; - return start[1] > end[1] || (start[1] === end[1] && start[0] > end[0]); - }; - SelectionModel.prototype.onTrim = function (amount) { - if (this.selectionStart) { - this.selectionStart[1] -= amount; - } - if (this.selectionEnd) { - this.selectionEnd[1] -= amount; - } - if (this.selectionEnd && this.selectionEnd[1] < 0) { - this.clearSelection(); - return true; - } - if (this.selectionStart && this.selectionStart[1] < 0) { - this.selectionStart[1] = 0; - } - return false; - }; - return SelectionModel; -}()); -exports.SelectionModel = SelectionModel; - - - -},{}],13:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -var Viewport = (function () { - function Viewport(terminal, viewportElement, scrollArea, charMeasure) { - var _this = this; - this.terminal = terminal; - this.viewportElement = viewportElement; - this.scrollArea = scrollArea; - this.charMeasure = charMeasure; - this.currentRowHeight = 0; - this.lastRecordedBufferLength = 0; - this.lastRecordedViewportHeight = 0; - this.terminal.on('scroll', this.syncScrollArea.bind(this)); - this.terminal.on('resize', this.syncScrollArea.bind(this)); - this.viewportElement.addEventListener('scroll', this.onScroll.bind(this)); - setTimeout(function () { return _this.syncScrollArea(); }, 0); - } - Viewport.prototype.refresh = function () { - if (this.charMeasure.height > 0) { - var rowHeightChanged = this.charMeasure.height !== this.currentRowHeight; - if (rowHeightChanged) { - this.currentRowHeight = this.charMeasure.height; - this.viewportElement.style.lineHeight = this.charMeasure.height + 'px'; - this.terminal.rowContainer.style.lineHeight = this.charMeasure.height + 'px'; - } - var viewportHeightChanged = this.lastRecordedViewportHeight !== this.terminal.rows; - if (rowHeightChanged || viewportHeightChanged) { - this.lastRecordedViewportHeight = this.terminal.rows; - this.viewportElement.style.height = this.charMeasure.height * this.terminal.rows + 'px'; - this.terminal.selectionContainer.style.height = this.viewportElement.style.height; - } - this.scrollArea.style.height = (this.charMeasure.height * this.lastRecordedBufferLength) + 'px'; - } - }; - Viewport.prototype.syncScrollArea = function () { - if (this.lastRecordedBufferLength !== this.terminal.buffer.lines.length) { - this.lastRecordedBufferLength = this.terminal.buffer.lines.length; - this.refresh(); - } - else if (this.lastRecordedViewportHeight !== this.terminal.rows) { - this.refresh(); - } - else { - if (this.charMeasure.height !== this.currentRowHeight) { - this.refresh(); - } - } - var scrollTop = this.terminal.buffer.ydisp * this.currentRowHeight; - if (this.viewportElement.scrollTop !== scrollTop) { - this.viewportElement.scrollTop = scrollTop; - } - }; - Viewport.prototype.onScroll = function (ev) { - var newRow = Math.round(this.viewportElement.scrollTop / this.currentRowHeight); - var diff = newRow - this.terminal.buffer.ydisp; - this.terminal.scrollDisp(diff, true); - }; - Viewport.prototype.onWheel = function (ev) { - if (ev.deltaY === 0) { - return; - } - var multiplier = 1; - if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) { - multiplier = this.currentRowHeight; - } - else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) { - multiplier = this.currentRowHeight * this.terminal.rows; - } - this.viewportElement.scrollTop += ev.deltaY * multiplier; - ev.preventDefault(); - }; - ; - Viewport.prototype.onTouchStart = function (ev) { - this.lastTouchY = ev.touches[0].pageY; - }; - ; - Viewport.prototype.onTouchMove = function (ev) { - var deltaY = this.lastTouchY - ev.touches[0].pageY; - this.lastTouchY = ev.touches[0].pageY; - if (deltaY === 0) { - return; - } - this.viewportElement.scrollTop += deltaY; - ev.preventDefault(); - }; - ; - return Viewport; -}()); -exports.Viewport = Viewport; - - - -},{}],14:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -function prepareTextForTerminal(text, isMSWindows) { - if (isMSWindows) { - return text.replace(/\r?\n/g, '\r'); - } - return text; -} -exports.prepareTextForTerminal = prepareTextForTerminal; -function copyHandler(ev, term, selectionManager) { - if (term.browser.isMSIE) { - window.clipboardData.setData('Text', selectionManager.selectionText); - } - else { - ev.clipboardData.setData('text/plain', selectionManager.selectionText); - } - ev.preventDefault(); -} -exports.copyHandler = copyHandler; -function pasteHandler(ev, term) { - ev.stopPropagation(); - var text; - var dispatchPaste = function (text) { - text = prepareTextForTerminal(text, term.browser.isMSWindows); - term.handler(text); - term.textarea.value = ''; - term.emit('paste', text); - return term.cancel(ev); - }; - if (term.browser.isMSIE) { - if (window.clipboardData) { - text = window.clipboardData.getData('Text'); - dispatchPaste(text); - } - } - else { - if (ev.clipboardData) { - text = ev.clipboardData.getData('text/plain'); - dispatchPaste(text); - } - } -} -exports.pasteHandler = pasteHandler; -function moveTextAreaUnderMouseCursor(ev, textarea) { - textarea.style.position = 'fixed'; - textarea.style.width = '20px'; - textarea.style.height = '20px'; - textarea.style.left = (ev.clientX - 10) + 'px'; - textarea.style.top = (ev.clientY - 10) + 'px'; - textarea.style.zIndex = '1000'; - textarea.focus(); - setTimeout(function () { - textarea.style.position = null; - textarea.style.width = null; - textarea.style.height = null; - textarea.style.left = null; - textarea.style.top = null; - textarea.style.zIndex = null; - }, 4); -} -exports.moveTextAreaUnderMouseCursor = moveTextAreaUnderMouseCursor; -function rightClickHandler(ev, textarea, selectionManager) { - moveTextAreaUnderMouseCursor(ev, textarea); - textarea.value = selectionManager.selectionText; - textarea.select(); -} -exports.rightClickHandler = rightClickHandler; - - - -},{}],15:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -var Generic_1 = require("./Generic"); -var isNode = (typeof navigator === 'undefined') ? true : false; -var userAgent = (isNode) ? 'node' : navigator.userAgent; -var platform = (isNode) ? 'node' : navigator.platform; -exports.isFirefox = !!~userAgent.indexOf('Firefox'); -exports.isMSIE = !!~userAgent.indexOf('MSIE') || !!~userAgent.indexOf('Trident'); -exports.isMac = Generic_1.contains(['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'], platform); -exports.isIpad = platform === 'iPad'; -exports.isIphone = platform === 'iPhone'; -exports.isMSWindows = Generic_1.contains(['Windows', 'Win16', 'Win32', 'WinCE'], platform); -exports.isLinux = platform.indexOf('Linux') >= 0; - - - -},{"./Generic":20}],16:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -var LINE_DATA_CHAR_INDEX = 1; -var LINE_DATA_WIDTH_INDEX = 2; -function translateBufferLineToString(line, trimRight, startCol, endCol) { - if (startCol === void 0) { startCol = 0; } - if (endCol === void 0) { endCol = null; } - var lineString = ''; - var widthAdjustedStartCol = startCol; - var widthAdjustedEndCol = endCol; - for (var i = 0; i < line.length; i++) { - var char = line[i]; - lineString += char[LINE_DATA_CHAR_INDEX]; - if (char[LINE_DATA_WIDTH_INDEX] === 0) { - if (startCol >= i) { - widthAdjustedStartCol--; - } - if (endCol >= i) { - widthAdjustedEndCol--; - } - } - } - var finalEndCol = widthAdjustedEndCol || line.length; - if (trimRight) { - var rightWhitespaceIndex = lineString.search(/\s+$/); - if (rightWhitespaceIndex !== -1) { - finalEndCol = Math.min(finalEndCol, rightWhitespaceIndex); - } - if (finalEndCol <= widthAdjustedStartCol) { - return ''; - } - } - return lineString.substring(widthAdjustedStartCol, finalEndCol); -} -exports.translateBufferLineToString = translateBufferLineToString; - - - -},{}],17:[function(require,module,exports){ -"use strict"; -var __extends = (this && this.__extends) || (function () { - var extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -Object.defineProperty(exports, "__esModule", { value: true }); -var EventEmitter_js_1 = require("../EventEmitter.js"); -var CharMeasure = (function (_super) { - __extends(CharMeasure, _super); - function CharMeasure(document, parentElement) { - var _this = _super.call(this) || this; - _this._document = document; - _this._parentElement = parentElement; - return _this; - } - Object.defineProperty(CharMeasure.prototype, "width", { - get: function () { - return this._width; - }, - enumerable: true, - configurable: true - }); - Object.defineProperty(CharMeasure.prototype, "height", { - get: function () { - return this._height; - }, - enumerable: true, - configurable: true - }); - CharMeasure.prototype.measure = function () { - var _this = this; - if (!this._measureElement) { - this._measureElement = this._document.createElement('span'); - this._measureElement.style.position = 'absolute'; - this._measureElement.style.top = '0'; - this._measureElement.style.left = '-9999em'; - this._measureElement.textContent = 'W'; - this._measureElement.setAttribute('aria-hidden', 'true'); - this._parentElement.appendChild(this._measureElement); - setTimeout(function () { return _this._doMeasure(); }, 0); - } - else { - this._doMeasure(); - } - }; - CharMeasure.prototype._doMeasure = function () { - var geometry = this._measureElement.getBoundingClientRect(); - if (geometry.width === 0 || geometry.height === 0) { - return; - } - if (this._width !== geometry.width || this._height !== geometry.height) { - this._width = geometry.width; - this._height = geometry.height; - this.emit('charsizechanged'); - } - }; - return CharMeasure; -}(EventEmitter_js_1.EventEmitter)); -exports.CharMeasure = CharMeasure; - - - -},{"../EventEmitter.js":6}],18:[function(require,module,exports){ -"use strict"; -var __extends = (this && this.__extends) || (function () { - var extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -Object.defineProperty(exports, "__esModule", { value: true }); -var EventEmitter_1 = require("../EventEmitter"); -var CircularList = (function (_super) { - __extends(CircularList, _super); - function CircularList(maxLength) { - var _this = _super.call(this) || this; - _this._array = new Array(maxLength); - _this._startIndex = 0; - _this._length = 0; - return _this; - } - Object.defineProperty(CircularList.prototype, "maxLength", { - get: function () { - return this._array.length; - }, - set: function (newMaxLength) { - var newArray = new Array(newMaxLength); - for (var i = 0; i < Math.min(newMaxLength, this.length); i++) { - newArray[i] = this._array[this._getCyclicIndex(i)]; - } - this._array = newArray; - this._startIndex = 0; - }, - enumerable: true, - configurable: true - }); - Object.defineProperty(CircularList.prototype, "length", { - get: function () { - return this._length; - }, - set: function (newLength) { - if (newLength > this._length) { - for (var i = this._length; i < newLength; i++) { - this._array[i] = undefined; - } - } - this._length = newLength; - }, - enumerable: true, - configurable: true - }); - Object.defineProperty(CircularList.prototype, "forEach", { - get: function () { - var _this = this; - return function (callbackfn) { - var i = 0; - var length = _this.length; - for (var i_1 = 0; i_1 < length; i_1++) { - callbackfn(_this.get(i_1), i_1); - } - }; - }, - enumerable: true, - configurable: true - }); - CircularList.prototype.get = function (index) { - return this._array[this._getCyclicIndex(index)]; - }; - CircularList.prototype.set = function (index, value) { - this._array[this._getCyclicIndex(index)] = value; - }; - CircularList.prototype.push = function (value) { - this._array[this._getCyclicIndex(this._length)] = value; - if (this._length === this.maxLength) { - this._startIndex++; - if (this._startIndex === this.maxLength) { - this._startIndex = 0; - } - this.emit('trim', 1); - } - else { - this._length++; - } - }; - CircularList.prototype.pop = function () { - return this._array[this._getCyclicIndex(this._length-- - 1)]; - }; - CircularList.prototype.splice = function (start, deleteCount) { - var items = []; - for (var _i = 2; _i < arguments.length; _i++) { - items[_i - 2] = arguments[_i]; - } - if (deleteCount) { - for (var i = start; i < this._length - deleteCount; i++) { - this._array[this._getCyclicIndex(i)] = this._array[this._getCyclicIndex(i + deleteCount)]; - } - this._length -= deleteCount; - } - if (items && items.length) { - for (var i = this._length - 1; i >= start; i--) { - this._array[this._getCyclicIndex(i + items.length)] = this._array[this._getCyclicIndex(i)]; - } - for (var i = 0; i < items.length; i++) { - this._array[this._getCyclicIndex(start + i)] = items[i]; - } - if (this._length + items.length > this.maxLength) { - var countToTrim = (this._length + items.length) - this.maxLength; - this._startIndex += countToTrim; - this._length = this.maxLength; - this.emit('trim', countToTrim); - } - else { - this._length += items.length; - } - } - }; - CircularList.prototype.trimStart = function (count) { - if (count > this._length) { - count = this._length; - } - this._startIndex += count; - this._length -= count; - this.emit('trim', count); - }; - CircularList.prototype.shiftElements = function (start, count, offset) { - if (count <= 0) { - return; - } - if (start < 0 || start >= this._length) { - throw new Error('start argument out of range'); - } - if (start + offset < 0) { - throw new Error('Cannot shift elements in list beyond index 0'); - } - if (offset > 0) { - for (var i = count - 1; i >= 0; i--) { - this.set(start + i + offset, this.get(start + i)); - } - var expandListBy = (start + count + offset) - this._length; - if (expandListBy > 0) { - this._length += expandListBy; - while (this._length > this.maxLength) { - this._length--; - this._startIndex++; - this.emit('trim', 1); - } - } - } - else { - for (var i = 0; i < count; i++) { - this.set(start + i + offset, this.get(start + i)); - } - } - }; - CircularList.prototype._getCyclicIndex = function (index) { - return (this._startIndex + index) % this.maxLength; - }; - return CircularList; -}(EventEmitter_1.EventEmitter)); -exports.CircularList = CircularList; - - - -},{"../EventEmitter":6}],19:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -var DomElementObjectPool = (function () { - function DomElementObjectPool(type) { - this.type = type; - this._type = type; - this._pool = []; - this._inUse = {}; - } - DomElementObjectPool.prototype.acquire = function () { - var element; - if (this._pool.length === 0) { - element = this._createNew(); - } - else { - element = this._pool.pop(); - } - this._inUse[element.getAttribute(DomElementObjectPool.OBJECT_ID_ATTRIBUTE)] = element; - return element; - }; - DomElementObjectPool.prototype.release = function (element) { - if (!this._inUse[element.getAttribute(DomElementObjectPool.OBJECT_ID_ATTRIBUTE)]) { - throw new Error('Could not release an element not yet acquired'); - } - delete this._inUse[element.getAttribute(DomElementObjectPool.OBJECT_ID_ATTRIBUTE)]; - this._cleanElement(element); - this._pool.push(element); - }; - DomElementObjectPool.prototype._createNew = function () { - var element = document.createElement(this._type); - var id = DomElementObjectPool._objectCount++; - element.setAttribute(DomElementObjectPool.OBJECT_ID_ATTRIBUTE, id.toString(10)); - return element; - }; - DomElementObjectPool.prototype._cleanElement = function (element) { - element.className = ''; - element.innerHTML = ''; - }; - return DomElementObjectPool; -}()); -DomElementObjectPool.OBJECT_ID_ATTRIBUTE = 'data-obj-id'; -DomElementObjectPool._objectCount = 0; -exports.DomElementObjectPool = DomElementObjectPool; - - - -},{}],20:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -function contains(arr, el) { - return arr.indexOf(el) >= 0; -} -exports.contains = contains; -; - - - -},{}],21:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -function getCoordsRelativeToElement(event, element) { - if (event.pageX == null) { - return null; - } - var x = event.pageX; - var y = event.pageY; - while (element && element !== self.document.documentElement) { - x -= element.offsetLeft; - y -= element.offsetTop; - element = 'offsetParent' in element ? element.offsetParent : element.parentElement; - } - return [x, y]; -} -exports.getCoordsRelativeToElement = getCoordsRelativeToElement; -function getCoords(event, rowContainer, charMeasure, colCount, rowCount, isSelection) { - if (!charMeasure.width || !charMeasure.height) { - return null; - } - var coords = getCoordsRelativeToElement(event, rowContainer); - if (!coords) { - return null; - } - coords[0] = Math.ceil((coords[0] + (isSelection ? charMeasure.width / 2 : 0)) / charMeasure.width); - coords[1] = Math.ceil(coords[1] / charMeasure.height); - coords[0] = Math.min(Math.max(coords[0], 1), colCount + 1); - coords[1] = Math.min(Math.max(coords[1], 1), rowCount + 1); - return coords; -} -exports.getCoords = getCoords; -function getRawByteCoords(event, rowContainer, charMeasure, colCount, rowCount) { - var coords = getCoords(event, rowContainer, charMeasure, colCount, rowCount); - var x = coords[0]; - var y = coords[1]; - x += 32; - y += 32; - return { x: x, y: y }; -} -exports.getRawByteCoords = getRawByteCoords; - - - -},{}],22:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -var BufferSet_1 = require("./BufferSet"); -var CompositionHelper_1 = require("./CompositionHelper"); -var EventEmitter_1 = require("./EventEmitter"); -var Viewport_1 = require("./Viewport"); -var Clipboard_1 = require("./handlers/Clipboard"); -var EscapeSequences_1 = require("./EscapeSequences"); -var InputHandler_1 = require("./InputHandler"); -var Parser_1 = require("./Parser"); -var Renderer_1 = require("./Renderer"); -var Linkifier_1 = require("./Linkifier"); -var SelectionManager_1 = require("./SelectionManager"); -var CharMeasure_1 = require("./utils/CharMeasure"); -var Browser = require("./utils/Browser"); -var Mouse_1 = require("./utils/Mouse"); -var BufferLine_1 = require("./utils/BufferLine"); -var document = (typeof window != 'undefined') ? window.document : null; -var WRITE_BUFFER_PAUSE_THRESHOLD = 5; -var WRITE_BATCH_SIZE = 300; -var CURSOR_BLINK_INTERVAL = 600; -function Terminal(options) { - var self = this; - if (!(this instanceof Terminal)) { - return new Terminal(arguments[0], arguments[1], arguments[2]); - } - self.browser = Browser; - self.cancel = Terminal.cancel; - EventEmitter_1.EventEmitter.call(this); - if (typeof options === 'number') { - options = { - cols: arguments[0], - rows: arguments[1], - handler: arguments[2] - }; - } - options = options || {}; - Object.keys(Terminal.defaults).forEach(function (key) { - if (options[key] == null) { - options[key] = Terminal.options[key]; - if (Terminal[key] !== Terminal.defaults[key]) { - options[key] = Terminal[key]; - } - } - self[key] = options[key]; - }); - if (options.colors.length === 8) { - options.colors = options.colors.concat(Terminal._colors.slice(8)); - } - else if (options.colors.length === 16) { - options.colors = options.colors.concat(Terminal._colors.slice(16)); - } - else if (options.colors.length === 10) { - options.colors = options.colors.slice(0, -2).concat(Terminal._colors.slice(8, -2), options.colors.slice(-2)); - } - else if (options.colors.length === 18) { - options.colors = options.colors.concat(Terminal._colors.slice(16, -2), options.colors.slice(-2)); - } - this.colors = options.colors; - this.options = options; - this.parent = options.body || options.parent || (document ? document.getElementsByTagName('body')[0] : null); - this.cols = options.cols || options.geometry[0]; - this.rows = options.rows || options.geometry[1]; - this.geometry = [this.cols, this.rows]; - if (options.handler) { - this.on('data', options.handler); - } - this.cursorState = 0; - this.cursorHidden = false; - this.convertEol; - this.queue = ''; - this.customKeyEventHandler = null; - this.cursorBlinkInterval = null; - this.applicationKeypad = false; - this.applicationCursor = false; - this.originMode = false; - this.insertMode = false; - this.wraparoundMode = true; - this.charset = null; - this.gcharset = null; - this.glevel = 0; - this.charsets = [null]; - this.decLocator; - this.x10Mouse; - this.vt200Mouse; - this.vt300Mouse; - this.normalMouse; - this.mouseEvents; - this.sendFocus; - this.utfMouse; - this.sgrMouse; - this.urxvtMouse; - this.element; - this.children; - this.refreshStart; - this.refreshEnd; - this.savedX; - this.savedY; - this.savedCols; - this.readable = true; - this.writable = true; - this.defAttr = (0 << 18) | (257 << 9) | (256 << 0); - this.curAttr = this.defAttr; - this.params = []; - this.currentParam = 0; - this.prefix = ''; - this.postfix = ''; - this.inputHandler = new InputHandler_1.InputHandler(this); - this.parser = new Parser_1.Parser(this.inputHandler, this); - this.renderer = this.renderer || null; - this.selectionManager = this.selectionManager || null; - this.linkifier = this.linkifier || new Linkifier_1.Linkifier(); - this.writeBuffer = []; - this.writeInProgress = false; - this.xoffSentToCatchUp = false; - this.writeStopped = false; - this.surrogate_high = ''; - this.buffers = new BufferSet_1.BufferSet(this); - this.buffer = this.buffers.active; - this.buffers.on('activate', function (buffer) { - this._terminal.buffer = buffer; - }); - if (this.selectionManager) { - this.selectionManager.setBuffer(this.buffer.lines); - } - this.setupStops(); - this.userScrolling = false; -} -inherits(Terminal, EventEmitter_1.EventEmitter); -Terminal.prototype.eraseAttr = function () { - return (this.defAttr & ~0x1ff) | (this.curAttr & 0x1ff); -}; -Terminal.tangoColors = [ - '#2e3436', - '#cc0000', - '#4e9a06', - '#c4a000', - '#3465a4', - '#75507b', - '#06989a', - '#d3d7cf', - '#555753', - '#ef2929', - '#8ae234', - '#fce94f', - '#729fcf', - '#ad7fa8', - '#34e2e2', - '#eeeeec' -]; -Terminal.colors = (function () { - var colors = Terminal.tangoColors.slice(), r = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff], i; - i = 0; - for (; i < 216; i++) { - out(r[(i / 36) % 6 | 0], r[(i / 6) % 6 | 0], r[i % 6]); - } - i = 0; - for (; i < 24; i++) { - r = 8 + i * 10; - out(r, r, r); - } - function out(r, g, b) { - colors.push('#' + hex(r) + hex(g) + hex(b)); - } - function hex(c) { - c = c.toString(16); - return c.length < 2 ? '0' + c : c; - } - return colors; -})(); -Terminal._colors = Terminal.colors.slice(); -Terminal.vcolors = (function () { - var out = [], colors = Terminal.colors, i = 0, color; - for (; i < 256; i++) { - color = parseInt(colors[i].substring(1), 16); - out.push([ - (color >> 16) & 0xff, - (color >> 8) & 0xff, - color & 0xff - ]); - } - return out; -})(); -Terminal.defaults = { - colors: Terminal.colors, - theme: 'default', - convertEol: false, - termName: 'xterm', - geometry: [80, 24], - cursorBlink: false, - cursorStyle: 'block', - visualBell: false, - popOnBell: false, - scrollback: 1000, - screenKeys: false, - debug: false, - cancelEvents: false, - disableStdin: false, - useFlowControl: false, - tabStopWidth: 8 -}; -Terminal.options = {}; -Terminal.focus = null; -each(keys(Terminal.defaults), function (key) { - Terminal[key] = Terminal.defaults[key]; - Terminal.options[key] = Terminal.defaults[key]; -}); -Terminal.prototype.focus = function () { - return this.textarea.focus(); -}; -Terminal.prototype.getOption = function (key) { - if (!(key in Terminal.defaults)) { - throw new Error('No option with key "' + key + '"'); - } - if (typeof this.options[key] !== 'undefined') { - return this.options[key]; - } - return this[key]; -}; -Terminal.prototype.setOption = function (key, value) { - if (!(key in Terminal.defaults)) { - throw new Error('No option with key "' + key + '"'); - } - switch (key) { - case 'scrollback': - if (value < this.rows) { - var msg = 'Setting the scrollback value less than the number of rows '; - msg += "(" + this.rows + ") is not allowed."; - console.warn(msg); - return false; - } - if (this.options[key] !== value) { - if (this.buffer.lines.length > value) { - var amountToTrim = this.buffer.lines.length - value; - var needsRefresh = (this.buffer.ydisp - amountToTrim < 0); - this.buffer.lines.trimStart(amountToTrim); - this.buffer.ybase = Math.max(this.buffer.ybase - amountToTrim, 0); - this.buffer.ydisp = Math.max(this.buffer.ydisp - amountToTrim, 0); - if (needsRefresh) { - this.refresh(0, this.rows - 1); - } - } - this.buffer.lines.maxLength = value; - this.viewport.syncScrollArea(); - } - break; - } - this[key] = value; - this.options[key] = value; - switch (key) { - case 'cursorBlink': - this.setCursorBlinking(value); - break; - case 'cursorStyle': - this.element.classList.toggle("xterm-cursor-style-block", value === 'block'); - this.element.classList.toggle("xterm-cursor-style-underline", value === 'underline'); - this.element.classList.toggle("xterm-cursor-style-bar", value === 'bar'); - break; - case 'tabStopWidth': - this.setupStops(); - break; - } -}; -Terminal.prototype.restartCursorBlinking = function () { - this.setCursorBlinking(this.options.cursorBlink); -}; -Terminal.prototype.setCursorBlinking = function (enabled) { - this.element.classList.toggle('xterm-cursor-blink', enabled); - this.clearCursorBlinkingInterval(); - if (enabled) { - var self = this; - this.cursorBlinkInterval = setInterval(function () { - self.element.classList.toggle('xterm-cursor-blink-on'); - }, CURSOR_BLINK_INTERVAL); - } -}; -Terminal.prototype.clearCursorBlinkingInterval = function () { - this.element.classList.remove('xterm-cursor-blink-on'); - if (this.cursorBlinkInterval) { - clearInterval(this.cursorBlinkInterval); - this.cursorBlinkInterval = null; - } -}; -Terminal.bindFocus = function (term) { - on(term.textarea, 'focus', function (ev) { - if (term.sendFocus) { - term.send(EscapeSequences_1.C0.ESC + '[I'); - } - term.element.classList.add('focus'); - term.showCursor(); - term.restartCursorBlinking.apply(term); - Terminal.focus = term; - term.emit('focus', { terminal: term }); - }); -}; -Terminal.prototype.blur = function () { - return this.textarea.blur(); -}; -Terminal.bindBlur = function (term) { - on(term.textarea, 'blur', function (ev) { - term.refresh(term.buffer.y, term.buffer.y); - if (term.sendFocus) { - term.send(EscapeSequences_1.C0.ESC + '[O'); - } - term.element.classList.remove('focus'); - term.clearCursorBlinkingInterval.apply(term); - Terminal.focus = null; - term.emit('blur', { terminal: term }); - }); -}; -Terminal.prototype.initGlobal = function () { - var _this = this; - var term = this; - Terminal.bindKeys(this); - Terminal.bindFocus(this); - Terminal.bindBlur(this); - on(this.element, 'copy', function (event) { - if (!term.hasSelection()) { - return; - } - Clipboard_1.copyHandler(event, term, _this.selectionManager); - }); - var pasteHandlerWrapper = function (event) { return Clipboard_1.pasteHandler(event, term); }; - on(this.textarea, 'paste', pasteHandlerWrapper); - on(this.element, 'paste', pasteHandlerWrapper); - if (term.browser.isFirefox) { - on(this.element, 'mousedown', function (event) { - if (event.button == 2) { - Clipboard_1.rightClickHandler(event, _this.textarea, _this.selectionManager); - } - }); - } - else { - on(this.element, 'contextmenu', function (event) { - Clipboard_1.rightClickHandler(event, _this.textarea, _this.selectionManager); - }); - } - if (term.browser.isLinux) { - on(this.element, 'auxclick', function (event) { - if (event.button === 1) { - Clipboard_1.moveTextAreaUnderMouseCursor(event, _this.textarea, _this.selectionManager); - } - }); - } -}; -Terminal.bindKeys = function (term) { - on(term.element, 'keydown', function (ev) { - if (document.activeElement != this) { - return; - } - term.keyDown(ev); - }, true); - on(term.element, 'keypress', function (ev) { - if (document.activeElement != this) { - return; - } - term.keyPress(ev); - }, true); - on(term.element, 'keyup', function (ev) { - if (!wasMondifierKeyOnlyEvent(ev)) { - term.focus(term); - } - }, true); - on(term.textarea, 'keydown', function (ev) { - term.keyDown(ev); - }, true); - on(term.textarea, 'keypress', function (ev) { - term.keyPress(ev); - this.value = ''; - }, true); - on(term.textarea, 'compositionstart', term.compositionHelper.compositionstart.bind(term.compositionHelper)); - on(term.textarea, 'compositionupdate', term.compositionHelper.compositionupdate.bind(term.compositionHelper)); - on(term.textarea, 'compositionend', term.compositionHelper.compositionend.bind(term.compositionHelper)); - term.on('refresh', term.compositionHelper.updateCompositionElements.bind(term.compositionHelper)); - term.on('refresh', function (data) { - term.queueLinkification(data.start, data.end); - }); -}; -Terminal.prototype.insertRow = function (row) { - if (typeof row != 'object') { - row = document.createElement('div'); - } - this.rowContainer.appendChild(row); - this.children.push(row); - return row; -}; -Terminal.prototype.open = function (parent, focus) { - var _this = this; - var self = this, i = 0, div; - this.parent = parent || this.parent; - if (!this.parent) { - throw new Error('Terminal requires a parent element.'); - } - this.context = this.parent.ownerDocument.defaultView; - this.document = this.parent.ownerDocument; - this.body = this.document.getElementsByTagName('body')[0]; - this.element = this.document.createElement('div'); - this.element.classList.add('terminal'); - this.element.classList.add('xterm'); - this.element.classList.add('xterm-theme-' + this.theme); - this.element.classList.add("xterm-cursor-style-" + this.options.cursorStyle); - this.setCursorBlinking(this.options.cursorBlink); - this.element.setAttribute('tabindex', 0); - this.viewportElement = document.createElement('div'); - this.viewportElement.classList.add('xterm-viewport'); - this.element.appendChild(this.viewportElement); - this.viewportScrollArea = document.createElement('div'); - this.viewportScrollArea.classList.add('xterm-scroll-area'); - this.viewportElement.appendChild(this.viewportScrollArea); - this.selectionContainer = document.createElement('div'); - this.selectionContainer.classList.add('xterm-selection'); - this.element.appendChild(this.selectionContainer); - this.rowContainer = document.createElement('div'); - this.rowContainer.classList.add('xterm-rows'); - this.element.appendChild(this.rowContainer); - this.children = []; - this.linkifier.attachToDom(document, this.children); - this.helperContainer = document.createElement('div'); - this.helperContainer.classList.add('xterm-helpers'); - this.element.appendChild(this.helperContainer); - this.textarea = document.createElement('textarea'); - this.textarea.classList.add('xterm-helper-textarea'); - this.textarea.setAttribute('autocorrect', 'off'); - this.textarea.setAttribute('autocapitalize', 'off'); - this.textarea.setAttribute('spellcheck', 'false'); - this.textarea.tabIndex = 0; - this.textarea.addEventListener('focus', function () { - self.emit('focus', { terminal: self }); - }); - this.textarea.addEventListener('blur', function () { - self.emit('blur', { terminal: self }); - }); - this.helperContainer.appendChild(this.textarea); - this.compositionView = document.createElement('div'); - this.compositionView.classList.add('composition-view'); - this.compositionHelper = new CompositionHelper_1.CompositionHelper(this.textarea, this.compositionView, this); - this.helperContainer.appendChild(this.compositionView); - this.charSizeStyleElement = document.createElement('style'); - this.helperContainer.appendChild(this.charSizeStyleElement); - for (; i < this.rows; i++) { - this.insertRow(); - } - this.parent.appendChild(this.element); - this.charMeasure = new CharMeasure_1.CharMeasure(document, this.helperContainer); - this.charMeasure.on('charsizechanged', function () { - self.updateCharSizeStyles(); - }); - this.charMeasure.measure(); - this.viewport = new Viewport_1.Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasure); - this.renderer = new Renderer_1.Renderer(this); - this.selectionManager = new SelectionManager_1.SelectionManager(this, this.buffer.lines, this.rowContainer, this.charMeasure); - this.selectionManager.on('refresh', function (data) { - _this.renderer.refreshSelection(data.start, data.end); - }); - this.selectionManager.on('newselection', function (text) { - _this.textarea.value = text; - _this.textarea.focus(); - _this.textarea.select(); - }); - this.on('scroll', function () { return _this.selectionManager.refresh(); }); - this.viewportElement.addEventListener('scroll', function () { return _this.selectionManager.refresh(); }); - this.refresh(0, this.rows - 1); - this.initGlobal(); - if (typeof focus == 'undefined') { - var message = 'You did not pass the `focus` argument in `Terminal.prototype.open()`.\n'; - message += 'The `focus` argument now defaults to `true` but starting with xterm.js 3.0 '; - message += 'it will default to `false`.'; - console.warn(message); - focus = true; - } - if (focus) { - this.focus(); - } - this.bindMouse(); - this.emit('open'); -}; -Terminal.loadAddon = function (addon, callback) { - if (typeof exports === 'object' && typeof module === 'object') { - return require('./addons/' + addon + '/' + addon); - } - else if (typeof define == 'function') { - return require(['./addons/' + addon + '/' + addon], callback); - } - else { - console.error('Cannot load a module without a CommonJS or RequireJS environment.'); - return false; - } -}; -Terminal.prototype.updateCharSizeStyles = function () { - this.charSizeStyleElement.textContent = - ".xterm-wide-char{width:" + this.charMeasure.width * 2 + "px;}" + - (".xterm-normal-char{width:" + this.charMeasure.width + "px;}") + - (".xterm-rows > div{height:" + this.charMeasure.height + "px;}"); -}; -Terminal.prototype.bindMouse = function () { - var el = this.element, self = this, pressed = 32; - function sendButton(ev) { - var button, pos; - button = getButton(ev); - pos = Mouse_1.getRawByteCoords(ev, self.rowContainer, self.charMeasure, self.cols, self.rows); - if (!pos) - return; - sendEvent(button, pos); - switch (ev.overrideType || ev.type) { - case 'mousedown': - pressed = button; - break; - case 'mouseup': - pressed = 32; - break; - case 'wheel': - break; - } - } - function sendMove(ev) { - var button = pressed, pos; - pos = Mouse_1.getRawByteCoords(ev, self.rowContainer, self.charMeasure, self.cols, self.rows); - if (!pos) - return; - button += 32; - sendEvent(button, pos); - } - function encode(data, ch) { - if (!self.utfMouse) { - if (ch === 255) - return data.push(0); - if (ch > 127) - ch = 127; - data.push(ch); - } - else { - if (ch === 2047) - return data.push(0); - if (ch < 127) { - data.push(ch); - } - else { - if (ch > 2047) - ch = 2047; - data.push(0xC0 | (ch >> 6)); - data.push(0x80 | (ch & 0x3F)); - } - } - } - function sendEvent(button, pos) { - if (self.vt300Mouse) { - button &= 3; - pos.x -= 32; - pos.y -= 32; - var data = EscapeSequences_1.C0.ESC + '[24'; - if (button === 0) - data += '1'; - else if (button === 1) - data += '3'; - else if (button === 2) - data += '5'; - else if (button === 3) - return; - else - data += '0'; - data += '~[' + pos.x + ',' + pos.y + ']\r'; - self.send(data); - return; - } - if (self.decLocator) { - button &= 3; - pos.x -= 32; - pos.y -= 32; - if (button === 0) - button = 2; - else if (button === 1) - button = 4; - else if (button === 2) - button = 6; - else if (button === 3) - button = 3; - self.send(EscapeSequences_1.C0.ESC + '[' - + button - + ';' - + (button === 3 ? 4 : 0) - + ';' - + pos.y - + ';' - + pos.x - + ';' - + (pos.page || 0) - + '&w'); - return; - } - if (self.urxvtMouse) { - pos.x -= 32; - pos.y -= 32; - pos.x++; - pos.y++; - self.send(EscapeSequences_1.C0.ESC + '[' + button + ';' + pos.x + ';' + pos.y + 'M'); - return; - } - if (self.sgrMouse) { - pos.x -= 32; - pos.y -= 32; - self.send(EscapeSequences_1.C0.ESC + '[<' - + (((button & 3) === 3 ? button & ~3 : button) - 32) - + ';' - + pos.x - + ';' - + pos.y - + ((button & 3) === 3 ? 'm' : 'M')); - return; - } - var data = []; - encode(data, button); - encode(data, pos.x); - encode(data, pos.y); - self.send(EscapeSequences_1.C0.ESC + '[M' + String.fromCharCode.apply(String, data)); - } - function getButton(ev) { - var button, shift, meta, ctrl, mod; - switch (ev.overrideType || ev.type) { - case 'mousedown': - button = ev.button != null - ? +ev.button - : ev.which != null - ? ev.which - 1 - : null; - if (self.browser.isMSIE) { - button = button === 1 ? 0 : button === 4 ? 1 : button; - } - break; - case 'mouseup': - button = 3; - break; - case 'DOMMouseScroll': - button = ev.detail < 0 - ? 64 - : 65; - break; - case 'wheel': - button = ev.wheelDeltaY > 0 - ? 64 - : 65; - break; - } - shift = ev.shiftKey ? 4 : 0; - meta = ev.metaKey ? 8 : 0; - ctrl = ev.ctrlKey ? 16 : 0; - mod = shift | meta | ctrl; - if (self.vt200Mouse) { - mod &= ctrl; - } - else if (!self.normalMouse) { - mod = 0; - } - button = (32 + (mod << 2)) + button; - return button; - } - on(el, 'mousedown', function (ev) { - ev.preventDefault(); - self.focus(); - if (!self.mouseEvents) - return; - sendButton(ev); - if (self.vt200Mouse) { - ev.overrideType = 'mouseup'; - sendButton(ev); - return self.cancel(ev); - } - if (self.normalMouse) - on(self.document, 'mousemove', sendMove); - if (!self.x10Mouse) { - on(self.document, 'mouseup', function up(ev) { - sendButton(ev); - if (self.normalMouse) - off(self.document, 'mousemove', sendMove); - off(self.document, 'mouseup', up); - return self.cancel(ev); - }); - } - return self.cancel(ev); - }); - on(el, 'wheel', function (ev) { - if (!self.mouseEvents) - return; - if (self.x10Mouse - || self.vt300Mouse - || self.decLocator) - return; - sendButton(ev); - return self.cancel(ev); - }); - on(el, 'wheel', function (ev) { - if (self.mouseEvents) - return; - self.viewport.onWheel(ev); - return self.cancel(ev); - }); - on(el, 'touchstart', function (ev) { - if (self.mouseEvents) - return; - self.viewport.onTouchStart(ev); - return self.cancel(ev); - }); - on(el, 'touchmove', function (ev) { - if (self.mouseEvents) - return; - self.viewport.onTouchMove(ev); - return self.cancel(ev); - }); -}; -Terminal.prototype.destroy = function () { - this.readable = false; - this.writable = false; - this._events = {}; - this.handler = function () { }; - this.write = function () { }; - if (this.element && this.element.parentNode) { - this.element.parentNode.removeChild(this.element); - } -}; -Terminal.prototype.refresh = function (start, end) { - if (this.renderer) { - this.renderer.queueRefresh(start, end); - } -}; -Terminal.prototype.queueLinkification = function (start, end) { - if (this.linkifier) { - for (var i = start; i <= end; i++) { - this.linkifier.linkifyRow(i); - } - } -}; -Terminal.prototype.showCursor = function () { - if (!this.cursorState) { - this.cursorState = 1; - this.refresh(this.buffer.y, this.buffer.y); - } -}; -Terminal.prototype.scroll = function (isWrapped) { - var row; - if (this.buffer.lines.length === this.buffer.lines.maxLength) { - this.buffer.lines.trimStart(1); - this.buffer.ybase--; - if (this.buffer.ydisp !== 0) { - this.buffer.ydisp--; - } - } - this.buffer.ybase++; - if (!this.userScrolling) { - this.buffer.ydisp = this.buffer.ybase; - } - row = this.buffer.ybase + this.rows - 1; - row -= this.rows - 1 - this.buffer.scrollBottom; - if (row === this.buffer.lines.length) { - this.buffer.lines.push(this.blankLine(undefined, isWrapped)); - } - else { - this.buffer.lines.splice(row, 0, this.blankLine(undefined, isWrapped)); - } - if (this.buffer.scrollTop !== 0) { - if (this.buffer.ybase !== 0) { - this.buffer.ybase--; - if (!this.userScrolling) { - this.buffer.ydisp = this.buffer.ybase; - } - } - this.buffer.lines.splice(this.buffer.ybase + this.buffer.scrollTop, 1); - } - this.updateRange(this.buffer.scrollTop); - this.updateRange(this.buffer.scrollBottom); - this.emit('scroll', this.buffer.ydisp); -}; -Terminal.prototype.scrollDisp = function (disp, suppressScrollEvent) { - if (disp < 0) { - if (this.buffer.ydisp === 0) { - return; - } - this.userScrolling = true; - } - else if (disp + this.buffer.ydisp >= this.buffer.ybase) { - this.userScrolling = false; - } - var oldYdisp = this.buffer.ydisp; - this.buffer.ydisp = Math.max(Math.min(this.buffer.ydisp + disp, this.buffer.ybase), 0); - if (oldYdisp === this.buffer.ydisp) { - return; - } - if (!suppressScrollEvent) { - this.emit('scroll', this.buffer.ydisp); - } - this.refresh(0, this.rows - 1); -}; -Terminal.prototype.scrollPages = function (pageCount) { - this.scrollDisp(pageCount * (this.rows - 1)); -}; -Terminal.prototype.scrollToTop = function () { - this.scrollDisp(-this.buffer.ydisp); -}; -Terminal.prototype.scrollToBottom = function () { - this.scrollDisp(this.buffer.ybase - this.buffer.ydisp); -}; -Terminal.prototype.write = function (data) { - this.writeBuffer.push(data); - if (this.options.useFlowControl && !this.xoffSentToCatchUp && this.writeBuffer.length >= WRITE_BUFFER_PAUSE_THRESHOLD) { - this.send(EscapeSequences_1.C0.DC3); - this.xoffSentToCatchUp = true; - } - if (!this.writeInProgress && this.writeBuffer.length > 0) { - this.writeInProgress = true; - var self = this; - setTimeout(function () { - self.innerWrite(); - }); - } -}; -Terminal.prototype.innerWrite = function () { - var writeBatch = this.writeBuffer.splice(0, WRITE_BATCH_SIZE); - while (writeBatch.length > 0) { - var data = writeBatch.shift(); - var l = data.length, i = 0, j, cs, ch, code, low, ch_width, row; - if (this.xoffSentToCatchUp && writeBatch.length === 0 && this.writeBuffer.length === 0) { - this.send(EscapeSequences_1.C0.DC1); - this.xoffSentToCatchUp = false; - } - this.refreshStart = this.buffer.y; - this.refreshEnd = this.buffer.y; - var state = this.parser.parse(data); - this.parser.setState(state); - this.updateRange(this.buffer.y); - this.refresh(this.refreshStart, this.refreshEnd); - } - if (this.writeBuffer.length > 0) { - var self = this; - setTimeout(function () { - self.innerWrite(); - }, 0); - } - else { - this.writeInProgress = false; - } -}; -Terminal.prototype.writeln = function (data) { - this.write(data + '\r\n'); -}; -Terminal.prototype.attachCustomKeydownHandler = function (customKeydownHandler) { - var message = 'attachCustomKeydownHandler() is DEPRECATED and will be removed soon. Please use attachCustomKeyEventHandler() instead.'; - console.warn(message); - this.attachCustomKeyEventHandler(customKeydownHandler); -}; -Terminal.prototype.attachCustomKeyEventHandler = function (customKeyEventHandler) { - this.customKeyEventHandler = customKeyEventHandler; -}; -Terminal.prototype.setHypertextLinkHandler = function (handler) { - if (!this.linkifier) { - throw new Error('Cannot attach a hypertext link handler before Terminal.open is called'); - } - this.linkifier.setHypertextLinkHandler(handler); - this.refresh(0, this.rows - 1); -}; -Terminal.prototype.setHypertextValidationCallback = function (callback) { - if (!this.linkifier) { - throw new Error('Cannot attach a hypertext validation callback before Terminal.open is called'); - } - this.linkifier.setHypertextValidationCallback(callback); - this.refresh(0, this.rows - 1); -}; -Terminal.prototype.registerLinkMatcher = function (regex, handler, options) { - if (this.linkifier) { - var matcherId = this.linkifier.registerLinkMatcher(regex, handler, options); - this.refresh(0, this.rows - 1); - return matcherId; - } -}; -Terminal.prototype.deregisterLinkMatcher = function (matcherId) { - if (this.linkifier) { - if (this.linkifier.deregisterLinkMatcher(matcherId)) { - this.refresh(0, this.rows - 1); - } - } -}; -Terminal.prototype.hasSelection = function () { - return this.selectionManager ? this.selectionManager.hasSelection : false; -}; -Terminal.prototype.getSelection = function () { - return this.selectionManager ? this.selectionManager.selectionText : ''; -}; -Terminal.prototype.clearSelection = function () { - if (this.selectionManager) { - this.selectionManager.clearSelection(); - } -}; -Terminal.prototype.selectAll = function () { - if (this.selectionManager) { - this.selectionManager.selectAll(); - } -}; -Terminal.prototype.keyDown = function (ev) { - if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) { - return false; - } - this.restartCursorBlinking(); - if (!this.compositionHelper.keydown.bind(this.compositionHelper)(ev)) { - if (this.buffer.ybase !== this.buffer.ydisp) { - this.scrollToBottom(); - } - return false; - } - var self = this; - var result = this.evaluateKeyEscapeSequence(ev); - if (result.key === EscapeSequences_1.C0.DC3) { - this.writeStopped = true; - } - else if (result.key === EscapeSequences_1.C0.DC1) { - this.writeStopped = false; - } - if (result.scrollDisp) { - this.scrollDisp(result.scrollDisp); - return this.cancel(ev, true); - } - if (isThirdLevelShift(this, ev)) { - return true; - } - if (result.cancel) { - this.cancel(ev, true); - } - if (!result.key) { - return true; - } - this.emit('keydown', ev); - this.emit('key', result.key, ev); - this.showCursor(); - this.handler(result.key); - return this.cancel(ev, true); -}; -Terminal.prototype.evaluateKeyEscapeSequence = function (ev) { - var result = { - cancel: false, - key: undefined, - scrollDisp: undefined - }; - var modifiers = ev.shiftKey << 0 | ev.altKey << 1 | ev.ctrlKey << 2 | ev.metaKey << 3; - switch (ev.keyCode) { - case 8: - if (ev.shiftKey) { - result.key = EscapeSequences_1.C0.BS; - break; - } - result.key = EscapeSequences_1.C0.DEL; - break; - case 9: - if (ev.shiftKey) { - result.key = EscapeSequences_1.C0.ESC + '[Z'; - break; - } - result.key = EscapeSequences_1.C0.HT; - result.cancel = true; - break; - case 13: - result.key = EscapeSequences_1.C0.CR; - result.cancel = true; - break; - case 27: - result.key = EscapeSequences_1.C0.ESC; - result.cancel = true; - break; - case 37: - if (modifiers) { - result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'D'; - if (result.key == EscapeSequences_1.C0.ESC + '[1;3D') { - result.key = (this.browser.isMac) ? EscapeSequences_1.C0.ESC + 'b' : EscapeSequences_1.C0.ESC + '[1;5D'; - } - } - else if (this.applicationCursor) { - result.key = EscapeSequences_1.C0.ESC + 'OD'; - } - else { - result.key = EscapeSequences_1.C0.ESC + '[D'; - } - break; - case 39: - if (modifiers) { - result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'C'; - if (result.key == EscapeSequences_1.C0.ESC + '[1;3C') { - result.key = (this.browser.isMac) ? EscapeSequences_1.C0.ESC + 'f' : EscapeSequences_1.C0.ESC + '[1;5C'; - } - } - else if (this.applicationCursor) { - result.key = EscapeSequences_1.C0.ESC + 'OC'; - } - else { - result.key = EscapeSequences_1.C0.ESC + '[C'; - } - break; - case 38: - if (modifiers) { - result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'A'; - if (result.key == EscapeSequences_1.C0.ESC + '[1;3A') { - result.key = EscapeSequences_1.C0.ESC + '[1;5A'; - } - } - else if (this.applicationCursor) { - result.key = EscapeSequences_1.C0.ESC + 'OA'; - } - else { - result.key = EscapeSequences_1.C0.ESC + '[A'; - } - break; - case 40: - if (modifiers) { - result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'B'; - if (result.key == EscapeSequences_1.C0.ESC + '[1;3B') { - result.key = EscapeSequences_1.C0.ESC + '[1;5B'; - } - } - else if (this.applicationCursor) { - result.key = EscapeSequences_1.C0.ESC + 'OB'; - } - else { - result.key = EscapeSequences_1.C0.ESC + '[B'; - } - break; - case 45: - if (!ev.shiftKey && !ev.ctrlKey) { - result.key = EscapeSequences_1.C0.ESC + '[2~'; - } - break; - case 46: - if (modifiers) { - result.key = EscapeSequences_1.C0.ESC + '[3;' + (modifiers + 1) + '~'; - } - else { - result.key = EscapeSequences_1.C0.ESC + '[3~'; - } - break; - case 36: - if (modifiers) - result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'H'; - else if (this.applicationCursor) - result.key = EscapeSequences_1.C0.ESC + 'OH'; - else - result.key = EscapeSequences_1.C0.ESC + '[H'; - break; - case 35: - if (modifiers) - result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'F'; - else if (this.applicationCursor) - result.key = EscapeSequences_1.C0.ESC + 'OF'; - else - result.key = EscapeSequences_1.C0.ESC + '[F'; - break; - case 33: - if (ev.shiftKey) { - result.scrollDisp = -(this.rows - 1); - } - else { - result.key = EscapeSequences_1.C0.ESC + '[5~'; - } - break; - case 34: - if (ev.shiftKey) { - result.scrollDisp = this.rows - 1; - } - else { - result.key = EscapeSequences_1.C0.ESC + '[6~'; - } - break; - case 112: - if (modifiers) { - result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'P'; - } - else { - result.key = EscapeSequences_1.C0.ESC + 'OP'; - } - break; - case 113: - if (modifiers) { - result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'Q'; - } - else { - result.key = EscapeSequences_1.C0.ESC + 'OQ'; - } - break; - case 114: - if (modifiers) { - result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'R'; - } - else { - result.key = EscapeSequences_1.C0.ESC + 'OR'; - } - break; - case 115: - if (modifiers) { - result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'S'; - } - else { - result.key = EscapeSequences_1.C0.ESC + 'OS'; - } - break; - case 116: - if (modifiers) { - result.key = EscapeSequences_1.C0.ESC + '[15;' + (modifiers + 1) + '~'; - } - else { - result.key = EscapeSequences_1.C0.ESC + '[15~'; - } - break; - case 117: - if (modifiers) { - result.key = EscapeSequences_1.C0.ESC + '[17;' + (modifiers + 1) + '~'; - } - else { - result.key = EscapeSequences_1.C0.ESC + '[17~'; - } - break; - case 118: - if (modifiers) { - result.key = EscapeSequences_1.C0.ESC + '[18;' + (modifiers + 1) + '~'; - } - else { - result.key = EscapeSequences_1.C0.ESC + '[18~'; - } - break; - case 119: - if (modifiers) { - result.key = EscapeSequences_1.C0.ESC + '[19;' + (modifiers + 1) + '~'; - } - else { - result.key = EscapeSequences_1.C0.ESC + '[19~'; - } - break; - case 120: - if (modifiers) { - result.key = EscapeSequences_1.C0.ESC + '[20;' + (modifiers + 1) + '~'; - } - else { - result.key = EscapeSequences_1.C0.ESC + '[20~'; - } - break; - case 121: - if (modifiers) { - result.key = EscapeSequences_1.C0.ESC + '[21;' + (modifiers + 1) + '~'; - } - else { - result.key = EscapeSequences_1.C0.ESC + '[21~'; - } - break; - case 122: - if (modifiers) { - result.key = EscapeSequences_1.C0.ESC + '[23;' + (modifiers + 1) + '~'; - } - else { - result.key = EscapeSequences_1.C0.ESC + '[23~'; - } - break; - case 123: - if (modifiers) { - result.key = EscapeSequences_1.C0.ESC + '[24;' + (modifiers + 1) + '~'; - } - else { - result.key = EscapeSequences_1.C0.ESC + '[24~'; - } - break; - default: - if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) { - if (ev.keyCode >= 65 && ev.keyCode <= 90) { - result.key = String.fromCharCode(ev.keyCode - 64); - } - else if (ev.keyCode === 32) { - result.key = String.fromCharCode(0); - } - else if (ev.keyCode >= 51 && ev.keyCode <= 55) { - result.key = String.fromCharCode(ev.keyCode - 51 + 27); - } - else if (ev.keyCode === 56) { - result.key = String.fromCharCode(127); - } - else if (ev.keyCode === 219) { - result.key = String.fromCharCode(27); - } - else if (ev.keyCode === 220) { - result.key = String.fromCharCode(28); - } - else if (ev.keyCode === 221) { - result.key = String.fromCharCode(29); - } - } - else if (!this.browser.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) { - if (ev.keyCode >= 65 && ev.keyCode <= 90) { - result.key = EscapeSequences_1.C0.ESC + String.fromCharCode(ev.keyCode + 32); - } - else if (ev.keyCode === 192) { - result.key = EscapeSequences_1.C0.ESC + '`'; - } - else if (ev.keyCode >= 48 && ev.keyCode <= 57) { - result.key = EscapeSequences_1.C0.ESC + (ev.keyCode - 48); - } - } - else if (this.browser.isMac && !ev.altKey && !ev.ctrlKey && ev.metaKey) { - if (ev.keyCode === 65) { - this.selectAll(); - } - } - break; - } - return result; -}; -Terminal.prototype.setgLevel = function (g) { - this.glevel = g; - this.charset = this.charsets[g]; -}; -Terminal.prototype.setgCharset = function (g, charset) { - this.charsets[g] = charset; - if (this.glevel === g) { - this.charset = charset; - } -}; -Terminal.prototype.keyPress = function (ev) { - var key; - if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) { - return false; - } - this.cancel(ev); - if (ev.charCode) { - key = ev.charCode; - } - else if (ev.which == null) { - key = ev.keyCode; - } - else if (ev.which !== 0 && ev.charCode !== 0) { - key = ev.which; - } - else { - return false; - } - if (!key || ((ev.altKey || ev.ctrlKey || ev.metaKey) && !isThirdLevelShift(this, ev))) { - return false; - } - key = String.fromCharCode(key); - this.emit('keypress', key, ev); - this.emit('key', key, ev); - this.showCursor(); - this.handler(key); - return true; -}; -Terminal.prototype.send = function (data) { - var self = this; - if (!this.queue) { - setTimeout(function () { - self.handler(self.queue); - self.queue = ''; - }, 1); - } - this.queue += data; -}; -Terminal.prototype.bell = function () { - if (!this.visualBell) - return; - var self = this; - this.element.style.borderColor = 'white'; - setTimeout(function () { - self.element.style.borderColor = ''; - }, 10); - if (this.popOnBell) - this.focus(); -}; -Terminal.prototype.log = function () { - if (!this.debug) - return; - if (!this.context.console || !this.context.console.log) - return; - var args = Array.prototype.slice.call(arguments); - this.context.console.log.apply(this.context.console, args); -}; -Terminal.prototype.error = function () { - if (!this.debug) - return; - if (!this.context.console || !this.context.console.error) - return; - var args = Array.prototype.slice.call(arguments); - this.context.console.error.apply(this.context.console, args); -}; -Terminal.prototype.resize = function (x, y) { - if (isNaN(x) || isNaN(y)) { - return; - } - if (y > this.getOption('scrollback')) { - this.setOption('scrollback', y); - } - var line, el, i, j, ch, addToY; - if (x === this.cols && y === this.rows) { - if (!this.charMeasure.width || !this.charMeasure.height) { - this.charMeasure.measure(); - } - return; - } - if (x < 1) - x = 1; - if (y < 1) - y = 1; - this.buffers.resize(x, y); - while (this.children.length < y) { - this.insertRow(); - } - while (this.children.length > y) { - el = this.children.shift(); - if (!el) - continue; - el.parentNode.removeChild(el); - } - this.cols = x; - this.rows = y; - this.setupStops(this.cols); - this.charMeasure.measure(); - this.refresh(0, this.rows - 1); - this.geometry = [this.cols, this.rows]; - this.emit('resize', { terminal: this, cols: x, rows: y }); -}; -Terminal.prototype.updateRange = function (y) { - if (y < this.refreshStart) - this.refreshStart = y; - if (y > this.refreshEnd) - this.refreshEnd = y; -}; -Terminal.prototype.maxRange = function () { - this.refreshStart = 0; - this.refreshEnd = this.rows - 1; -}; -Terminal.prototype.setupStops = function (i) { - if (i != null) { - if (!this.buffer.tabs[i]) { - i = this.prevStop(i); - } - } - else { - this.buffer.tabs = {}; - i = 0; - } - for (; i < this.cols; i += this.getOption('tabStopWidth')) { - this.buffer.tabs[i] = true; - } -}; -Terminal.prototype.prevStop = function (x) { - if (x == null) - x = this.buffer.x; - while (!this.buffer.tabs[--x] && x > 0) - ; - return x >= this.cols - ? this.cols - 1 - : x < 0 ? 0 : x; -}; -Terminal.prototype.nextStop = function (x) { - if (x == null) - x = this.buffer.x; - while (!this.buffer.tabs[++x] && x < this.cols) - ; - return x >= this.cols - ? this.cols - 1 - : x < 0 ? 0 : x; -}; -Terminal.prototype.eraseRight = function (x, y) { - var line = this.buffer.lines.get(this.buffer.ybase + y); - if (!line) { - return; - } - var ch = [this.eraseAttr(), ' ', 1]; - for (; x < this.cols; x++) { - line[x] = ch; - } - this.updateRange(y); -}; -Terminal.prototype.eraseLeft = function (x, y) { - var line = this.buffer.lines.get(this.buffer.ybase + y); - if (!line) { - return; - } - var ch = [this.eraseAttr(), ' ', 1]; - x++; - while (x--) { - line[x] = ch; - } - this.updateRange(y); -}; -Terminal.prototype.clear = function () { - if (this.buffer.ybase === 0 && this.buffer.y === 0) { - return; - } - this.buffer.lines.set(0, this.buffer.lines.get(this.buffer.ybase + this.buffer.y)); - this.buffer.lines.length = 1; - this.buffer.ydisp = 0; - this.buffer.ybase = 0; - this.buffer.y = 0; - for (var i = 1; i < this.rows; i++) { - this.buffer.lines.push(this.blankLine()); - } - this.refresh(0, this.rows - 1); - this.emit('scroll', this.buffer.ydisp); -}; -Terminal.prototype.eraseLine = function (y) { - this.eraseRight(0, y); -}; -Terminal.prototype.blankLine = function (cur, isWrapped) { - var attr = cur - ? this.eraseAttr() - : this.defAttr; - var ch = [attr, ' ', 1], line = [], i = 0; - if (isWrapped) { - line.isWrapped = isWrapped; - } - for (; i < this.cols; i++) { - line[i] = ch; - } - return line; -}; -Terminal.prototype.ch = function (cur) { - return cur - ? [this.eraseAttr(), ' ', 1] - : [this.defAttr, ' ', 1]; -}; -Terminal.prototype.is = function (term) { - var name = this.termName; - return (name + '').indexOf(term) === 0; -}; -Terminal.prototype.handler = function (data) { - if (this.options.disableStdin) { - return; - } - if (this.selectionManager && this.selectionManager.hasSelection) { - this.selectionManager.clearSelection(); - } - if (this.buffer.ybase !== this.buffer.ydisp) { - this.scrollToBottom(); - } - this.emit('data', data); -}; -Terminal.prototype.handleTitle = function (title) { - this.emit('title', title); -}; -Terminal.prototype.index = function () { - this.buffer.y++; - if (this.buffer.y > this.buffer.scrollBottom) { - this.buffer.y--; - this.scroll(); - } - if (this.buffer.x >= this.cols) { - this.buffer.x--; - } -}; -Terminal.prototype.reverseIndex = function () { - var j; - if (this.buffer.y === this.buffer.scrollTop) { - this.buffer.lines.shiftElements(this.buffer.y + this.buffer.ybase, this.rows - 1, 1); - this.buffer.lines.set(this.buffer.y + this.buffer.ybase, this.blankLine(true)); - this.updateRange(this.buffer.scrollTop); - this.updateRange(this.buffer.scrollBottom); - } - else { - this.buffer.y--; - } -}; -Terminal.prototype.reset = function () { - this.options.rows = this.rows; - this.options.cols = this.cols; - var customKeyEventHandler = this.customKeyEventHandler; - var cursorBlinkInterval = this.cursorBlinkInterval; - var inputHandler = this.inputHandler; - Terminal.call(this, this.options); - this.customKeyEventHandler = customKeyEventHandler; - this.cursorBlinkInterval = cursorBlinkInterval; - this.inputHandler = inputHandler; - this.refresh(0, this.rows - 1); - this.viewport.syncScrollArea(); -}; -Terminal.prototype.tabSet = function () { - this.buffer.tabs[this.buffer.x] = true; -}; -function on(el, type, handler, capture) { - if (!Array.isArray(el)) { - el = [el]; - } - el.forEach(function (element) { - element.addEventListener(type, handler, capture || false); - }); -} -function off(el, type, handler, capture) { - el.removeEventListener(type, handler, capture || false); -} -function cancel(ev, force) { - if (!this.cancelEvents && !force) { - return; - } - ev.preventDefault(); - ev.stopPropagation(); - return false; -} -function inherits(child, parent) { - function f() { - this.constructor = child; - } - f.prototype = parent.prototype; - child.prototype = new f; -} -function indexOf(obj, el) { - var i = obj.length; - while (i--) { - if (obj[i] === el) - return i; - } - return -1; -} -function isThirdLevelShift(term, ev) { - var thirdLevelKey = (term.browser.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) || - (term.browser.isMSWindows && ev.altKey && ev.ctrlKey && !ev.metaKey); - if (ev.type == 'keypress') { - return thirdLevelKey; - } - return thirdLevelKey && (!ev.keyCode || ev.keyCode > 47); -} -Terminal.prototype.matchColor = matchColor; -function matchColor(r1, g1, b1) { - var hash = (r1 << 16) | (g1 << 8) | b1; - if (matchColor._cache[hash] != null) { - return matchColor._cache[hash]; - } - var ldiff = Infinity, li = -1, i = 0, c, r2, g2, b2, diff; - for (; i < Terminal.vcolors.length; i++) { - c = Terminal.vcolors[i]; - r2 = c[0]; - g2 = c[1]; - b2 = c[2]; - diff = matchColor.distance(r1, g1, b1, r2, g2, b2); - if (diff === 0) { - li = i; - break; - } - if (diff < ldiff) { - ldiff = diff; - li = i; - } - } - return matchColor._cache[hash] = li; -} -matchColor._cache = {}; -matchColor.distance = function (r1, g1, b1, r2, g2, b2) { - return Math.pow(30 * (r1 - r2), 2) - + Math.pow(59 * (g1 - g2), 2) - + Math.pow(11 * (b1 - b2), 2); -}; -function each(obj, iter, con) { - if (obj.forEach) - return obj.forEach(iter, con); - for (var i = 0; i < obj.length; i++) { - iter.call(con, obj[i], i, obj); - } -} -function wasMondifierKeyOnlyEvent(ev) { - return ev.keyCode === 16 || - ev.keyCode === 17 || - ev.keyCode === 18; -} -function keys(obj) { - if (Object.keys) - return Object.keys(obj); - var key, keys = []; - for (key in obj) { - if (Object.prototype.hasOwnProperty.call(obj, key)) { - keys.push(key); - } - } - return keys; -} -Terminal.translateBufferLineToString = BufferLine_1.translateBufferLineToString; -Terminal.EventEmitter = EventEmitter_1.EventEmitter; -Terminal.inherits = inherits; -Terminal.on = on; -Terminal.off = off; -Terminal.cancel = cancel; -module.exports = Terminal; - - - -},{"./BufferSet":2,"./CompositionHelper":4,"./EscapeSequences":5,"./EventEmitter":6,"./InputHandler":7,"./Linkifier":8,"./Parser":9,"./Renderer":10,"./SelectionManager":11,"./Viewport":13,"./handlers/Clipboard":14,"./utils/Browser":15,"./utils/BufferLine":16,"./utils/CharMeasure":17,"./utils/Mouse":21}]},{},[22])(22) -}); -//# sourceMappingURL=xterm.js.map diff --git a/dist/xterm.js.map b/dist/xterm.js.map deleted file mode 100644 index 64e1b0e6..00000000 --- a/dist/xterm.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"xterm.js","sources":["../src/xterm.js","../src/utils/Mouse.ts","../src/utils/Generic.ts","../src/utils/DomElementObjectPool.ts","../src/utils/CircularList.ts","../src/utils/CharMeasure.ts","../src/utils/BufferLine.ts","../src/utils/Browser.ts","../src/handlers/Clipboard.ts","../src/Viewport.ts","../src/SelectionModel.ts","../src/SelectionManager.ts","../src/Renderer.ts","../src/Parser.ts","../src/Linkifier.ts","../src/InputHandler.ts","../src/EventEmitter.ts","../src/EscapeSequences.ts","../src/CompositionHelper.ts","../src/Charsets.ts","../src/BufferSet.ts","../src/Buffer.ts","../node_modules/browserify/node_modules/browser-pack/_prelude.js"],"sourcesContent":["/**\n * xterm.js: xterm, in the browser\n * Originally forked from (with the author's permission):\n * Fabrice Bellard's javascript vt100 for jslinux:\n * http://bellard.org/jslinux/\n * Copyright (c) 2011 Fabrice Bellard\n * The original design remains. The terminal itself\n * has been extended to include xterm CSI codes, among\n * other features.\n * @license MIT\n */\n\nimport { BufferSet } from './BufferSet';\nimport { CompositionHelper } from './CompositionHelper';\nimport { EventEmitter } from './EventEmitter';\nimport { Viewport } from './Viewport';\nimport { rightClickHandler, moveTextAreaUnderMouseCursor, pasteHandler, copyHandler } from './handlers/Clipboard';\nimport { CircularList } from './utils/CircularList';\nimport { C0 } from './EscapeSequences';\nimport { InputHandler } from './InputHandler';\nimport { Parser } from './Parser';\nimport { Renderer } from './Renderer';\nimport { Linkifier } from './Linkifier';\nimport { SelectionManager } from './SelectionManager';\nimport { CharMeasure } from './utils/CharMeasure';\nimport * as Browser from './utils/Browser';\nimport * as Mouse from './utils/Mouse';\nimport { CHARSETS } from './Charsets';\nimport { getRawByteCoords } from './utils/Mouse';\nimport { translateBufferLineToString } from './utils/BufferLine';\n\n/**\n * Terminal Emulation References:\n * http://vt100.net/\n * http://invisible-island.net/xterm/ctlseqs/ctlseqs.txt\n * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html\n * http://invisible-island.net/vttest/\n * http://www.inwap.com/pdp10/ansicode.txt\n * http://linux.die.net/man/4/console_codes\n * http://linux.die.net/man/7/urxvt\n */\n\n// Let it work inside Node.js for automated testing purposes.\nvar document = (typeof window != 'undefined') ? window.document : null;\n\n/**\n * The amount of write requests to queue before sending an XOFF signal to the\n * pty process. This number must be small in order for ^C and similar sequences\n * to be responsive.\n */\nvar WRITE_BUFFER_PAUSE_THRESHOLD = 5;\n\n/**\n * The number of writes to perform in a single batch before allowing the\n * renderer to catch up with a 0ms setTimeout.\n */\nvar WRITE_BATCH_SIZE = 300;\n\n/**\n * The time between cursor blinks. This is driven by JS rather than a CSS\n * animation due to a bug in Chromium that causes it to use excessive CPU time.\n * See https://github.com/Microsoft/vscode/issues/22900\n */\nvar CURSOR_BLINK_INTERVAL = 600;\n\n/**\n * Terminal\n */\n\n/**\n * Creates a new `Terminal` object.\n *\n * @param {object} options An object containing a set of options, the available options are:\n * - `cursorBlink` (boolean): Whether the terminal cursor blinks\n * - `cols` (number): The number of columns of the terminal (horizontal size)\n * - `rows` (number): The number of rows of the terminal (vertical size)\n *\n * @public\n * @class Xterm Xterm\n * @alias module:xterm/src/xterm\n */\nfunction Terminal(options) {\n var self = this;\n\n if (!(this instanceof Terminal)) {\n return new Terminal(arguments[0], arguments[1], arguments[2]);\n }\n\n self.browser = Browser;\n self.cancel = Terminal.cancel;\n\n EventEmitter.call(this);\n\n if (typeof options === 'number') {\n options = {\n cols: arguments[0],\n rows: arguments[1],\n handler: arguments[2]\n };\n }\n\n options = options || {};\n\n\n Object.keys(Terminal.defaults).forEach(function(key) {\n if (options[key] == null) {\n options[key] = Terminal.options[key];\n\n if (Terminal[key] !== Terminal.defaults[key]) {\n options[key] = Terminal[key];\n }\n }\n self[key] = options[key];\n });\n\n if (options.colors.length === 8) {\n options.colors = options.colors.concat(Terminal._colors.slice(8));\n } else if (options.colors.length === 16) {\n options.colors = options.colors.concat(Terminal._colors.slice(16));\n } else if (options.colors.length === 10) {\n options.colors = options.colors.slice(0, -2).concat(\n Terminal._colors.slice(8, -2), options.colors.slice(-2));\n } else if (options.colors.length === 18) {\n options.colors = options.colors.concat(\n Terminal._colors.slice(16, -2), options.colors.slice(-2));\n }\n this.colors = options.colors;\n\n this.options = options;\n\n // this.context = options.context || window;\n // this.document = options.document || document;\n this.parent = options.body || options.parent || (\n document ? document.getElementsByTagName('body')[0] : null\n );\n\n this.cols = options.cols || options.geometry[0];\n this.rows = options.rows || options.geometry[1];\n this.geometry = [this.cols, this.rows];\n\n if (options.handler) {\n this.on('data', options.handler);\n }\n\n this.cursorState = 0;\n this.cursorHidden = false;\n this.convertEol;\n this.queue = '';\n this.customKeyEventHandler = null;\n this.cursorBlinkInterval = null;\n\n // modes\n this.applicationKeypad = false;\n this.applicationCursor = false;\n this.originMode = false;\n this.insertMode = false;\n this.wraparoundMode = true; // defaults: xterm - true, vt100 - false\n\n // charset\n this.charset = null;\n this.gcharset = null;\n this.glevel = 0;\n this.charsets = [null];\n\n // mouse properties\n this.decLocator;\n this.x10Mouse;\n this.vt200Mouse;\n this.vt300Mouse;\n this.normalMouse;\n this.mouseEvents;\n this.sendFocus;\n this.utfMouse;\n this.sgrMouse;\n this.urxvtMouse;\n\n // misc\n this.element;\n this.children;\n this.refreshStart;\n this.refreshEnd;\n this.savedX;\n this.savedY;\n this.savedCols;\n\n // stream\n this.readable = true;\n this.writable = true;\n\n this.defAttr = (0 << 18) | (257 << 9) | (256 << 0);\n this.curAttr = this.defAttr;\n\n this.params = [];\n this.currentParam = 0;\n this.prefix = '';\n this.postfix = '';\n\n this.inputHandler = new InputHandler(this);\n this.parser = new Parser(this.inputHandler, this);\n // Reuse renderer if the Terminal is being recreated via a Terminal.reset call.\n this.renderer = this.renderer || null;\n this.selectionManager = this.selectionManager || null;\n this.linkifier = this.linkifier || new Linkifier();\n\n // user input states\n this.writeBuffer = [];\n this.writeInProgress = false;\n\n /**\n * Whether _xterm.js_ sent XOFF in order to catch up with the pty process.\n * This is a distinct state from writeStopped so that if the user requested\n * XOFF via ^S that it will not automatically resume when the writeBuffer goes\n * below threshold.\n */\n this.xoffSentToCatchUp = false;\n\n /** Whether writing has been stopped as a result of XOFF */\n this.writeStopped = false;\n\n // leftover surrogate high from previous write invocation\n this.surrogate_high = '';\n\n // Create the terminal's buffers and set the current buffer\n this.buffers = new BufferSet(this);\n this.buffer = this.buffers.active; // Convenience shortcut;\n this.buffers.on('activate', function (buffer) {\n this._terminal.buffer = buffer;\n });\n\n // Ensure the selection manager has the correct buffer\n if (this.selectionManager) {\n this.selectionManager.setBuffer(this.buffer.lines);\n }\n\n this.setupStops();\n\n // Store if user went browsing history in scrollback\n this.userScrolling = false;\n}\n\ninherits(Terminal, EventEmitter);\n\n/**\n * back_color_erase feature for xterm.\n */\nTerminal.prototype.eraseAttr = function() {\n // if (this.is('screen')) return this.defAttr;\n return (this.defAttr & ~0x1ff) | (this.curAttr & 0x1ff);\n};\n\n/**\n * Colors\n */\n\n// Colors 0-15\nTerminal.tangoColors = [\n // dark:\n '#2e3436',\n '#cc0000',\n '#4e9a06',\n '#c4a000',\n '#3465a4',\n '#75507b',\n '#06989a',\n '#d3d7cf',\n // bright:\n '#555753',\n '#ef2929',\n '#8ae234',\n '#fce94f',\n '#729fcf',\n '#ad7fa8',\n '#34e2e2',\n '#eeeeec'\n];\n\n// Colors 0-15 + 16-255\n// Much thanks to TooTallNate for writing this.\nTerminal.colors = (function() {\n var colors = Terminal.tangoColors.slice()\n , r = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]\n , i;\n\n // 16-231\n i = 0;\n for (; i < 216; i++) {\n out(r[(i / 36) % 6 | 0], r[(i / 6) % 6 | 0], r[i % 6]);\n }\n\n // 232-255 (grey)\n i = 0;\n for (; i < 24; i++) {\n r = 8 + i * 10;\n out(r, r, r);\n }\n\n function out(r, g, b) {\n colors.push('#' + hex(r) + hex(g) + hex(b));\n }\n\n function hex(c) {\n c = c.toString(16);\n return c.length < 2 ? '0' + c : c;\n }\n\n return colors;\n})();\n\nTerminal._colors = Terminal.colors.slice();\n\nTerminal.vcolors = (function() {\n var out = []\n , colors = Terminal.colors\n , i = 0\n , color;\n\n for (; i < 256; i++) {\n color = parseInt(colors[i].substring(1), 16);\n out.push([\n (color >> 16) & 0xff,\n (color >> 8) & 0xff,\n color & 0xff\n ]);\n }\n\n return out;\n})();\n\n/**\n * Options\n */\n\nTerminal.defaults = {\n colors: Terminal.colors,\n theme: 'default',\n convertEol: false,\n termName: 'xterm',\n geometry: [80, 24],\n cursorBlink: false,\n cursorStyle: 'block',\n visualBell: false,\n popOnBell: false,\n scrollback: 1000,\n screenKeys: false,\n debug: false,\n cancelEvents: false,\n disableStdin: false,\n useFlowControl: false,\n tabStopWidth: 8\n // programFeatures: false,\n // focusKeys: false,\n};\n\nTerminal.options = {};\n\nTerminal.focus = null;\n\neach(keys(Terminal.defaults), function(key) {\n Terminal[key] = Terminal.defaults[key];\n Terminal.options[key] = Terminal.defaults[key];\n});\n\n/**\n * Focus the terminal. Delegates focus handling to the terminal's DOM element.\n */\nTerminal.prototype.focus = function() {\n return this.textarea.focus();\n};\n\n/**\n * Retrieves an option's value from the terminal.\n * @param {string} key The option key.\n */\nTerminal.prototype.getOption = function(key) {\n if (!(key in Terminal.defaults)) {\n throw new Error('No option with key \"' + key + '\"');\n }\n\n if (typeof this.options[key] !== 'undefined') {\n return this.options[key];\n }\n\n return this[key];\n};\n\n/**\n * Sets an option on the terminal.\n * @param {string} key The option key.\n * @param {string} value The option value.\n */\nTerminal.prototype.setOption = function(key, value) {\n if (!(key in Terminal.defaults)) {\n throw new Error('No option with key \"' + key + '\"');\n }\n switch (key) {\n case 'scrollback':\n if (value < this.rows) {\n let msg = 'Setting the scrollback value less than the number of rows ';\n\n msg += `(${this.rows}) is not allowed.`;\n\n console.warn(msg);\n return false;\n }\n\n if (this.options[key] !== value) {\n if (this.buffer.lines.length > value) {\n const amountToTrim = this.buffer.lines.length - value;\n const needsRefresh = (this.buffer.ydisp - amountToTrim < 0);\n this.buffer.lines.trimStart(amountToTrim);\n this.buffer.ybase = Math.max(this.buffer.ybase - amountToTrim, 0);\n this.buffer.ydisp = Math.max(this.buffer.ydisp - amountToTrim, 0);\n if (needsRefresh) {\n this.refresh(0, this.rows - 1);\n }\n }\n this.buffer.lines.maxLength = value;\n this.viewport.syncScrollArea();\n }\n break;\n }\n this[key] = value;\n this.options[key] = value;\n switch (key) {\n case 'cursorBlink': this.setCursorBlinking(value); break;\n case 'cursorStyle':\n this.element.classList.toggle(`xterm-cursor-style-block`, value === 'block');\n this.element.classList.toggle(`xterm-cursor-style-underline`, value === 'underline');\n this.element.classList.toggle(`xterm-cursor-style-bar`, value === 'bar');\n break;\n case 'tabStopWidth': this.setupStops(); break;\n }\n};\n\nTerminal.prototype.restartCursorBlinking = function () {\n this.setCursorBlinking(this.options.cursorBlink);\n};\n\nTerminal.prototype.setCursorBlinking = function (enabled) {\n this.element.classList.toggle('xterm-cursor-blink', enabled);\n this.clearCursorBlinkingInterval();\n if (enabled) {\n var self = this;\n this.cursorBlinkInterval = setInterval(function () {\n self.element.classList.toggle('xterm-cursor-blink-on');\n }, CURSOR_BLINK_INTERVAL);\n }\n};\n\nTerminal.prototype.clearCursorBlinkingInterval = function () {\n this.element.classList.remove('xterm-cursor-blink-on');\n if (this.cursorBlinkInterval) {\n clearInterval(this.cursorBlinkInterval);\n this.cursorBlinkInterval = null;\n }\n};\n\n/**\n * Binds the desired focus behavior on a given terminal object.\n *\n * @static\n */\nTerminal.bindFocus = function (term) {\n on(term.textarea, 'focus', function (ev) {\n if (term.sendFocus) {\n term.send(C0.ESC + '[I');\n }\n term.element.classList.add('focus');\n term.showCursor();\n term.restartCursorBlinking.apply(term);\n Terminal.focus = term;\n term.emit('focus', {terminal: term});\n });\n};\n\n/**\n * Blur the terminal. Delegates blur handling to the terminal's DOM element.\n */\nTerminal.prototype.blur = function() {\n return this.textarea.blur();\n};\n\n/**\n * Binds the desired blur behavior on a given terminal object.\n *\n * @static\n */\nTerminal.bindBlur = function (term) {\n on(term.textarea, 'blur', function (ev) {\n term.refresh(term.buffer.y, term.buffer.y);\n if (term.sendFocus) {\n term.send(C0.ESC + '[O');\n }\n term.element.classList.remove('focus');\n term.clearCursorBlinkingInterval.apply(term);\n Terminal.focus = null;\n term.emit('blur', {terminal: term});\n });\n};\n\n/**\n * Initialize default behavior\n */\nTerminal.prototype.initGlobal = function() {\n var term = this;\n\n Terminal.bindKeys(this);\n Terminal.bindFocus(this);\n Terminal.bindBlur(this);\n\n // Bind clipboard functionality\n on(this.element, 'copy', event => {\n // If mouse events are active it means the selection manager is disabled and\n // copy should be handled by the host program.\n if (!term.hasSelection()) {\n return;\n }\n copyHandler(event, term, this.selectionManager);\n });\n const pasteHandlerWrapper = event => pasteHandler(event, term);\n on(this.textarea, 'paste', pasteHandlerWrapper);\n on(this.element, 'paste', pasteHandlerWrapper);\n\n // Handle right click context menus\n if (term.browser.isFirefox) {\n // Firefox doesn't appear to fire the contextmenu event on right click\n on(this.element, 'mousedown', event => {\n if (event.button == 2) {\n rightClickHandler(event, this.textarea, this.selectionManager);\n }\n });\n } else {\n on(this.element, 'contextmenu', event => {\n rightClickHandler(event, this.textarea, this.selectionManager);\n });\n }\n\n // Move the textarea under the cursor when middle clicking on Linux to ensure\n // middle click to paste selection works. This only appears to work in Chrome\n // at the time is writing.\n if (term.browser.isLinux) {\n // Use auxclick event over mousedown the latter doesn't seem to work. Note\n // that the regular click event doesn't fire for the middle mouse button.\n on(this.element, 'auxclick', event => {\n if (event.button === 1) {\n moveTextAreaUnderMouseCursor(event, this.textarea, this.selectionManager);\n }\n });\n }\n};\n\n/**\n * Apply key handling to the terminal\n */\nTerminal.bindKeys = function(term) {\n on(term.element, 'keydown', function(ev) {\n if (document.activeElement != this) {\n return;\n }\n term.keyDown(ev);\n }, true);\n\n on(term.element, 'keypress', function(ev) {\n if (document.activeElement != this) {\n return;\n }\n term.keyPress(ev);\n }, true);\n\n on(term.element, 'keyup', function(ev) {\n if (!wasMondifierKeyOnlyEvent(ev)) {\n term.focus(term);\n }\n }, true);\n\n on(term.textarea, 'keydown', function(ev) {\n term.keyDown(ev);\n }, true);\n\n on(term.textarea, 'keypress', function(ev) {\n term.keyPress(ev);\n // Truncate the textarea's value, since it is not needed\n this.value = '';\n }, true);\n\n on(term.textarea, 'compositionstart', term.compositionHelper.compositionstart.bind(term.compositionHelper));\n on(term.textarea, 'compositionupdate', term.compositionHelper.compositionupdate.bind(term.compositionHelper));\n on(term.textarea, 'compositionend', term.compositionHelper.compositionend.bind(term.compositionHelper));\n term.on('refresh', term.compositionHelper.updateCompositionElements.bind(term.compositionHelper));\n term.on('refresh', function (data) {\n term.queueLinkification(data.start, data.end)\n });\n};\n\n\n/**\n * Insert the given row to the terminal or produce a new one\n * if no row argument is passed. Return the inserted row.\n * @param {HTMLElement} row (optional) The row to append to the terminal.\n */\nTerminal.prototype.insertRow = function (row) {\n if (typeof row != 'object') {\n row = document.createElement('div');\n }\n\n this.rowContainer.appendChild(row);\n this.children.push(row);\n\n return row;\n};\n\n/**\n * Opens the terminal within an element.\n *\n * @param {HTMLElement} parent The element to create the terminal within.\n * @param {boolean} focus Focus the terminal, after it gets instantiated in the DOM\n */\nTerminal.prototype.open = function(parent, focus) {\n var self=this, i=0, div;\n\n this.parent = parent || this.parent;\n\n if (!this.parent) {\n throw new Error('Terminal requires a parent element.');\n }\n\n // Grab global elements\n this.context = this.parent.ownerDocument.defaultView;\n this.document = this.parent.ownerDocument;\n this.body = this.document.getElementsByTagName('body')[0];\n\n //Create main element container\n this.element = this.document.createElement('div');\n this.element.classList.add('terminal');\n this.element.classList.add('xterm');\n this.element.classList.add('xterm-theme-' + this.theme);\n this.element.classList.add(`xterm-cursor-style-${this.options.cursorStyle}`);\n this.setCursorBlinking(this.options.cursorBlink);\n\n this.element.setAttribute('tabindex', 0);\n\n this.viewportElement = document.createElement('div');\n this.viewportElement.classList.add('xterm-viewport');\n this.element.appendChild(this.viewportElement);\n this.viewportScrollArea = document.createElement('div');\n this.viewportScrollArea.classList.add('xterm-scroll-area');\n this.viewportElement.appendChild(this.viewportScrollArea);\n\n // Create the selection container.\n this.selectionContainer = document.createElement('div');\n this.selectionContainer.classList.add('xterm-selection');\n this.element.appendChild(this.selectionContainer);\n\n // Create the container that will hold the lines of the terminal and then\n // produce the lines the lines.\n this.rowContainer = document.createElement('div');\n this.rowContainer.classList.add('xterm-rows');\n this.element.appendChild(this.rowContainer);\n this.children = [];\n this.linkifier.attachToDom(document, this.children);\n\n // Create the container that will hold helpers like the textarea for\n // capturing DOM Events. Then produce the helpers.\n this.helperContainer = document.createElement('div');\n this.helperContainer.classList.add('xterm-helpers');\n // TODO: This should probably be inserted once it's filled to prevent an additional layout\n this.element.appendChild(this.helperContainer);\n this.textarea = document.createElement('textarea');\n this.textarea.classList.add('xterm-helper-textarea');\n this.textarea.setAttribute('autocorrect', 'off');\n this.textarea.setAttribute('autocapitalize', 'off');\n this.textarea.setAttribute('spellcheck', 'false');\n this.textarea.tabIndex = 0;\n this.textarea.addEventListener('focus', function() {\n self.emit('focus', {terminal: self});\n });\n this.textarea.addEventListener('blur', function() {\n self.emit('blur', {terminal: self});\n });\n this.helperContainer.appendChild(this.textarea);\n\n this.compositionView = document.createElement('div');\n this.compositionView.classList.add('composition-view');\n this.compositionHelper = new CompositionHelper(this.textarea, this.compositionView, this);\n this.helperContainer.appendChild(this.compositionView);\n\n this.charSizeStyleElement = document.createElement('style');\n this.helperContainer.appendChild(this.charSizeStyleElement);\n\n for (; i < this.rows; i++) {\n this.insertRow();\n }\n this.parent.appendChild(this.element);\n\n this.charMeasure = new CharMeasure(document, this.helperContainer);\n this.charMeasure.on('charsizechanged', function () {\n self.updateCharSizeStyles();\n });\n this.charMeasure.measure();\n\n this.viewport = new Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasure);\n this.renderer = new Renderer(this);\n this.selectionManager = new SelectionManager(\n this, this.buffer.lines, this.rowContainer, this.charMeasure\n );\n this.selectionManager.on('refresh', data => {\n this.renderer.refreshSelection(data.start, data.end);\n });\n this.selectionManager.on('newselection', text => {\n // If there's a new selection, put it into the textarea, focus and select it\n // in order to register it as a selection on the OS. This event is fired\n // only on Linux to enable middle click to paste selection.\n this.textarea.value = text;\n this.textarea.focus();\n this.textarea.select();\n });\n this.on('scroll', () => this.selectionManager.refresh());\n this.viewportElement.addEventListener('scroll', () => this.selectionManager.refresh());\n\n // Setup loop that draws to screen\n this.refresh(0, this.rows - 1);\n\n // Initialize global actions that\n // need to be taken on the document.\n this.initGlobal();\n\n /**\n * Automatic focus functionality.\n * TODO: Default to `false` starting with xterm.js 3.0.\n */\n if (typeof focus == 'undefined') {\n let message = 'You did not pass the `focus` argument in `Terminal.prototype.open()`.\\n';\n\n message += 'The `focus` argument now defaults to `true` but starting with xterm.js 3.0 ';\n message += 'it will default to `false`.';\n\n console.warn(message);\n focus = true;\n }\n\n if (focus) {\n this.focus();\n }\n\n // Listen for mouse events and translate\n // them into terminal mouse protocols.\n this.bindMouse();\n\n /**\n * This event is emitted when terminal has completed opening.\n *\n * @event open\n */\n this.emit('open');\n};\n\n\n/**\n * Attempts to load an add-on using CommonJS or RequireJS (whichever is available).\n * @param {string} addon The name of the addon to load\n * @static\n */\nTerminal.loadAddon = function(addon, callback) {\n if (typeof exports === 'object' && typeof module === 'object') {\n // CommonJS\n return require('./addons/' + addon + '/' + addon);\n } else if (typeof define == 'function') {\n // RequireJS\n return require(['./addons/' + addon + '/' + addon], callback);\n } else {\n console.error('Cannot load a module without a CommonJS or RequireJS environment.');\n return false;\n }\n};\n\n/**\n * Updates the helper CSS class with any changes necessary after the terminal's\n * character width has been changed.\n */\nTerminal.prototype.updateCharSizeStyles = function() {\n this.charSizeStyleElement.textContent =\n `.xterm-wide-char{width:${this.charMeasure.width * 2}px;}` +\n `.xterm-normal-char{width:${this.charMeasure.width}px;}` +\n `.xterm-rows > div{height:${this.charMeasure.height}px;}`;\n}\n\n/**\n * XTerm mouse events\n * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking\n * To better understand these\n * the xterm code is very helpful:\n * Relevant files:\n * button.c, charproc.c, misc.c\n * Relevant functions in xterm/button.c:\n * BtnCode, EmitButtonCode, EditorButton, SendMousePosition\n */\nTerminal.prototype.bindMouse = function() {\n var el = this.element, self = this, pressed = 32;\n\n // mouseup, mousedown, wheel\n // left click: ^[[M 3<^[[M#3<\n // wheel up: ^[[M`3>\n function sendButton(ev) {\n var button\n , pos;\n\n // get the xterm-style button\n button = getButton(ev);\n\n // get mouse coordinates\n pos = getRawByteCoords(ev, self.rowContainer, self.charMeasure, self.cols, self.rows);\n if (!pos) return;\n\n sendEvent(button, pos);\n\n switch (ev.overrideType || ev.type) {\n case 'mousedown':\n pressed = button;\n break;\n case 'mouseup':\n // keep it at the left\n // button, just in case.\n pressed = 32;\n break;\n case 'wheel':\n // nothing. don't\n // interfere with\n // `pressed`.\n break;\n }\n }\n\n // motion example of a left click:\n // ^[[M 3<^[[M@4<^[[M@5<^[[M@6<^[[M@7<^[[M#7<\n function sendMove(ev) {\n var button = pressed\n , pos;\n\n pos = getRawByteCoords(ev, self.rowContainer, self.charMeasure, self.cols, self.rows);\n if (!pos) return;\n\n // buttons marked as motions\n // are incremented by 32\n button += 32;\n\n sendEvent(button, pos);\n }\n\n // encode button and\n // position to characters\n function encode(data, ch) {\n if (!self.utfMouse) {\n if (ch === 255) return data.push(0);\n if (ch > 127) ch = 127;\n data.push(ch);\n } else {\n if (ch === 2047) return data.push(0);\n if (ch < 127) {\n data.push(ch);\n } else {\n if (ch > 2047) ch = 2047;\n data.push(0xC0 | (ch >> 6));\n data.push(0x80 | (ch & 0x3F));\n }\n }\n }\n\n // send a mouse event:\n // regular/utf8: ^[[M Cb Cx Cy\n // urxvt: ^[[ Cb ; Cx ; Cy M\n // sgr: ^[[ Cb ; Cx ; Cy M/m\n // vt300: ^[[ 24(1/3/5)~ [ Cx , Cy ] \\r\n // locator: CSI P e ; P b ; P r ; P c ; P p & w\n function sendEvent(button, pos) {\n // self.emit('mouse', {\n // x: pos.x - 32,\n // y: pos.x - 32,\n // button: button\n // });\n\n if (self.vt300Mouse) {\n // NOTE: Unstable.\n // http://www.vt100.net/docs/vt3xx-gp/chapter15.html\n button &= 3;\n pos.x -= 32;\n pos.y -= 32;\n var data = C0.ESC + '[24';\n if (button === 0) data += '1';\n else if (button === 1) data += '3';\n else if (button === 2) data += '5';\n else if (button === 3) return;\n else data += '0';\n data += '~[' + pos.x + ',' + pos.y + ']\\r';\n self.send(data);\n return;\n }\n\n if (self.decLocator) {\n // NOTE: Unstable.\n button &= 3;\n pos.x -= 32;\n pos.y -= 32;\n if (button === 0) button = 2;\n else if (button === 1) button = 4;\n else if (button === 2) button = 6;\n else if (button === 3) button = 3;\n self.send(C0.ESC + '['\n + button\n + ';'\n + (button === 3 ? 4 : 0)\n + ';'\n + pos.y\n + ';'\n + pos.x\n + ';'\n + (pos.page || 0)\n + '&w');\n return;\n }\n\n if (self.urxvtMouse) {\n pos.x -= 32;\n pos.y -= 32;\n pos.x++;\n pos.y++;\n self.send(C0.ESC + '[' + button + ';' + pos.x + ';' + pos.y + 'M');\n return;\n }\n\n if (self.sgrMouse) {\n pos.x -= 32;\n pos.y -= 32;\n self.send(C0.ESC + '[<'\n + (((button & 3) === 3 ? button & ~3 : button) - 32)\n + ';'\n + pos.x\n + ';'\n + pos.y\n + ((button & 3) === 3 ? 'm' : 'M'));\n return;\n }\n\n var data = [];\n\n encode(data, button);\n encode(data, pos.x);\n encode(data, pos.y);\n\n self.send(C0.ESC + '[M' + String.fromCharCode.apply(String, data));\n }\n\n function getButton(ev) {\n var button\n , shift\n , meta\n , ctrl\n , mod;\n\n // two low bits:\n // 0 = left\n // 1 = middle\n // 2 = right\n // 3 = release\n // wheel up/down:\n // 1, and 2 - with 64 added\n switch (ev.overrideType || ev.type) {\n case 'mousedown':\n button = ev.button != null\n ? +ev.button\n : ev.which != null\n ? ev.which - 1\n : null;\n\n if (self.browser.isMSIE) {\n button = button === 1 ? 0 : button === 4 ? 1 : button;\n }\n break;\n case 'mouseup':\n button = 3;\n break;\n case 'DOMMouseScroll':\n button = ev.detail < 0\n ? 64\n : 65;\n break;\n case 'wheel':\n button = ev.wheelDeltaY > 0\n ? 64\n : 65;\n break;\n }\n\n // next three bits are the modifiers:\n // 4 = shift, 8 = meta, 16 = control\n shift = ev.shiftKey ? 4 : 0;\n meta = ev.metaKey ? 8 : 0;\n ctrl = ev.ctrlKey ? 16 : 0;\n mod = shift | meta | ctrl;\n\n // no mods\n if (self.vt200Mouse) {\n // ctrl only\n mod &= ctrl;\n } else if (!self.normalMouse) {\n mod = 0;\n }\n\n // increment to SP\n button = (32 + (mod << 2)) + button;\n\n return button;\n }\n\n on(el, 'mousedown', function(ev) {\n\n // Prevent the focus on the textarea from getting lost\n // and make sure we get focused on mousedown\n ev.preventDefault();\n self.focus();\n\n if (!self.mouseEvents) return;\n\n // send the button\n sendButton(ev);\n\n // fix for odd bug\n //if (self.vt200Mouse && !self.normalMouse) {\n if (self.vt200Mouse) {\n ev.overrideType = 'mouseup';\n sendButton(ev);\n return self.cancel(ev);\n }\n\n // bind events\n if (self.normalMouse) on(self.document, 'mousemove', sendMove);\n\n // x10 compatibility mode can't send button releases\n if (!self.x10Mouse) {\n on(self.document, 'mouseup', function up(ev) {\n sendButton(ev);\n if (self.normalMouse) off(self.document, 'mousemove', sendMove);\n off(self.document, 'mouseup', up);\n return self.cancel(ev);\n });\n }\n\n return self.cancel(ev);\n });\n\n //if (self.normalMouse) {\n // on(self.document, 'mousemove', sendMove);\n //}\n\n on(el, 'wheel', function(ev) {\n if (!self.mouseEvents) return;\n if (self.x10Mouse\n || self.vt300Mouse\n || self.decLocator) return;\n sendButton(ev);\n return self.cancel(ev);\n });\n\n // allow wheel scrolling in\n // the shell for example\n on(el, 'wheel', function(ev) {\n if (self.mouseEvents) return;\n self.viewport.onWheel(ev);\n return self.cancel(ev);\n });\n\n on(el, 'touchstart', function(ev) {\n if (self.mouseEvents) return;\n self.viewport.onTouchStart(ev);\n return self.cancel(ev);\n });\n\n on(el, 'touchmove', function(ev) {\n if (self.mouseEvents) return;\n self.viewport.onTouchMove(ev);\n return self.cancel(ev);\n });\n};\n\n/**\n * Destroys the terminal.\n */\nTerminal.prototype.destroy = function() {\n this.readable = false;\n this.writable = false;\n this._events = {};\n this.handler = function() {};\n this.write = function() {};\n if (this.element && this.element.parentNode) {\n this.element.parentNode.removeChild(this.element);\n }\n //this.emit('close');\n};\n\n/**\n * Tells the renderer to refresh terminal content between two rows (inclusive) at the next\n * opportunity.\n * @param {number} start The row to start from (between 0 and this.rows - 1).\n * @param {number} end The row to end at (between start and this.rows - 1).\n */\nTerminal.prototype.refresh = function(start, end) {\n if (this.renderer) {\n this.renderer.queueRefresh(start, end);\n }\n};\n\n/**\n * Queues linkification for the specified rows.\n * @param {number} start The row to start from (between 0 and this.rows - 1).\n * @param {number} end The row to end at (between start and this.rows - 1).\n */\nTerminal.prototype.queueLinkification = function(start, end) {\n if (this.linkifier) {\n for (let i = start; i <= end; i++) {\n this.linkifier.linkifyRow(i);\n }\n }\n};\n\n/**\n * Display the cursor element\n */\nTerminal.prototype.showCursor = function() {\n if (!this.cursorState) {\n this.cursorState = 1;\n this.refresh(this.buffer.y, this.buffer.y);\n }\n};\n\n/**\n * Scroll the terminal down 1 row, creating a blank line.\n * @param {boolean} isWrapped Whether the new line is wrapped from the previous\n * line.\n */\nTerminal.prototype.scroll = function(isWrapped) {\n var row;\n\n // Make room for the new row in lines\n if (this.buffer.lines.length === this.buffer.lines.maxLength) {\n this.buffer.lines.trimStart(1);\n this.buffer.ybase--;\n if (this.buffer.ydisp !== 0) {\n this.buffer.ydisp--;\n }\n }\n\n this.buffer.ybase++;\n\n // TODO: Why is this done twice?\n if (!this.userScrolling) {\n this.buffer.ydisp = this.buffer.ybase;\n }\n\n // last line\n row = this.buffer.ybase + this.rows - 1;\n\n // subtract the bottom scroll region\n row -= this.rows - 1 - this.buffer.scrollBottom;\n\n if (row === this.buffer.lines.length) {\n // Optimization: pushing is faster than splicing when they amount to the same behavior\n this.buffer.lines.push(this.blankLine(undefined, isWrapped));\n } else {\n // add our new line\n this.buffer.lines.splice(row, 0, this.blankLine(undefined, isWrapped));\n }\n\n if (this.buffer.scrollTop !== 0) {\n if (this.buffer.ybase !== 0) {\n this.buffer.ybase--;\n if (!this.userScrolling) {\n this.buffer.ydisp = this.buffer.ybase;\n }\n }\n this.buffer.lines.splice(this.buffer.ybase + this.buffer.scrollTop, 1);\n }\n\n // this.maxRange();\n this.updateRange(this.buffer.scrollTop);\n this.updateRange(this.buffer.scrollBottom);\n\n /**\n * This event is emitted whenever the terminal is scrolled.\n * The one parameter passed is the new y display position.\n *\n * @event scroll\n */\n this.emit('scroll', this.buffer.ydisp);\n};\n\n/**\n * Scroll the display of the terminal\n * @param {number} disp The number of lines to scroll down (negatives scroll up).\n * @param {boolean} suppressScrollEvent Don't emit the scroll event as scrollDisp. This is used\n * to avoid unwanted events being handled by the veiwport when the event was triggered from the\n * viewport originally.\n */\nTerminal.prototype.scrollDisp = function(disp, suppressScrollEvent) {\n if (disp < 0) {\n if (this.buffer.ydisp === 0) {\n return;\n }\n this.userScrolling = true;\n } else if (disp + this.buffer.ydisp >= this.buffer.ybase) {\n this.userScrolling = false;\n }\n\n const oldYdisp = this.buffer.ydisp;\n this.buffer.ydisp = Math.max(Math.min(this.buffer.ydisp + disp, this.buffer.ybase), 0);\n\n // No change occurred, don't trigger scroll/refresh\n if (oldYdisp === this.buffer.ydisp) {\n return;\n }\n\n if (!suppressScrollEvent) {\n this.emit('scroll', this.buffer.ydisp);\n }\n\n this.refresh(0, this.rows - 1);\n};\n\n/**\n * Scroll the display of the terminal by a number of pages.\n * @param {number} pageCount The number of pages to scroll (negative scrolls up).\n */\nTerminal.prototype.scrollPages = function(pageCount) {\n this.scrollDisp(pageCount * (this.rows - 1));\n};\n\n/**\n * Scrolls the display of the terminal to the top.\n */\nTerminal.prototype.scrollToTop = function() {\n this.scrollDisp(-this.buffer.ydisp);\n};\n\n/**\n * Scrolls the display of the terminal to the bottom.\n */\nTerminal.prototype.scrollToBottom = function() {\n this.scrollDisp(this.buffer.ybase - this.buffer.ydisp);\n};\n\n/**\n * Writes text to the terminal.\n * @param {string} data The text to write to the terminal.\n */\nTerminal.prototype.write = function(data) {\n this.writeBuffer.push(data);\n\n // Send XOFF to pause the pty process if the write buffer becomes too large so\n // xterm.js can catch up before more data is sent. This is necessary in order\n // to keep signals such as ^C responsive.\n if (this.options.useFlowControl && !this.xoffSentToCatchUp && this.writeBuffer.length >= WRITE_BUFFER_PAUSE_THRESHOLD) {\n // XOFF - stop pty pipe\n // XON will be triggered by emulator before processing data chunk\n this.send(C0.DC3);\n this.xoffSentToCatchUp = true;\n }\n\n if (!this.writeInProgress && this.writeBuffer.length > 0) {\n // Kick off a write which will write all data in sequence recursively\n this.writeInProgress = true;\n // Kick off an async innerWrite so more writes can come in while processing data\n var self = this;\n setTimeout(function () {\n self.innerWrite();\n });\n }\n};\n\nTerminal.prototype.innerWrite = function() {\n var writeBatch = this.writeBuffer.splice(0, WRITE_BATCH_SIZE);\n while (writeBatch.length > 0) {\n var data = writeBatch.shift();\n var l = data.length, i = 0, j, cs, ch, code, low, ch_width, row;\n\n // If XOFF was sent in order to catch up with the pty process, resume it if\n // the writeBuffer is empty to allow more data to come in.\n if (this.xoffSentToCatchUp && writeBatch.length === 0 && this.writeBuffer.length === 0) {\n this.send(C0.DC1);\n this.xoffSentToCatchUp = false;\n }\n\n this.refreshStart = this.buffer.y;\n this.refreshEnd = this.buffer.y;\n\n // HACK: Set the parser state based on it's state at the time of return.\n // This works around the bug #662 which saw the parser state reset in the\n // middle of parsing escape sequence in two chunks. For some reason the\n // state of the parser resets to 0 after exiting parser.parse. This change\n // just sets the state back based on the correct return statement.\n var state = this.parser.parse(data);\n this.parser.setState(state);\n\n this.updateRange(this.buffer.y);\n this.refresh(this.refreshStart, this.refreshEnd);\n }\n if (this.writeBuffer.length > 0) {\n // Allow renderer to catch up before processing the next batch\n var self = this;\n setTimeout(function () {\n self.innerWrite();\n }, 0);\n } else {\n this.writeInProgress = false;\n }\n};\n\n/**\n * Writes text to the terminal, followed by a break line character (\\n).\n * @param {string} data The text to write to the terminal.\n */\nTerminal.prototype.writeln = function(data) {\n this.write(data + '\\r\\n');\n};\n\n/**\n * DEPRECATED: only for backward compatibility. Please use attachCustomKeyEventHandler() instead.\n * @param {function} customKeydownHandler The custom KeyboardEvent handler to attach. This is a\n * function that takes a KeyboardEvent, allowing consumers to stop propogation and/or prevent\n * the default action. The function returns whether the event should be processed by xterm.js.\n */\nTerminal.prototype.attachCustomKeydownHandler = function(customKeydownHandler) {\n let message = 'attachCustomKeydownHandler() is DEPRECATED and will be removed soon. Please use attachCustomKeyEventHandler() instead.';\n console.warn(message);\n this.attachCustomKeyEventHandler(customKeydownHandler);\n};\n\n/**\n * Attaches a custom key event handler which is run before keys are processed, giving consumers of\n * xterm.js ultimate control as to what keys should be processed by the terminal and what keys\n * should not.\n * @param {function} customKeyEventHandler The custom KeyboardEvent handler to attach. This is a\n * function that takes a KeyboardEvent, allowing consumers to stop propogation and/or prevent\n * the default action. The function returns whether the event should be processed by xterm.js.\n */\nTerminal.prototype.attachCustomKeyEventHandler = function(customKeyEventHandler) {\n this.customKeyEventHandler = customKeyEventHandler;\n};\n\n/**\n * Attaches a http(s) link handler, forcing web links to behave differently to\n * regular tags. This will trigger a refresh as links potentially need to be\n * reconstructed. Calling this with null will remove the handler.\n * @param {LinkMatcherHandler} handler The handler callback function.\n */\nTerminal.prototype.setHypertextLinkHandler = function(handler) {\n if (!this.linkifier) {\n throw new Error('Cannot attach a hypertext link handler before Terminal.open is called');\n }\n this.linkifier.setHypertextLinkHandler(handler);\n // Refresh to force links to refresh\n this.refresh(0, this.rows - 1);\n};\n\n/**\n * Attaches a validation callback for hypertext links. This is useful to use\n * validation logic or to do something with the link's element and url.\n * @param {LinkMatcherValidationCallback} callback The callback to use, this can\n * be cleared with null.\n */\nTerminal.prototype.setHypertextValidationCallback = function(callback) {\n if (!this.linkifier) {\n throw new Error('Cannot attach a hypertext validation callback before Terminal.open is called');\n }\n this.linkifier.setHypertextValidationCallback(callback);\n // Refresh to force links to refresh\n this.refresh(0, this.rows - 1);\n};\n\n/**\n * Registers a link matcher, allowing custom link patterns to be matched and\n * handled.\n * @param {RegExp} regex The regular expression to search for, specifically\n * this searches the textContent of the rows. You will want to use \\s to match\n * a space ' ' character for example.\n * @param {LinkMatcherHandler} handler The callback when the link is called.\n * @param {LinkMatcherOptions} [options] Options for the link matcher.\n * @return {number} The ID of the new matcher, this can be used to deregister.\n */\nTerminal.prototype.registerLinkMatcher = function(regex, handler, options) {\n if (this.linkifier) {\n var matcherId = this.linkifier.registerLinkMatcher(regex, handler, options);\n this.refresh(0, this.rows - 1);\n return matcherId;\n }\n};\n\n/**\n * Deregisters a link matcher if it has been registered.\n * @param {number} matcherId The link matcher's ID (returned after register)\n */\nTerminal.prototype.deregisterLinkMatcher = function(matcherId) {\n if (this.linkifier) {\n if (this.linkifier.deregisterLinkMatcher(matcherId)) {\n this.refresh(0, this.rows - 1);\n }\n }\n};\n\n/**\n * Gets whether the terminal has an active selection.\n */\nTerminal.prototype.hasSelection = function() {\n return this.selectionManager ? this.selectionManager.hasSelection : false;\n};\n\n/**\n * Gets the terminal's current selection, this is useful for implementing copy\n * behavior outside of xterm.js.\n */\nTerminal.prototype.getSelection = function() {\n return this.selectionManager ? this.selectionManager.selectionText : '';\n};\n\n/**\n * Clears the current terminal selection.\n */\nTerminal.prototype.clearSelection = function() {\n if (this.selectionManager) {\n this.selectionManager.clearSelection();\n }\n};\n\n/**\n * Selects all text within the terminal.\n */\nTerminal.prototype.selectAll = function() {\n if (this.selectionManager) {\n this.selectionManager.selectAll();\n }\n};\n\n/**\n * Handle a keydown event\n * Key Resources:\n * - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent\n * @param {KeyboardEvent} ev The keydown event to be handled.\n */\nTerminal.prototype.keyDown = function(ev) {\n if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) {\n return false;\n }\n\n this.restartCursorBlinking();\n\n if (!this.compositionHelper.keydown.bind(this.compositionHelper)(ev)) {\n if (this.buffer.ybase !== this.buffer.ydisp) {\n this.scrollToBottom();\n }\n return false;\n }\n\n var self = this;\n var result = this.evaluateKeyEscapeSequence(ev);\n\n if (result.key === C0.DC3) { // XOFF\n this.writeStopped = true;\n } else if (result.key === C0.DC1) { // XON\n this.writeStopped = false;\n }\n\n if (result.scrollDisp) {\n this.scrollDisp(result.scrollDisp);\n return this.cancel(ev, true);\n }\n\n if (isThirdLevelShift(this, ev)) {\n return true;\n }\n\n if (result.cancel) {\n // The event is canceled at the end already, is this necessary?\n this.cancel(ev, true);\n }\n\n if (!result.key) {\n return true;\n }\n\n this.emit('keydown', ev);\n this.emit('key', result.key, ev);\n this.showCursor();\n this.handler(result.key);\n\n return this.cancel(ev, true);\n};\n\n/**\n * Returns an object that determines how a KeyboardEvent should be handled. The key of the\n * returned value is the new key code to pass to the PTY.\n *\n * Reference: http://invisible-island.net/xterm/ctlseqs/ctlseqs.html\n * @param {KeyboardEvent} ev The keyboard event to be translated to key escape sequence.\n */\nTerminal.prototype.evaluateKeyEscapeSequence = function(ev) {\n var result = {\n // Whether to cancel event propogation (NOTE: this may not be needed since the event is\n // canceled at the end of keyDown\n cancel: false,\n // The new key even to emit\n key: undefined,\n // The number of characters to scroll, if this is defined it will cancel the event\n scrollDisp: undefined\n };\n var modifiers = ev.shiftKey << 0 | ev.altKey << 1 | ev.ctrlKey << 2 | ev.metaKey << 3;\n switch (ev.keyCode) {\n case 8:\n // backspace\n if (ev.shiftKey) {\n result.key = C0.BS; // ^H\n break;\n }\n result.key = C0.DEL; // ^?\n break;\n case 9:\n // tab\n if (ev.shiftKey) {\n result.key = C0.ESC + '[Z';\n break;\n }\n result.key = C0.HT;\n result.cancel = true;\n break;\n case 13:\n // return/enter\n result.key = C0.CR;\n result.cancel = true;\n break;\n case 27:\n // escape\n result.key = C0.ESC;\n result.cancel = true;\n break;\n case 37:\n // left-arrow\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'D';\n // HACK: Make Alt + left-arrow behave like Ctrl + left-arrow: move one word backwards\n // http://unix.stackexchange.com/a/108106\n // macOS uses different escape sequences than linux\n if (result.key == C0.ESC + '[1;3D') {\n result.key = (this.browser.isMac) ? C0.ESC + 'b' : C0.ESC + '[1;5D';\n }\n } else if (this.applicationCursor) {\n result.key = C0.ESC + 'OD';\n } else {\n result.key = C0.ESC + '[D';\n }\n break;\n case 39:\n // right-arrow\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'C';\n // HACK: Make Alt + right-arrow behave like Ctrl + right-arrow: move one word forward\n // http://unix.stackexchange.com/a/108106\n // macOS uses different escape sequences than linux\n if (result.key == C0.ESC + '[1;3C') {\n result.key = (this.browser.isMac) ? C0.ESC + 'f' : C0.ESC + '[1;5C';\n }\n } else if (this.applicationCursor) {\n result.key = C0.ESC + 'OC';\n } else {\n result.key = C0.ESC + '[C';\n }\n break;\n case 38:\n // up-arrow\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'A';\n // HACK: Make Alt + up-arrow behave like Ctrl + up-arrow\n // http://unix.stackexchange.com/a/108106\n if (result.key == C0.ESC + '[1;3A') {\n result.key = C0.ESC + '[1;5A';\n }\n } else if (this.applicationCursor) {\n result.key = C0.ESC + 'OA';\n } else {\n result.key = C0.ESC + '[A';\n }\n break;\n case 40:\n // down-arrow\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'B';\n // HACK: Make Alt + down-arrow behave like Ctrl + down-arrow\n // http://unix.stackexchange.com/a/108106\n if (result.key == C0.ESC + '[1;3B') {\n result.key = C0.ESC + '[1;5B';\n }\n } else if (this.applicationCursor) {\n result.key = C0.ESC + 'OB';\n } else {\n result.key = C0.ESC + '[B';\n }\n break;\n case 45:\n // insert\n if (!ev.shiftKey && !ev.ctrlKey) {\n // or + are used to\n // copy-paste on some systems.\n result.key = C0.ESC + '[2~';\n }\n break;\n case 46:\n // delete\n if (modifiers) {\n result.key = C0.ESC + '[3;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[3~';\n }\n break;\n case 36:\n // home\n if (modifiers)\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'H';\n else if (this.applicationCursor)\n result.key = C0.ESC + 'OH';\n else\n result.key = C0.ESC + '[H';\n break;\n case 35:\n // end\n if (modifiers)\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'F';\n else if (this.applicationCursor)\n result.key = C0.ESC + 'OF';\n else\n result.key = C0.ESC + '[F';\n break;\n case 33:\n // page up\n if (ev.shiftKey) {\n result.scrollDisp = -(this.rows - 1);\n } else {\n result.key = C0.ESC + '[5~';\n }\n break;\n case 34:\n // page down\n if (ev.shiftKey) {\n result.scrollDisp = this.rows - 1;\n } else {\n result.key = C0.ESC + '[6~';\n }\n break;\n case 112:\n // F1-F12\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'P';\n } else {\n result.key = C0.ESC + 'OP';\n }\n break;\n case 113:\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'Q';\n } else {\n result.key = C0.ESC + 'OQ';\n }\n break;\n case 114:\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'R';\n } else {\n result.key = C0.ESC + 'OR';\n }\n break;\n case 115:\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'S';\n } else {\n result.key = C0.ESC + 'OS';\n }\n break;\n case 116:\n if (modifiers) {\n result.key = C0.ESC + '[15;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[15~';\n }\n break;\n case 117:\n if (modifiers) {\n result.key = C0.ESC + '[17;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[17~';\n }\n break;\n case 118:\n if (modifiers) {\n result.key = C0.ESC + '[18;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[18~';\n }\n break;\n case 119:\n if (modifiers) {\n result.key = C0.ESC + '[19;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[19~';\n }\n break;\n case 120:\n if (modifiers) {\n result.key = C0.ESC + '[20;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[20~';\n }\n break;\n case 121:\n if (modifiers) {\n result.key = C0.ESC + '[21;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[21~';\n }\n break;\n case 122:\n if (modifiers) {\n result.key = C0.ESC + '[23;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[23~';\n }\n break;\n case 123:\n if (modifiers) {\n result.key = C0.ESC + '[24;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[24~';\n }\n break;\n default:\n // a-z and space\n if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) {\n if (ev.keyCode >= 65 && ev.keyCode <= 90) {\n result.key = String.fromCharCode(ev.keyCode - 64);\n } else if (ev.keyCode === 32) {\n // NUL\n result.key = String.fromCharCode(0);\n } else if (ev.keyCode >= 51 && ev.keyCode <= 55) {\n // escape, file sep, group sep, record sep, unit sep\n result.key = String.fromCharCode(ev.keyCode - 51 + 27);\n } else if (ev.keyCode === 56) {\n // delete\n result.key = String.fromCharCode(127);\n } else if (ev.keyCode === 219) {\n // ^[ - Control Sequence Introducer (CSI)\n result.key = String.fromCharCode(27);\n } else if (ev.keyCode === 220) {\n // ^\\ - String Terminator (ST)\n result.key = String.fromCharCode(28);\n } else if (ev.keyCode === 221) {\n // ^] - Operating System Command (OSC)\n result.key = String.fromCharCode(29);\n }\n } else if (!this.browser.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) {\n // On Mac this is a third level shift. Use instead.\n if (ev.keyCode >= 65 && ev.keyCode <= 90) {\n result.key = C0.ESC + String.fromCharCode(ev.keyCode + 32);\n } else if (ev.keyCode === 192) {\n result.key = C0.ESC + '`';\n } else if (ev.keyCode >= 48 && ev.keyCode <= 57) {\n result.key = C0.ESC + (ev.keyCode - 48);\n }\n } else if (this.browser.isMac && !ev.altKey && !ev.ctrlKey && ev.metaKey) {\n if (ev.keyCode === 65) { // cmd + a\n this.selectAll();\n }\n }\n break;\n }\n\n return result;\n};\n\n/**\n * Set the G level of the terminal\n * @param g\n */\nTerminal.prototype.setgLevel = function(g) {\n this.glevel = g;\n this.charset = this.charsets[g];\n};\n\n/**\n * Set the charset for the given G level of the terminal\n * @param g\n * @param charset\n */\nTerminal.prototype.setgCharset = function(g, charset) {\n this.charsets[g] = charset;\n if (this.glevel === g) {\n this.charset = charset;\n }\n};\n\n/**\n * Handle a keypress event.\n * Key Resources:\n * - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent\n * @param {KeyboardEvent} ev The keypress event to be handled.\n */\nTerminal.prototype.keyPress = function(ev) {\n var key;\n\n if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) {\n return false;\n }\n\n this.cancel(ev);\n\n if (ev.charCode) {\n key = ev.charCode;\n } else if (ev.which == null) {\n key = ev.keyCode;\n } else if (ev.which !== 0 && ev.charCode !== 0) {\n key = ev.which;\n } else {\n return false;\n }\n\n if (!key || (\n (ev.altKey || ev.ctrlKey || ev.metaKey) && !isThirdLevelShift(this, ev)\n )) {\n return false;\n }\n\n key = String.fromCharCode(key);\n\n this.emit('keypress', key, ev);\n this.emit('key', key, ev);\n this.showCursor();\n this.handler(key);\n\n return true;\n};\n\n/**\n * Send data for handling to the terminal\n * @param {string} data\n */\nTerminal.prototype.send = function(data) {\n var self = this;\n\n if (!this.queue) {\n setTimeout(function() {\n self.handler(self.queue);\n self.queue = '';\n }, 1);\n }\n\n this.queue += data;\n};\n\n/**\n * Ring the bell.\n * Note: We could do sweet things with webaudio here\n */\nTerminal.prototype.bell = function() {\n if (!this.visualBell) return;\n var self = this;\n this.element.style.borderColor = 'white';\n setTimeout(function() {\n self.element.style.borderColor = '';\n }, 10);\n if (this.popOnBell) this.focus();\n};\n\n/**\n * Log the current state to the console.\n */\nTerminal.prototype.log = function() {\n if (!this.debug) return;\n if (!this.context.console || !this.context.console.log) return;\n var args = Array.prototype.slice.call(arguments);\n this.context.console.log.apply(this.context.console, args);\n};\n\n/**\n * Log the current state as error to the console.\n */\nTerminal.prototype.error = function() {\n if (!this.debug) return;\n if (!this.context.console || !this.context.console.error) return;\n var args = Array.prototype.slice.call(arguments);\n this.context.console.error.apply(this.context.console, args);\n};\n\n/**\n * Resizes the terminal.\n *\n * @param {number} x The number of columns to resize to.\n * @param {number} y The number of rows to resize to.\n */\nTerminal.prototype.resize = function(x, y) {\n if (isNaN(x) || isNaN(y)) {\n return;\n }\n\n if (y > this.getOption('scrollback')) {\n this.setOption('scrollback', y)\n }\n\n var line\n , el\n , i\n , j\n , ch\n , addToY;\n\n if (x === this.cols && y === this.rows) {\n // Check if we still need to measure the char size (fixes #785).\n if (!this.charMeasure.width || !this.charMeasure.height) {\n this.charMeasure.measure();\n }\n return;\n }\n\n if (x < 1) x = 1;\n if (y < 1) y = 1;\n\n this.buffers.resize(x, y);\n\n // Adjust rows in the DOM to accurately reflect the new dimensions\n while (this.children.length < y) {\n this.insertRow();\n }\n while (this.children.length > y) {\n el = this.children.shift();\n if (!el) continue;\n el.parentNode.removeChild(el);\n }\n\n this.cols = x;\n this.rows = y;\n this.setupStops(this.cols);\n\n this.charMeasure.measure();\n\n this.refresh(0, this.rows - 1);\n\n this.geometry = [this.cols, this.rows];\n this.emit('resize', {terminal: this, cols: x, rows: y});\n};\n\n/**\n * Updates the range of rows to refresh\n * @param {number} y The number of rows to refresh next.\n */\nTerminal.prototype.updateRange = function(y) {\n if (y < this.refreshStart) this.refreshStart = y;\n if (y > this.refreshEnd) this.refreshEnd = y;\n // if (y > this.refreshEnd) {\n // this.refreshEnd = y;\n // if (y > this.rows - 1) {\n // this.refreshEnd = this.rows - 1;\n // }\n // }\n};\n\n/**\n * Set the range of refreshing to the maximum value\n */\nTerminal.prototype.maxRange = function() {\n this.refreshStart = 0;\n this.refreshEnd = this.rows - 1;\n};\n\n\n\n/**\n * Setup the tab stops.\n * @param {number} i\n */\nTerminal.prototype.setupStops = function(i) {\n if (i != null) {\n if (!this.buffer.tabs[i]) {\n i = this.prevStop(i);\n }\n } else {\n this.buffer.tabs = {};\n i = 0;\n }\n\n for (; i < this.cols; i += this.getOption('tabStopWidth')) {\n this.buffer.tabs[i] = true;\n }\n};\n\n\n/**\n * Move the cursor to the previous tab stop from the given position (default is current).\n * @param {number} x The position to move the cursor to the previous tab stop.\n */\nTerminal.prototype.prevStop = function(x) {\n if (x == null) x = this.buffer.x;\n while (!this.buffer.tabs[--x] && x > 0);\n return x >= this.cols\n ? this.cols - 1\n : x < 0 ? 0 : x;\n};\n\n\n/**\n * Move the cursor one tab stop forward from the given position (default is current).\n * @param {number} x The position to move the cursor one tab stop forward.\n */\nTerminal.prototype.nextStop = function(x) {\n if (x == null) x = this.buffer.x;\n while (!this.buffer.tabs[++x] && x < this.cols);\n return x >= this.cols\n ? this.cols - 1\n : x < 0 ? 0 : x;\n};\n\n\n/**\n * Erase in the identified line everything from \"x\" to the end of the line (right).\n * @param {number} x The column from which to start erasing to the end of the line.\n * @param {number} y The line in which to operate.\n */\nTerminal.prototype.eraseRight = function(x, y) {\n var line = this.buffer.lines.get(this.buffer.ybase + y);\n if (!line) {\n return;\n }\n var ch = [this.eraseAttr(), ' ', 1]; // xterm\n for (; x < this.cols; x++) {\n line[x] = ch;\n }\n this.updateRange(y);\n};\n\n\n\n/**\n * Erase in the identified line everything from \"x\" to the start of the line (left).\n * @param {number} x The column from which to start erasing to the start of the line.\n * @param {number} y The line in which to operate.\n */\nTerminal.prototype.eraseLeft = function(x, y) {\n var line = this.buffer.lines.get(this.buffer.ybase + y);\n if (!line) {\n return;\n }\n var ch = [this.eraseAttr(), ' ', 1]; // xterm\n x++;\n while (x--) {\n line[x] = ch;\n }\n this.updateRange(y);\n};\n\n/**\n * Clears the entire buffer, making the prompt line the new first line.\n */\nTerminal.prototype.clear = function() {\n if (this.buffer.ybase === 0 && this.buffer.y === 0) {\n // Don't clear if it's already clear\n return;\n }\n this.buffer.lines.set(0, this.buffer.lines.get(this.buffer.ybase + this.buffer.y));\n this.buffer.lines.length = 1;\n this.buffer.ydisp = 0;\n this.buffer.ybase = 0;\n this.buffer.y = 0;\n for (var i = 1; i < this.rows; i++) {\n this.buffer.lines.push(this.blankLine());\n }\n this.refresh(0, this.rows - 1);\n this.emit('scroll', this.buffer.ydisp);\n};\n\n/**\n * Erase all content in the given line\n * @param {number} y The line to erase all of its contents.\n */\nTerminal.prototype.eraseLine = function(y) {\n this.eraseRight(0, y);\n};\n\n\n/**\n * Return the data array of a blank line\n * @param {number} cur First bunch of data for each \"blank\" character.\n * @param {boolean} isWrapped Whether the new line is wrapped from the previous line.\n */\nTerminal.prototype.blankLine = function(cur, isWrapped) {\n var attr = cur\n ? this.eraseAttr()\n : this.defAttr;\n\n var ch = [attr, ' ', 1] // width defaults to 1 halfwidth character\n , line = []\n , i = 0;\n\n // TODO: It is not ideal that this is a property on an array, a buffer line\n // class should be added that will hold this data and other useful functions.\n if (isWrapped) {\n line.isWrapped = isWrapped;\n }\n\n for (; i < this.cols; i++) {\n line[i] = ch;\n }\n\n return line;\n};\n\n\n/**\n * If cur return the back color xterm feature attribute. Else return defAttr.\n * @param {object} cur\n */\nTerminal.prototype.ch = function(cur) {\n return cur\n ? [this.eraseAttr(), ' ', 1]\n : [this.defAttr, ' ', 1];\n};\n\n\n/**\n * Evaluate if the current terminal is the given argument.\n * @param {object} term The terminal to evaluate\n */\nTerminal.prototype.is = function(term) {\n var name = this.termName;\n return (name + '').indexOf(term) === 0;\n};\n\n\n/**\n * Emit the 'data' event and populate the given data.\n * @param {string} data The data to populate in the event.\n */\nTerminal.prototype.handler = function(data) {\n // Prevents all events to pty process if stdin is disabled\n if (this.options.disableStdin) {\n return;\n }\n\n // Clear the selection if the selection manager is available and has an active selection\n if (this.selectionManager && this.selectionManager.hasSelection) {\n this.selectionManager.clearSelection();\n }\n\n // Input is being sent to the terminal, the terminal should focus the prompt.\n if (this.buffer.ybase !== this.buffer.ydisp) {\n this.scrollToBottom();\n }\n this.emit('data', data);\n};\n\n\n/**\n * Emit the 'title' event and populate the given title.\n * @param {string} title The title to populate in the event.\n */\nTerminal.prototype.handleTitle = function(title) {\n /**\n * This event is emitted when the title of the terminal is changed\n * from inside the terminal. The parameter is the new title.\n *\n * @event title\n */\n this.emit('title', title);\n};\n\n\n/**\n * ESC\n */\n\n/**\n * ESC D Index (IND is 0x84).\n */\nTerminal.prototype.index = function() {\n this.buffer.y++;\n if (this.buffer.y > this.buffer.scrollBottom) {\n this.buffer.y--;\n this.scroll();\n }\n // If the end of the line is hit, prevent this action from wrapping around to the next line.\n if (this.buffer.x >= this.cols) {\n this.buffer.x--;\n }\n};\n\n\n/**\n * ESC M Reverse Index (RI is 0x8d).\n *\n * Move the cursor up one row, inserting a new blank line if necessary.\n */\nTerminal.prototype.reverseIndex = function() {\n var j;\n if (this.buffer.y === this.buffer.scrollTop) {\n // possibly move the code below to term.reverseScroll();\n // test: echo -ne '\\e[1;1H\\e[44m\\eM\\e[0m'\n // blankLine(true) is xterm/linux behavior\n this.buffer.lines.shiftElements(this.buffer.y + this.buffer.ybase, this.rows - 1, 1);\n this.buffer.lines.set(this.buffer.y + this.buffer.ybase, this.blankLine(true));\n this.updateRange(this.buffer.scrollTop);\n this.updateRange(this.buffer.scrollBottom);\n } else {\n this.buffer.y--;\n }\n};\n\n\n/**\n * ESC c Full Reset (RIS).\n */\nTerminal.prototype.reset = function() {\n this.options.rows = this.rows;\n this.options.cols = this.cols;\n var customKeyEventHandler = this.customKeyEventHandler;\n var cursorBlinkInterval = this.cursorBlinkInterval;\n var inputHandler = this.inputHandler;\n Terminal.call(this, this.options);\n this.customKeyEventHandler = customKeyEventHandler;\n this.cursorBlinkInterval = cursorBlinkInterval;\n this.inputHandler = inputHandler;\n this.refresh(0, this.rows - 1);\n this.viewport.syncScrollArea();\n};\n\n\n/**\n * ESC H Tab Set (HTS is 0x88).\n */\nTerminal.prototype.tabSet = function() {\n this.buffer.tabs[this.buffer.x] = true;\n};\n\n/**\n * Helpers\n */\n\nfunction on(el, type, handler, capture) {\n if (!Array.isArray(el)) {\n el = [el];\n }\n el.forEach(function (element) {\n element.addEventListener(type, handler, capture || false);\n });\n}\n\nfunction off(el, type, handler, capture) {\n el.removeEventListener(type, handler, capture || false);\n}\n\nfunction cancel(ev, force) {\n if (!this.cancelEvents && !force) {\n return;\n }\n ev.preventDefault();\n ev.stopPropagation();\n return false;\n}\n\nfunction inherits(child, parent) {\n function f() {\n this.constructor = child;\n }\n f.prototype = parent.prototype;\n child.prototype = new f;\n}\n\nfunction indexOf(obj, el) {\n var i = obj.length;\n while (i--) {\n if (obj[i] === el) return i;\n }\n return -1;\n}\n\nfunction isThirdLevelShift(term, ev) {\n var thirdLevelKey =\n (term.browser.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) ||\n (term.browser.isMSWindows && ev.altKey && ev.ctrlKey && !ev.metaKey);\n\n if (ev.type == 'keypress') {\n return thirdLevelKey;\n }\n\n // Don't invoke for arrows, pageDown, home, backspace, etc. (on non-keypress events)\n return thirdLevelKey && (!ev.keyCode || ev.keyCode > 47);\n}\n\n// Expose to InputHandler (temporary)\nTerminal.prototype.matchColor = matchColor;\n\nfunction matchColor(r1, g1, b1) {\n var hash = (r1 << 16) | (g1 << 8) | b1;\n\n if (matchColor._cache[hash] != null) {\n return matchColor._cache[hash];\n }\n\n var ldiff = Infinity\n , li = -1\n , i = 0\n , c\n , r2\n , g2\n , b2\n , diff;\n\n for (; i < Terminal.vcolors.length; i++) {\n c = Terminal.vcolors[i];\n r2 = c[0];\n g2 = c[1];\n b2 = c[2];\n\n diff = matchColor.distance(r1, g1, b1, r2, g2, b2);\n\n if (diff === 0) {\n li = i;\n break;\n }\n\n if (diff < ldiff) {\n ldiff = diff;\n li = i;\n }\n }\n\n return matchColor._cache[hash] = li;\n}\n\nmatchColor._cache = {};\n\n// http://stackoverflow.com/questions/1633828\nmatchColor.distance = function(r1, g1, b1, r2, g2, b2) {\n return Math.pow(30 * (r1 - r2), 2)\n + Math.pow(59 * (g1 - g2), 2)\n + Math.pow(11 * (b1 - b2), 2);\n};\n\nfunction each(obj, iter, con) {\n if (obj.forEach) return obj.forEach(iter, con);\n for (var i = 0; i < obj.length; i++) {\n iter.call(con, obj[i], i, obj);\n }\n}\n\nfunction wasMondifierKeyOnlyEvent(ev) {\n return ev.keyCode === 16 || // Shift\n ev.keyCode === 17 || // Ctrl\n ev.keyCode === 18; // Alt\n}\n\nfunction keys(obj) {\n if (Object.keys) return Object.keys(obj);\n var key, keys = [];\n for (key in obj) {\n if (Object.prototype.hasOwnProperty.call(obj, key)) {\n keys.push(key);\n }\n }\n return keys;\n}\n\n/**\n * Expose\n */\n\nTerminal.translateBufferLineToString = translateBufferLineToString;\nTerminal.EventEmitter = EventEmitter;\nTerminal.inherits = inherits;\n\n/**\n * Adds an event listener to the terminal.\n *\n * @param {string} event The name of the event. TODO: Document all event types\n * @param {function} callback The function to call when the event is triggered.\n */\nTerminal.on = on;\nTerminal.off = off;\nTerminal.cancel = cancel;\n\nmodule.exports = Terminal;\n","/**\n * @license MIT\n */\n\nimport { CharMeasure } from './CharMeasure';\n\nexport function getCoordsRelativeToElement(event: MouseEvent, element: HTMLElement): [number, number] {\n // Ignore browsers that don't support MouseEvent.pageX\n if (event.pageX == null) {\n return null;\n }\n\n let x = event.pageX;\n let y = event.pageY;\n\n // Converts the coordinates from being relative to the document to being\n // relative to the terminal.\n while (element && element !== self.document.documentElement) {\n x -= element.offsetLeft;\n y -= element.offsetTop;\n element = 'offsetParent' in element ? element.offsetParent : element.parentElement;\n }\n return [x, y];\n}\n\n/**\n * Gets coordinates within the terminal for a particular mouse event. The result\n * is returned as an array in the form [x, y] instead of an object as it's a\n * little faster and this function is used in some low level code.\n * @param event The mouse event.\n * @param rowContainer The terminal's row container.\n * @param charMeasure The char measure object used to determine character sizes.\n * @param colCount The number of columns in the terminal.\n * @param rowCount The number of rows n the terminal.\n * @param isSelection Whether the request is for the selection or not. This will\n * apply an offset to the x value such that the left half of the cell will\n * select that cell and the right half will select the next cell.\n */\nexport function getCoords(event: MouseEvent, rowContainer: HTMLElement, charMeasure: CharMeasure, colCount: number, rowCount: number, isSelection?: boolean): [number, number] {\n // Coordinates cannot be measured if charMeasure has not been initialized\n if (!charMeasure.width || !charMeasure.height) {\n return null;\n }\n\n const coords = getCoordsRelativeToElement(event, rowContainer);\n if (!coords) {\n return null;\n }\n\n // Convert to cols/rows.\n coords[0] = Math.ceil((coords[0] + (isSelection ? charMeasure.width / 2 : 0)) / charMeasure.width);\n coords[1] = Math.ceil(coords[1] / charMeasure.height);\n\n // Ensure coordinates are within the terminal viewport.\n coords[0] = Math.min(Math.max(coords[0], 1), colCount + 1);\n coords[1] = Math.min(Math.max(coords[1], 1), rowCount + 1);\n\n return coords;\n}\n\n/**\n * Gets coordinates within the terminal for a particular mouse event, wrapping\n * them to the bounds of the terminal and adding 32 to both the x and y values\n * as expected by xterm.\n * @param event The mouse event.\n * @param rowContainer The terminal's row container.\n * @param charMeasure The char measure object used to determine character sizes.\n * @param colCount The number of columns in the terminal.\n * @param rowCount The number of rows in the terminal.\n */\nexport function getRawByteCoords(event: MouseEvent, rowContainer: HTMLElement, charMeasure: CharMeasure, colCount: number, rowCount: number): { x: number, y: number } {\n const coords = getCoords(event, rowContainer, charMeasure, colCount, rowCount);\n let x = coords[0];\n let y = coords[1];\n\n // xterm sends raw bytes and starts at 32 (SP) for each.\n x += 32;\n y += 32;\n\n return { x, y };\n}\n","/**\n * Generic utilities module with methods that can be helpful at different parts of the code base.\n * @module xterm/utils/Generic\n * @license MIT\n */\n\n/**\n * Return if the given array contains the given element\n * @param {Array} array The array to search for the given element.\n * @param {Object} el The element to look for into the array\n */\nexport function contains(arr: any[], el: any) {\n return arr.indexOf(el) >= 0;\n};\n","/**\n * @module xterm/utils/DomElementObjectPool\n * @license MIT\n */\n\n/**\n * An object pool that manages acquisition and releasing of DOM elements for\n * when reuse is desirable.\n */\nexport class DomElementObjectPool {\n private static readonly OBJECT_ID_ATTRIBUTE = 'data-obj-id';\n\n private static _objectCount = 0;\n\n private _type: string;\n private _pool: HTMLElement[];\n private _inUse: {[key: string]: HTMLElement};\n\n /**\n * @param type The DOM element type (div, span, etc.).\n */\n constructor(private type: string) {\n this._type = type;\n this._pool = [];\n this._inUse = {};\n }\n\n /**\n * Acquire an element from the pool, creating it if the pool is empty.\n */\n public acquire(): HTMLElement {\n let element: HTMLElement;\n if (this._pool.length === 0) {\n element = this._createNew();\n } else {\n element = this._pool.pop();\n }\n this._inUse[element.getAttribute(DomElementObjectPool.OBJECT_ID_ATTRIBUTE)] = element;\n return element;\n }\n\n /**\n * Release an element back into the pool. It's up to the caller of this\n * function to ensure that all external references to the element have been\n * removed.\n * @param element The element being released.\n */\n public release(element: HTMLElement): void {\n if (!this._inUse[element.getAttribute(DomElementObjectPool.OBJECT_ID_ATTRIBUTE)]) {\n throw new Error('Could not release an element not yet acquired');\n }\n delete this._inUse[element.getAttribute(DomElementObjectPool.OBJECT_ID_ATTRIBUTE)];\n this._cleanElement(element);\n this._pool.push(element);\n }\n\n /**\n * Creates a new element for the pool.\n */\n private _createNew(): HTMLElement {\n const element = document.createElement(this._type);\n const id = DomElementObjectPool._objectCount++;\n element.setAttribute(DomElementObjectPool.OBJECT_ID_ATTRIBUTE, id.toString(10));\n return element;\n }\n\n /**\n * Resets an element back to a \"clean state\".\n * @param element The element to be cleaned.\n */\n private _cleanElement(element: HTMLElement): void {\n element.className = '';\n element.innerHTML = '';\n }\n}\n","/**\n * Represents a circular list; a list with a maximum size that wraps around when push is called,\n * overriding values at the start of the list.\n * @module xterm/utils/CircularList\n * @license MIT\n */\nimport { EventEmitter } from '../EventEmitter';\nimport { ICircularList } from '../Interfaces';\n\nexport class CircularList extends EventEmitter implements ICircularList {\n private _array: T[];\n private _startIndex: number;\n private _length: number;\n\n constructor(maxLength: number) {\n super();\n this._array = new Array(maxLength);\n this._startIndex = 0;\n this._length = 0;\n }\n\n public get maxLength(): number {\n return this._array.length;\n }\n\n public set maxLength(newMaxLength: number) {\n // Reconstruct array, starting at index 0. Only transfer values from the\n // indexes 0 to length.\n let newArray = new Array(newMaxLength);\n for (let i = 0; i < Math.min(newMaxLength, this.length); i++) {\n newArray[i] = this._array[this._getCyclicIndex(i)];\n }\n this._array = newArray;\n this._startIndex = 0;\n }\n\n public get length(): number {\n return this._length;\n }\n\n public set length(newLength: number) {\n if (newLength > this._length) {\n for (let i = this._length; i < newLength; i++) {\n this._array[i] = undefined;\n }\n }\n this._length = newLength;\n }\n\n public get forEach(): (callbackfn: (value: T, index: number) => void) => void {\n return (callbackfn: (value: T, index: number) => void) => {\n let i = 0;\n let length = this.length;\n for (let i = 0; i < length; i++) {\n callbackfn(this.get(i), i);\n }\n };\n }\n\n /**\n * Gets the value at an index.\n *\n * Note that for performance reasons there is no bounds checking here, the index reference is\n * circular so this should always return a value and never throw.\n * @param index The index of the value to get.\n * @return The value corresponding to the index.\n */\n public get(index: number): T {\n return this._array[this._getCyclicIndex(index)];\n }\n\n /**\n * Sets the value at an index.\n *\n * Note that for performance reasons there is no bounds checking here, the index reference is\n * circular so this should always return a value and never throw.\n * @param index The index to set.\n * @param value The value to set.\n */\n public set(index: number, value: T): void {\n this._array[this._getCyclicIndex(index)] = value;\n }\n\n /**\n * Pushes a new value onto the list, wrapping around to the start of the array, overriding index 0\n * if the maximum length is reached.\n * @param value The value to push onto the list.\n */\n public push(value: T): void {\n this._array[this._getCyclicIndex(this._length)] = value;\n if (this._length === this.maxLength) {\n this._startIndex++;\n if (this._startIndex === this.maxLength) {\n this._startIndex = 0;\n }\n this.emit('trim', 1);\n } else {\n this._length++;\n }\n }\n\n /**\n * Removes and returns the last value on the list.\n * @return The popped value.\n */\n public pop(): T {\n return this._array[this._getCyclicIndex(this._length-- - 1)];\n }\n\n /**\n * Deletes and/or inserts items at a particular index (in that order). Unlike\n * Array.prototype.splice, this operation does not return the deleted items as a new array in\n * order to save creating a new array. Note that this operation may shift all values in the list\n * in the worst case.\n * @param start The index to delete and/or insert.\n * @param deleteCount The number of elements to delete.\n * @param items The items to insert.\n */\n public splice(start: number, deleteCount: number, ...items: T[]): void {\n // Delete items\n if (deleteCount) {\n for (let i = start; i < this._length - deleteCount; i++) {\n this._array[this._getCyclicIndex(i)] = this._array[this._getCyclicIndex(i + deleteCount)];\n }\n this._length -= deleteCount;\n }\n\n if (items && items.length) {\n // Add items\n for (let i = this._length - 1; i >= start; i--) {\n this._array[this._getCyclicIndex(i + items.length)] = this._array[this._getCyclicIndex(i)];\n }\n for (let i = 0; i < items.length; i++) {\n this._array[this._getCyclicIndex(start + i)] = items[i];\n }\n\n // Adjust length as needed\n if (this._length + items.length > this.maxLength) {\n const countToTrim = (this._length + items.length) - this.maxLength;\n this._startIndex += countToTrim;\n this._length = this.maxLength;\n this.emit('trim', countToTrim);\n } else {\n this._length += items.length;\n }\n }\n }\n\n /**\n * Trims a number of items from the start of the list.\n * @param count The number of items to remove.\n */\n public trimStart(count: number): void {\n if (count > this._length) {\n count = this._length;\n }\n this._startIndex += count;\n this._length -= count;\n this.emit('trim', count);\n }\n\n public shiftElements(start: number, count: number, offset: number): void {\n if (count <= 0) {\n return;\n }\n if (start < 0 || start >= this._length) {\n throw new Error('start argument out of range');\n }\n if (start + offset < 0) {\n throw new Error('Cannot shift elements in list beyond index 0');\n }\n\n if (offset > 0) {\n for (let i = count - 1; i >= 0; i--) {\n this.set(start + i + offset, this.get(start + i));\n }\n const expandListBy = (start + count + offset) - this._length;\n if (expandListBy > 0) {\n this._length += expandListBy;\n while (this._length > this.maxLength) {\n this._length--;\n this._startIndex++;\n this.emit('trim', 1);\n }\n }\n } else {\n for (let i = 0; i < count; i++) {\n this.set(start + i + offset, this.get(start + i));\n }\n }\n }\n\n /**\n * Gets the cyclic index for the specified regular index. The cyclic index can then be used on the\n * backing array to get the element associated with the regular index.\n * @param index The regular index.\n * @returns The cyclic index.\n */\n private _getCyclicIndex(index: number): number {\n return (this._startIndex + index) % this.maxLength;\n }\n}\n","/**\n * @module xterm/utils/CharMeasure\n * @license MIT\n */\n\nimport { EventEmitter } from '../EventEmitter.js';\n\n/**\n * Utility class that measures the size of a character.\n */\nexport class CharMeasure extends EventEmitter {\n private _document: Document;\n private _parentElement: HTMLElement;\n private _measureElement: HTMLElement;\n private _width: number;\n private _height: number;\n\n constructor(document: Document, parentElement: HTMLElement) {\n super();\n this._document = document;\n this._parentElement = parentElement;\n }\n\n public get width(): number {\n return this._width;\n }\n\n public get height(): number {\n return this._height;\n }\n\n public measure(): void {\n if (!this._measureElement) {\n this._measureElement = this._document.createElement('span');\n this._measureElement.style.position = 'absolute';\n this._measureElement.style.top = '0';\n this._measureElement.style.left = '-9999em';\n this._measureElement.textContent = 'W';\n this._measureElement.setAttribute('aria-hidden', 'true');\n this._parentElement.appendChild(this._measureElement);\n // Perform _doMeasure async if the element was just attached as sometimes\n // getBoundingClientRect does not return accurate values without this.\n setTimeout(() => this._doMeasure(), 0);\n } else {\n this._doMeasure();\n }\n }\n\n private _doMeasure(): void {\n const geometry = this._measureElement.getBoundingClientRect();\n // The element is likely currently display:none, we should retain the\n // previous value.\n if (geometry.width === 0 || geometry.height === 0) {\n return;\n }\n if (this._width !== geometry.width || this._height !== geometry.height) {\n this._width = geometry.width;\n this._height = geometry.height;\n this.emit('charsizechanged');\n }\n }\n}\n","/**\n * @license MIT\n */\n\n// TODO: This module should be merged into a buffer or buffer line class\n\nconst LINE_DATA_CHAR_INDEX = 1;\nconst LINE_DATA_WIDTH_INDEX = 2;\n\n/**\n * Translates a buffer line to a string, with optional start and end columns.\n * Wide characters will count as two columns in the resulting string. This\n * function is useful for getting the actual text underneath the raw selection\n * position.\n * @param line The line being translated.\n * @param trimRight Whether to trim whitespace to the right.\n * @param startCol The column to start at.\n * @param endCol The column to end at.\n */\nexport function translateBufferLineToString(line: any, trimRight: boolean, startCol: number = 0, endCol: number = null): string {\n // Get full line\n let lineString = '';\n let widthAdjustedStartCol = startCol;\n let widthAdjustedEndCol = endCol;\n for (let i = 0; i < line.length; i++) {\n const char = line[i];\n lineString += char[LINE_DATA_CHAR_INDEX];\n // Adjust start and end cols for wide characters if they affect their\n // column indexes\n if (char[LINE_DATA_WIDTH_INDEX] === 0) {\n if (startCol >= i) {\n widthAdjustedStartCol--;\n }\n if (endCol >= i) {\n widthAdjustedEndCol--;\n }\n }\n }\n\n // Calculate the final end col by trimming whitespace on the right of the\n // line if needed.\n let finalEndCol = widthAdjustedEndCol || line.length;\n if (trimRight) {\n const rightWhitespaceIndex = lineString.search(/\\s+$/);\n if (rightWhitespaceIndex !== -1) {\n finalEndCol = Math.min(finalEndCol, rightWhitespaceIndex);\n }\n // Return the empty string if only trimmed whitespace is selected\n if (finalEndCol <= widthAdjustedStartCol) {\n return '';\n }\n }\n\n return lineString.substring(widthAdjustedStartCol, finalEndCol);\n}\n","/**\n * Attributes and methods to help with identifying the current browser and platform.\n * @module xterm/utils/Browser\n * @license MIT\n */\n\nimport { contains } from './Generic';\n\nconst isNode = (typeof navigator === 'undefined') ? true : false;\nconst userAgent = (isNode) ? 'node' : navigator.userAgent;\nconst platform = (isNode) ? 'node' : navigator.platform;\n\nexport const isFirefox = !!~userAgent.indexOf('Firefox');\nexport const isMSIE = !!~userAgent.indexOf('MSIE') || !!~userAgent.indexOf('Trident');\n\n// Find the users platform. We use this to interpret the meta key\n// and ISO third level shifts.\n// http://stackoverflow.com/q/19877924/577598\nexport const isMac = contains(['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'], platform);\nexport const isIpad = platform === 'iPad';\nexport const isIphone = platform === 'iPhone';\nexport const isMSWindows = contains(['Windows', 'Win16', 'Win32', 'WinCE'], platform);\nexport const isLinux = platform.indexOf('Linux') >= 0;\n","/**\n * Clipboard handler module: exports methods for handling all clipboard-related events in the\n * terminal.\n * @module xterm/handlers/Clipboard\n * @license MIT\n */\n\nimport { ITerminal, ISelectionManager } from '../Interfaces';\n\ninterface IWindow extends Window {\n clipboardData?: {\n getData(format: string): string;\n setData(format: string, data: string);\n };\n}\n\ndeclare var window: IWindow;\n\n/**\n * Prepares text to be pasted into the terminal by normalizing the line endings\n * @param text The pasted text that needs processing before inserting into the terminal\n */\nexport function prepareTextForTerminal(text: string, isMSWindows: boolean): string {\n if (isMSWindows) {\n return text.replace(/\\r?\\n/g, '\\r');\n }\n return text;\n}\n\n/**\n * Binds copy functionality to the given terminal.\n * @param {ClipboardEvent} ev The original copy event to be handled\n */\nexport function copyHandler(ev: ClipboardEvent, term: ITerminal, selectionManager: ISelectionManager) {\n if (term.browser.isMSIE) {\n window.clipboardData.setData('Text', selectionManager.selectionText);\n } else {\n ev.clipboardData.setData('text/plain', selectionManager.selectionText);\n }\n\n // Prevent or the original text will be copied.\n ev.preventDefault();\n}\n\n/**\n * Redirect the clipboard's data to the terminal's input handler.\n * @param {ClipboardEvent} ev The original paste event to be handled\n * @param {Terminal} term The terminal on which to apply the handled paste event\n */\nexport function pasteHandler(ev: ClipboardEvent, term: ITerminal) {\n ev.stopPropagation();\n\n let text: string;\n\n let dispatchPaste = function(text) {\n text = prepareTextForTerminal(text, term.browser.isMSWindows);\n term.handler(text);\n term.textarea.value = '';\n term.emit('paste', text);\n\n return term.cancel(ev);\n };\n\n if (term.browser.isMSIE) {\n if (window.clipboardData) {\n text = window.clipboardData.getData('Text');\n dispatchPaste(text);\n }\n } else {\n if (ev.clipboardData) {\n text = ev.clipboardData.getData('text/plain');\n dispatchPaste(text);\n }\n }\n}\n\n/**\n * Moves the textarea under the mouse cursor and focuses it.\n * @param ev The original right click event to be handled.\n * @param textarea The terminal's textarea.\n */\nexport function moveTextAreaUnderMouseCursor(ev: MouseEvent, textarea: HTMLTextAreaElement) {\n // Bring textarea at the cursor position\n textarea.style.position = 'fixed';\n textarea.style.width = '20px';\n textarea.style.height = '20px';\n textarea.style.left = (ev.clientX - 10) + 'px';\n textarea.style.top = (ev.clientY - 10) + 'px';\n textarea.style.zIndex = '1000';\n\n textarea.focus();\n\n // Reset the terminal textarea's styling\n setTimeout(function () {\n textarea.style.position = null;\n textarea.style.width = null;\n textarea.style.height = null;\n textarea.style.left = null;\n textarea.style.top = null;\n textarea.style.zIndex = null;\n }, 4);\n}\n\n/**\n * Bind to right-click event and allow right-click copy and paste.\n * @param ev The original right click event to be handled.\n * @param textarea The terminal's textarea.\n * @param selectionManager The terminal's selection manager.\n */\nexport function rightClickHandler(ev: MouseEvent, textarea: HTMLTextAreaElement, selectionManager: ISelectionManager) {\n moveTextAreaUnderMouseCursor(ev, textarea);\n\n // Get textarea ready to copy from the context menu\n textarea.value = selectionManager.selectionText;\n textarea.select();\n}\n","/**\n * @license MIT\n */\n\nimport { ITerminal } from './Interfaces';\nimport { CharMeasure } from './utils/CharMeasure';\n\n/**\n * Represents the viewport of a terminal, the visible area within the larger buffer of output.\n * Logic for the virtual scroll bar is included in this object.\n */\nexport class Viewport {\n private currentRowHeight: number;\n private lastRecordedBufferLength: number;\n private lastRecordedViewportHeight: number;\n private lastTouchY: number;\n\n /**\n * Creates a new Viewport.\n * @param terminal The terminal this viewport belongs to.\n * @param viewportElement The DOM element acting as the viewport.\n * @param scrollArea The DOM element acting as the scroll area.\n * @param charMeasure A DOM element used to measure the character size of. the terminal.\n */\n constructor(\n private terminal: ITerminal,\n private viewportElement: HTMLElement,\n private scrollArea: HTMLElement,\n private charMeasure: CharMeasure\n ) {\n this.currentRowHeight = 0;\n this.lastRecordedBufferLength = 0;\n this.lastRecordedViewportHeight = 0;\n\n this.terminal.on('scroll', this.syncScrollArea.bind(this));\n this.terminal.on('resize', this.syncScrollArea.bind(this));\n this.viewportElement.addEventListener('scroll', this.onScroll.bind(this));\n\n // Perform this async to ensure the CharMeasure is ready.\n setTimeout(() => this.syncScrollArea(), 0);\n }\n\n /**\n * Refreshes row height, setting line-height, viewport height and scroll area height if\n * necessary.\n */\n private refresh(): void {\n if (this.charMeasure.height > 0) {\n const rowHeightChanged = this.charMeasure.height !== this.currentRowHeight;\n if (rowHeightChanged) {\n this.currentRowHeight = this.charMeasure.height;\n this.viewportElement.style.lineHeight = this.charMeasure.height + 'px';\n this.terminal.rowContainer.style.lineHeight = this.charMeasure.height + 'px';\n }\n const viewportHeightChanged = this.lastRecordedViewportHeight !== this.terminal.rows;\n if (rowHeightChanged || viewportHeightChanged) {\n this.lastRecordedViewportHeight = this.terminal.rows;\n this.viewportElement.style.height = this.charMeasure.height * this.terminal.rows + 'px';\n this.terminal.selectionContainer.style.height = this.viewportElement.style.height;\n }\n this.scrollArea.style.height = (this.charMeasure.height * this.lastRecordedBufferLength) + 'px';\n }\n }\n\n /**\n * Updates dimensions and synchronizes the scroll area if necessary.\n */\n public syncScrollArea(): void {\n if (this.lastRecordedBufferLength !== this.terminal.buffer.lines.length) {\n // If buffer height changed\n this.lastRecordedBufferLength = this.terminal.buffer.lines.length;\n this.refresh();\n } else if (this.lastRecordedViewportHeight !== this.terminal.rows) {\n // If viewport height changed\n this.refresh();\n } else {\n // If size has changed, refresh viewport\n if (this.charMeasure.height !== this.currentRowHeight) {\n this.refresh();\n }\n }\n\n // Sync scrollTop\n const scrollTop = this.terminal.buffer.ydisp * this.currentRowHeight;\n if (this.viewportElement.scrollTop !== scrollTop) {\n this.viewportElement.scrollTop = scrollTop;\n }\n }\n\n /**\n * Handles scroll events on the viewport, calculating the new viewport and requesting the\n * terminal to scroll to it.\n * @param ev The scroll event.\n */\n private onScroll(ev: Event) {\n const newRow = Math.round(this.viewportElement.scrollTop / this.currentRowHeight);\n const diff = newRow - this.terminal.buffer.ydisp;\n this.terminal.scrollDisp(diff, true);\n }\n\n /**\n * Handles mouse wheel events by adjusting the viewport's scrollTop and delegating the actual\n * scrolling to `onScroll`, this event needs to be attached manually by the consumer of\n * `Viewport`.\n * @param ev The mouse wheel event.\n */\n public onWheel(ev: WheelEvent) {\n if (ev.deltaY === 0) {\n // Do nothing if it's not a vertical scroll event\n return;\n }\n // Fallback to WheelEvent.DOM_DELTA_PIXEL\n let multiplier = 1;\n if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) {\n multiplier = this.currentRowHeight;\n } else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) {\n multiplier = this.currentRowHeight * this.terminal.rows;\n }\n this.viewportElement.scrollTop += ev.deltaY * multiplier;\n // Prevent the page from scrolling when the terminal scrolls\n ev.preventDefault();\n };\n\n /**\n * Handles the touchstart event, recording the touch occurred.\n * @param ev The touch event.\n */\n public onTouchStart(ev: TouchEvent) {\n this.lastTouchY = ev.touches[0].pageY;\n };\n\n /**\n * Handles the touchmove event, scrolling the viewport if the position shifted.\n * @param ev The touch event.\n */\n public onTouchMove(ev: TouchEvent) {\n let deltaY = this.lastTouchY - ev.touches[0].pageY;\n this.lastTouchY = ev.touches[0].pageY;\n if (deltaY === 0) {\n return;\n }\n this.viewportElement.scrollTop += deltaY;\n ev.preventDefault();\n };\n}\n","/**\n * @license MIT\n */\n\nimport { ITerminal } from './Interfaces';\n\n/**\n * Represents a selection within the buffer. This model only cares about column\n * and row coordinates, not wide characters.\n */\nexport class SelectionModel {\n /**\n * Whether select all is currently active.\n */\n public isSelectAllActive: boolean;\n\n /**\n * The [x, y] position the selection starts at.\n */\n public selectionStart: [number, number];\n\n /**\n * The minimal length of the selection from the start position. When double\n * clicking on a word, the word will be selected which makes the selection\n * start at the start of the word and makes this variable the length.\n */\n public selectionStartLength: number;\n\n /**\n * The [x, y] position the selection ends at.\n */\n public selectionEnd: [number, number];\n\n constructor(\n private _terminal: ITerminal\n ) {\n this.clearSelection();\n }\n\n /**\n * Clears the current selection.\n */\n public clearSelection(): void {\n this.selectionStart = null;\n this.selectionEnd = null;\n this.isSelectAllActive = false;\n this.selectionStartLength = 0;\n }\n\n /**\n * The final selection start, taking into consideration select all.\n */\n public get finalSelectionStart(): [number, number] {\n if (this.isSelectAllActive) {\n return [0, 0];\n }\n\n if (!this.selectionEnd || !this.selectionStart) {\n return this.selectionStart;\n }\n\n return this.areSelectionValuesReversed() ? this.selectionEnd : this.selectionStart;\n }\n\n /**\n * The final selection end, taking into consideration select all, double click\n * word selection and triple click line selection.\n */\n public get finalSelectionEnd(): [number, number] {\n if (this.isSelectAllActive) {\n return [this._terminal.cols, this._terminal.buffer.ybase + this._terminal.rows - 1];\n }\n\n if (!this.selectionStart) {\n return null;\n }\n\n // Use the selection start if the end doesn't exist or they're reversed\n if (!this.selectionEnd || this.areSelectionValuesReversed()) {\n return [this.selectionStart[0] + this.selectionStartLength, this.selectionStart[1]];\n }\n\n // Ensure the the word/line is selected after a double/triple click\n if (this.selectionStartLength) {\n // Select the larger of the two when start and end are on the same line\n if (this.selectionEnd[1] === this.selectionStart[1]) {\n return [Math.max(this.selectionStart[0] + this.selectionStartLength, this.selectionEnd[0]), this.selectionEnd[1]];\n }\n }\n return this.selectionEnd;\n }\n\n /**\n * Returns whether the selection start and end are reversed.\n */\n public areSelectionValuesReversed(): boolean {\n const start = this.selectionStart;\n const end = this.selectionEnd;\n return start[1] > end[1] || (start[1] === end[1] && start[0] > end[0]);\n }\n\n /**\n * Handle the buffer being trimmed, adjust the selection position.\n * @param amount The amount the buffer is being trimmed.\n * @return Whether a refresh is necessary.\n */\n public onTrim(amount: number): boolean {\n // Adjust the selection position based on the trimmed amount.\n if (this.selectionStart) {\n this.selectionStart[1] -= amount;\n }\n if (this.selectionEnd) {\n this.selectionEnd[1] -= amount;\n }\n\n // The selection has moved off the buffer, clear it.\n if (this.selectionEnd && this.selectionEnd[1] < 0) {\n this.clearSelection();\n return true;\n }\n\n // If the selection start is trimmed, ensure the start column is 0.\n if (this.selectionStart && this.selectionStart[1] < 0) {\n this.selectionStart[1] = 0;\n }\n return false;\n }\n}\n","/**\n * @license MIT\n */\n\nimport * as Mouse from './utils/Mouse';\nimport * as Browser from './utils/Browser';\nimport { CharMeasure } from './utils/CharMeasure';\nimport { CircularList } from './utils/CircularList';\nimport { EventEmitter } from './EventEmitter';\nimport { ITerminal, ICircularList } from './Interfaces';\nimport { SelectionModel } from './SelectionModel';\nimport { translateBufferLineToString } from './utils/BufferLine';\n\n/**\n * The number of pixels the mouse needs to be above or below the viewport in\n * order to scroll at the maximum speed.\n */\nconst DRAG_SCROLL_MAX_THRESHOLD = 50;\n\n/**\n * The maximum scrolling speed\n */\nconst DRAG_SCROLL_MAX_SPEED = 15;\n\n/**\n * The number of milliseconds between drag scroll updates.\n */\nconst DRAG_SCROLL_INTERVAL = 50;\n\n/**\n * A string containing all characters that are considered word separated by the\n * double click to select work logic.\n */\nconst WORD_SEPARATORS = ' ()[]{}\\'\"';\n\n// TODO: Move these constants elsewhere, they belong in a buffer or buffer\n// data/line class.\nconst LINE_DATA_CHAR_INDEX = 1;\nconst LINE_DATA_WIDTH_INDEX = 2;\n\nconst NON_BREAKING_SPACE_CHAR = String.fromCharCode(160);\nconst ALL_NON_BREAKING_SPACE_REGEX = new RegExp(NON_BREAKING_SPACE_CHAR, 'g');\n\n/**\n * Represents a position of a word on a line.\n */\ninterface IWordPosition {\n start: number;\n length: number;\n}\n\n/**\n * A selection mode, this drives how the selection behaves on mouse move.\n */\nenum SelectionMode {\n NORMAL,\n WORD,\n LINE\n}\n\n/**\n * A class that manages the selection of the terminal. With help from\n * SelectionModel, SelectionManager handles with all logic associated with\n * dealing with the selection, including handling mouse interaction, wide\n * characters and fetching the actual text within the selection. Rendering is\n * not handled by the SelectionManager but a 'refresh' event is fired when the\n * selection is ready to be redrawn.\n */\nexport class SelectionManager extends EventEmitter {\n protected _model: SelectionModel;\n\n /**\n * The amount to scroll every drag scroll update (depends on how far the mouse\n * drag is above or below the terminal).\n */\n private _dragScrollAmount: number;\n\n /**\n * The current selection mode.\n */\n private _activeSelectionMode: SelectionMode;\n\n /**\n * A setInterval timer that is active while the mouse is down whose callback\n * scrolls the viewport when necessary.\n */\n private _dragScrollIntervalTimer: NodeJS.Timer;\n\n /**\n * The animation frame ID used for refreshing the selection.\n */\n private _refreshAnimationFrame: number;\n\n /**\n * Whether selection is enabled.\n */\n private _enabled = true;\n\n private _mouseMoveListener: EventListener;\n private _mouseUpListener: EventListener;\n\n constructor(\n private _terminal: ITerminal,\n private _buffer: ICircularList<[number, string, number][]>,\n private _rowContainer: HTMLElement,\n private _charMeasure: CharMeasure\n ) {\n super();\n this._initListeners();\n this.enable();\n\n this._model = new SelectionModel(_terminal);\n this._activeSelectionMode = SelectionMode.NORMAL;\n }\n\n /**\n * Initializes listener variables.\n */\n private _initListeners() {\n this._mouseMoveListener = event => this._onMouseMove(event);\n this._mouseUpListener = event => this._onMouseUp(event);\n\n this._rowContainer.addEventListener('mousedown', event => this._onMouseDown(event));\n\n // Only adjust the selection on trim, shiftElements is rarely used (only in\n // reverseIndex) and delete in a splice is only ever used when the same\n // number of elements was just added. Given this is could actually be\n // beneficial to leave the selection as is for these cases.\n this._buffer.on('trim', (amount: number) => this._onTrim(amount));\n }\n\n /**\n * Disables the selection manager. This is useful for when terminal mouse\n * are enabled.\n */\n public disable() {\n this.clearSelection();\n this._enabled = false;\n }\n\n /**\n * Enable the selection manager.\n */\n public enable() {\n this._enabled = true;\n }\n\n /**\n * Sets the active buffer, this should be called when the alt buffer is\n * switched in or out.\n * @param buffer The active buffer.\n */\n public setBuffer(buffer: ICircularList<[number, string, number][]>): void {\n this._buffer = buffer;\n this.clearSelection();\n }\n\n public get selectionStart(): [number, number] { return this._model.finalSelectionStart; }\n public get selectionEnd(): [number, number] { return this._model.finalSelectionEnd; }\n\n /**\n * Gets whether there is an active text selection.\n */\n public get hasSelection(): boolean {\n const start = this._model.finalSelectionStart;\n const end = this._model.finalSelectionEnd;\n if (!start || !end) {\n return false;\n }\n return start[0] !== end[0] || start[1] !== end[1];\n }\n\n /**\n * Gets the text currently selected.\n */\n public get selectionText(): string {\n const start = this._model.finalSelectionStart;\n const end = this._model.finalSelectionEnd;\n if (!start || !end) {\n return '';\n }\n\n // Get first row\n const startRowEndCol = start[1] === end[1] ? end[0] : null;\n let result: string[] = [];\n result.push(translateBufferLineToString(this._buffer.get(start[1]), true, start[0], startRowEndCol));\n\n // Get middle rows\n for (let i = start[1] + 1; i <= end[1] - 1; i++) {\n const bufferLine = this._buffer.get(i);\n const lineText = translateBufferLineToString(bufferLine, true);\n if ((bufferLine).isWrapped) {\n result[result.length - 1] += lineText;\n } else {\n result.push(lineText);\n }\n }\n\n // Get final row\n if (start[1] !== end[1]) {\n const bufferLine = this._buffer.get(end[1]);\n const lineText = translateBufferLineToString(bufferLine, true, 0, end[0]);\n if ((bufferLine).isWrapped) {\n result[result.length - 1] += lineText;\n } else {\n result.push(lineText);\n }\n }\n\n // Format string by replacing non-breaking space chars with regular spaces\n // and joining the array into a multi-line string.\n const formattedResult = result.map(line => {\n return line.replace(ALL_NON_BREAKING_SPACE_REGEX, ' ');\n }).join(Browser.isMSWindows ? '\\r\\n' : '\\n');\n\n return formattedResult;\n }\n\n /**\n * Clears the current terminal selection.\n */\n public clearSelection(): void {\n this._model.clearSelection();\n this._removeMouseDownListeners();\n this.refresh();\n }\n\n /**\n * Queues a refresh, redrawing the selection on the next opportunity.\n * @param isNewSelection Whether the selection should be registered as a new\n * selection on Linux.\n */\n public refresh(isNewSelection?: boolean): void {\n // Queue the refresh for the renderer\n if (!this._refreshAnimationFrame) {\n this._refreshAnimationFrame = window.requestAnimationFrame(() => this._refresh());\n }\n\n // If the platform is Linux and the refresh call comes from a mouse event,\n // we need to update the selection for middle click to paste selection.\n if (Browser.isLinux && isNewSelection) {\n const selectionText = this.selectionText;\n if (selectionText.length) {\n this.emit('newselection', this.selectionText);\n }\n }\n }\n\n /**\n * Fires the refresh event, causing consumers to pick it up and redraw the\n * selection state.\n */\n private _refresh(): void {\n this._refreshAnimationFrame = null;\n this.emit('refresh', { start: this._model.finalSelectionStart, end: this._model.finalSelectionEnd });\n }\n\n /**\n * Selects all text within the terminal.\n */\n public selectAll(): void {\n this._model.isSelectAllActive = true;\n this.refresh();\n }\n\n /**\n * Handle the buffer being trimmed, adjust the selection position.\n * @param amount The amount the buffer is being trimmed.\n */\n private _onTrim(amount: number) {\n const needsRefresh = this._model.onTrim(amount);\n if (needsRefresh) {\n this.refresh();\n }\n }\n\n /**\n * Gets the 0-based [x, y] buffer coordinates of the current mouse event.\n * @param event The mouse event.\n */\n private _getMouseBufferCoords(event: MouseEvent): [number, number] {\n const coords = Mouse.getCoords(event, this._rowContainer, this._charMeasure, this._terminal.cols, this._terminal.rows, true);\n if (!coords) {\n return null;\n }\n\n // Convert to 0-based\n coords[0]--;\n coords[1]--;\n // Convert viewport coords to buffer coords\n coords[1] += this._terminal.buffer.ydisp;\n return coords;\n }\n\n /**\n * Gets the amount the viewport should be scrolled based on how far out of the\n * terminal the mouse is.\n * @param event The mouse event.\n */\n private _getMouseEventScrollAmount(event: MouseEvent): number {\n let offset = Mouse.getCoordsRelativeToElement(event, this._rowContainer)[1];\n const terminalHeight = this._terminal.rows * this._charMeasure.height;\n if (offset >= 0 && offset <= terminalHeight) {\n return 0;\n }\n if (offset > terminalHeight) {\n offset -= terminalHeight;\n }\n\n offset = Math.min(Math.max(offset, -DRAG_SCROLL_MAX_THRESHOLD), DRAG_SCROLL_MAX_THRESHOLD);\n offset /= DRAG_SCROLL_MAX_THRESHOLD;\n return (offset / Math.abs(offset)) + Math.round(offset * (DRAG_SCROLL_MAX_SPEED - 1));\n }\n\n /**\n * Handles te mousedown event, setting up for a new selection.\n * @param event The mousedown event.\n */\n private _onMouseDown(event: MouseEvent) {\n // If we have selection, we want the context menu on right click even if the\n // terminal is in mouse mode.\n if (event.button === 2 && this.hasSelection) {\n event.stopPropagation();\n return;\n }\n\n // Only action the primary button\n if (event.button !== 0) {\n return;\n }\n\n // Allow selection when using a specific modifier key, even when disabled\n if (!this._enabled) {\n const shouldForceSelection = Browser.isMac && event.altKey;\n\n if (!shouldForceSelection) {\n return;\n }\n\n // Don't send the mouse down event to the current process, we want to select\n event.stopPropagation();\n }\n\n // Tell the browser not to start a regular selection\n event.preventDefault();\n\n // Reset drag scroll state\n this._dragScrollAmount = 0;\n\n if (this._enabled && event.shiftKey) {\n this._onIncrementalClick(event);\n } else {\n if (event.detail === 1) {\n this._onSingleClick(event);\n } else if (event.detail === 2) {\n this._onDoubleClick(event);\n } else if (event.detail === 3) {\n this._onTripleClick(event);\n }\n }\n\n this._addMouseDownListeners();\n this.refresh(true);\n }\n\n /**\n * Adds listeners when mousedown is triggered.\n */\n private _addMouseDownListeners(): void {\n // Listen on the document so that dragging outside of viewport works\n this._rowContainer.ownerDocument.addEventListener('mousemove', this._mouseMoveListener);\n this._rowContainer.ownerDocument.addEventListener('mouseup', this._mouseUpListener);\n this._dragScrollIntervalTimer = setInterval(() => this._dragScroll(), DRAG_SCROLL_INTERVAL);\n }\n\n /**\n * Removes the listeners that are registered when mousedown is triggered.\n */\n private _removeMouseDownListeners(): void {\n this._rowContainer.ownerDocument.removeEventListener('mousemove', this._mouseMoveListener);\n this._rowContainer.ownerDocument.removeEventListener('mouseup', this._mouseUpListener);\n clearInterval(this._dragScrollIntervalTimer);\n this._dragScrollIntervalTimer = null;\n }\n\n /**\n * Performs an incremental click, setting the selection end position to the mouse\n * position.\n * @param event The mouse event.\n */\n private _onIncrementalClick(event: MouseEvent): void {\n if (this._model.selectionStart) {\n this._model.selectionEnd = this._getMouseBufferCoords(event);\n }\n }\n\n /**\n * Performs a single click, resetting relevant state and setting the selection\n * start position.\n * @param event The mouse event.\n */\n private _onSingleClick(event: MouseEvent): void {\n this._model.selectionStartLength = 0;\n this._model.isSelectAllActive = false;\n this._activeSelectionMode = SelectionMode.NORMAL;\n\n // Initialize the new selection\n this._model.selectionStart = this._getMouseBufferCoords(event);\n if (!this._model.selectionStart) {\n return;\n }\n this._model.selectionEnd = null;\n\n // Ensure the line exists\n const line = this._buffer.get(this._model.selectionStart[1]);\n if (!line) {\n return;\n }\n\n // If the mouse is over the second half of a wide character, adjust the\n // selection to cover the whole character\n const char = line[this._model.selectionStart[0]];\n if (char[LINE_DATA_WIDTH_INDEX] === 0) {\n this._model.selectionStart[0]++;\n }\n }\n\n /**\n * Performs a double click, selecting the current work.\n * @param event The mouse event.\n */\n private _onDoubleClick(event: MouseEvent): void {\n const coords = this._getMouseBufferCoords(event);\n if (coords) {\n this._activeSelectionMode = SelectionMode.WORD;\n this._selectWordAt(coords);\n }\n }\n\n /**\n * Performs a triple click, selecting the current line and activating line\n * select mode.\n * @param event The mouse event.\n */\n private _onTripleClick(event: MouseEvent): void {\n const coords = this._getMouseBufferCoords(event);\n if (coords) {\n this._activeSelectionMode = SelectionMode.LINE;\n this._selectLineAt(coords[1]);\n }\n }\n\n /**\n * Handles the mousemove event when the mouse button is down, recording the\n * end of the selection and refreshing the selection.\n * @param event The mousemove event.\n */\n private _onMouseMove(event: MouseEvent) {\n // Record the previous position so we know whether to redraw the selection\n // at the end.\n const previousSelectionEnd = this._model.selectionEnd ? [this._model.selectionEnd[0], this._model.selectionEnd[1]] : null;\n\n // Set the initial selection end based on the mouse coordinates\n this._model.selectionEnd = this._getMouseBufferCoords(event);\n if (!this._model.selectionEnd) {\n this.refresh(true);\n return;\n }\n\n // Select the entire line if line select mode is active.\n if (this._activeSelectionMode === SelectionMode.LINE) {\n if (this._model.selectionEnd[1] < this._model.selectionStart[1]) {\n this._model.selectionEnd[0] = 0;\n } else {\n this._model.selectionEnd[0] = this._terminal.cols;\n }\n } else if (this._activeSelectionMode === SelectionMode.WORD) {\n this._selectToWordAt(this._model.selectionEnd);\n }\n\n // Determine the amount of scrolling that will happen.\n this._dragScrollAmount = this._getMouseEventScrollAmount(event);\n\n // If the cursor was above or below the viewport, make sure it's at the\n // start or end of the viewport respectively.\n if (this._dragScrollAmount > 0) {\n this._model.selectionEnd[0] = this._terminal.cols - 1;\n } else if (this._dragScrollAmount < 0) {\n this._model.selectionEnd[0] = 0;\n }\n\n // If the character is a wide character include the cell to the right in the\n // selection. Note that selections at the very end of the line will never\n // have a character.\n if (this._model.selectionEnd[1] < this._buffer.length) {\n const char = this._buffer.get(this._model.selectionEnd[1])[this._model.selectionEnd[0]];\n if (char && char[2] === 0) {\n this._model.selectionEnd[0]++;\n }\n }\n\n // Only draw here if the selection changes.\n if (!previousSelectionEnd ||\n previousSelectionEnd[0] !== this._model.selectionEnd[0] ||\n previousSelectionEnd[1] !== this._model.selectionEnd[1]) {\n this.refresh(true);\n }\n }\n\n /**\n * The callback that occurs every DRAG_SCROLL_INTERVAL ms that does the\n * scrolling of the viewport.\n */\n private _dragScroll() {\n if (this._dragScrollAmount) {\n this._terminal.scrollDisp(this._dragScrollAmount, false);\n // Re-evaluate selection\n if (this._dragScrollAmount > 0) {\n this._model.selectionEnd = [this._terminal.cols - 1, this._terminal.buffer.ydisp + this._terminal.rows];\n } else {\n this._model.selectionEnd = [0, this._terminal.buffer.ydisp];\n }\n this.refresh();\n }\n }\n\n /**\n * Handles the mouseup event, removing the mousedown listeners.\n * @param event The mouseup event.\n */\n private _onMouseUp(event: MouseEvent) {\n this._removeMouseDownListeners();\n }\n\n /**\n * Converts a viewport column to the character index on the buffer line, the\n * latter takes into account wide characters.\n * @param coords The coordinates to find the 2 index for.\n */\n private _convertViewportColToCharacterIndex(bufferLine: any, coords: [number, number]): number {\n let charIndex = coords[0];\n for (let i = 0; coords[0] >= i; i++) {\n const char = bufferLine[i];\n if (char[LINE_DATA_WIDTH_INDEX] === 0) {\n charIndex--;\n }\n }\n return charIndex;\n }\n\n public setSelection(col: number, row: number, length: number): void {\n this._model.clearSelection();\n this._removeMouseDownListeners();\n this._model.selectionStart = [col, row];\n this._model.selectionStartLength = length;\n this.refresh();\n }\n\n /**\n * Gets positional information for the word at the coordinated specified.\n * @param coords The coordinates to get the word at.\n */\n private _getWordAt(coords: [number, number]): IWordPosition {\n const bufferLine = this._buffer.get(coords[1]);\n if (!bufferLine) {\n return null;\n }\n\n const line = translateBufferLineToString(bufferLine, false);\n\n // Get actual index, taking into consideration wide characters\n let endIndex = this._convertViewportColToCharacterIndex(bufferLine, coords);\n let startIndex = endIndex;\n\n // Record offset to be used later\n const charOffset = coords[0] - startIndex;\n let leftWideCharCount = 0;\n let rightWideCharCount = 0;\n\n if (line.charAt(startIndex) === ' ') {\n // Expand until non-whitespace is hit\n while (startIndex > 0 && line.charAt(startIndex - 1) === ' ') {\n startIndex--;\n }\n while (endIndex < line.length && line.charAt(endIndex + 1) === ' ') {\n endIndex++;\n }\n } else {\n // Expand until whitespace is hit. This algorithm works by scanning left\n // and right from the starting position, keeping both the index format\n // (line) and the column format (bufferLine) in sync. When a wide\n // character is hit, it is recorded and the column index is adjusted.\n let startCol = coords[0];\n let endCol = coords[0];\n // Consider the initial position, skip it and increment the wide char\n // variable\n if (bufferLine[startCol][LINE_DATA_WIDTH_INDEX] === 0) {\n leftWideCharCount++;\n startCol--;\n }\n if (bufferLine[endCol][LINE_DATA_WIDTH_INDEX] === 2) {\n rightWideCharCount++;\n endCol++;\n }\n // Expand the string in both directions until a space is hit\n while (startIndex > 0 && !this._isCharWordSeparator(line.charAt(startIndex - 1))) {\n if (bufferLine[startCol - 1][LINE_DATA_WIDTH_INDEX] === 0) {\n // If the next character is a wide char, record it and skip the column\n leftWideCharCount++;\n startCol--;\n }\n startIndex--;\n startCol--;\n }\n while (endIndex + 1 < line.length && !this._isCharWordSeparator(line.charAt(endIndex + 1))) {\n if (bufferLine[endCol + 1][LINE_DATA_WIDTH_INDEX] === 2) {\n // If the next character is a wide char, record it and skip the column\n rightWideCharCount++;\n endCol++;\n }\n endIndex++;\n endCol++;\n }\n }\n\n const start = startIndex + charOffset - leftWideCharCount;\n const length = Math.min(endIndex - startIndex + leftWideCharCount + rightWideCharCount + 1/*include endIndex char*/, this._terminal.cols);\n return { start, length };\n }\n\n /**\n * Selects the word at the coordinates specified.\n * @param coords The coordinates to get the word at.\n */\n protected _selectWordAt(coords: [number, number]): void {\n const wordPosition = this._getWordAt(coords);\n if (wordPosition) {\n this._model.selectionStart = [wordPosition.start, coords[1]];\n this._model.selectionStartLength = wordPosition.length;\n }\n }\n\n /**\n * Sets the selection end to the word at the coordinated specified.\n * @param coords The coordinates to get the word at.\n */\n private _selectToWordAt(coords: [number, number]): void {\n const wordPosition = this._getWordAt(coords);\n if (wordPosition) {\n this._model.selectionEnd = [this._model.areSelectionValuesReversed() ? wordPosition.start : (wordPosition.start + wordPosition.length), coords[1]];\n }\n }\n\n /**\n * Gets whether the character is considered a word separator by the select\n * word logic.\n * @param char The character to check.\n */\n private _isCharWordSeparator(char: string): boolean {\n return WORD_SEPARATORS.indexOf(char) >= 0;\n }\n\n /**\n * Selects the line specified.\n * @param line The line index.\n */\n protected _selectLineAt(line: number): void {\n this._model.selectionStart = [0, line];\n this._model.selectionStartLength = this._terminal.cols;\n }\n}\n","/**\n * @license MIT\n */\n\nimport { ITerminal } from './Interfaces';\nimport { DomElementObjectPool } from './utils/DomElementObjectPool';\n\n/**\n * The maximum number of refresh frames to skip when the write buffer is non-\n * empty. Note that these frames may be intermingled with frames that are\n * skipped via requestAnimationFrame's mechanism.\n */\nconst MAX_REFRESH_FRAME_SKIP = 5;\n\n/**\n * Flags used to render terminal text properly.\n */\nenum FLAGS {\n BOLD = 1,\n UNDERLINE = 2,\n BLINK = 4,\n INVERSE = 8,\n INVISIBLE = 16\n};\n\nlet brokenBold: boolean = null;\n\nexport class Renderer {\n /** A queue of the rows to be refreshed */\n private _refreshRowsQueue: {start: number, end: number}[] = [];\n private _refreshFramesSkipped = 0;\n private _refreshAnimationFrame = null;\n\n private _spanElementObjectPool = new DomElementObjectPool('span');\n\n constructor(private _terminal: ITerminal) {\n // Figure out whether boldness affects\n // the character width of monospace fonts.\n if (brokenBold === null) {\n brokenBold = checkBoldBroken((this._terminal).element);\n }\n this._spanElementObjectPool = new DomElementObjectPool('span');\n\n // TODO: Pull more DOM interactions into Renderer.constructor, element for\n // example should be owned by Renderer (and also exposed by Terminal due to\n // to established public API).\n }\n\n /**\n * Queues a refresh between two rows (inclusive), to be done on next animation\n * frame.\n * @param {number} start The start row.\n * @param {number} end The end row.\n */\n public queueRefresh(start: number, end: number): void {\n this._refreshRowsQueue.push({ start: start, end: end });\n if (!this._refreshAnimationFrame) {\n this._refreshAnimationFrame = window.requestAnimationFrame(this._refreshLoop.bind(this));\n }\n }\n\n /**\n * Performs the refresh loop callback, calling refresh only if a refresh is\n * necessary before queueing up the next one.\n */\n private _refreshLoop(): void {\n // Skip MAX_REFRESH_FRAME_SKIP frames if the writeBuffer is non-empty as it\n // will need to be immediately refreshed anyway. This saves a lot of\n // rendering time as the viewport DOM does not need to be refreshed, no\n // scroll events, no layouts, etc.\n const skipFrame = this._terminal.writeBuffer.length > 0 && this._refreshFramesSkipped++ <= MAX_REFRESH_FRAME_SKIP;\n if (skipFrame) {\n this._refreshAnimationFrame = window.requestAnimationFrame(this._refreshLoop.bind(this));\n return;\n }\n\n this._refreshFramesSkipped = 0;\n let start;\n let end;\n if (this._refreshRowsQueue.length > 4) {\n // Just do a full refresh when 5+ refreshes are queued\n start = 0;\n end = this._terminal.rows - 1;\n } else {\n // Get start and end rows that need refreshing\n start = this._refreshRowsQueue[0].start;\n end = this._refreshRowsQueue[0].end;\n for (let i = 1; i < this._refreshRowsQueue.length; i++) {\n if (this._refreshRowsQueue[i].start < start) {\n start = this._refreshRowsQueue[i].start;\n }\n if (this._refreshRowsQueue[i].end > end) {\n end = this._refreshRowsQueue[i].end;\n }\n }\n }\n this._refreshRowsQueue = [];\n this._refreshAnimationFrame = null;\n this._refresh(start, end);\n }\n\n /**\n * Refreshes (re-renders) terminal content within two rows (inclusive)\n *\n * Rendering Engine:\n *\n * In the screen buffer, each character is stored as a an array with a character\n * and a 32-bit integer:\n * - First value: a utf-16 character.\n * - Second value:\n * - Next 9 bits: background color (0-511).\n * - Next 9 bits: foreground color (0-511).\n * - Next 14 bits: a mask for misc. flags:\n * - 1=bold\n * - 2=underline\n * - 4=blink\n * - 8=inverse\n * - 16=invisible\n *\n * @param {number} start The row to start from (between 0 and terminal's height terminal - 1)\n * @param {number} end The row to end at (between fromRow and terminal's height terminal - 1)\n */\n private _refresh(start: number, end: number): void {\n // If this is a big refresh, remove the terminal rows from the DOM for faster calculations\n let parent;\n if (end - start >= this._terminal.rows / 2) {\n parent = this._terminal.element.parentNode;\n if (parent) {\n this._terminal.element.removeChild(this._terminal.rowContainer);\n }\n }\n\n let width = this._terminal.cols;\n let y = start;\n\n if (end >= this._terminal.rows) {\n this._terminal.log('`end` is too large. Most likely a bad CSR.');\n end = this._terminal.rows - 1;\n }\n\n for (; y <= end; y++) {\n let row = y + this._terminal.buffer.ydisp;\n\n let line = this._terminal.buffer.lines.get(row);\n\n let x;\n if (this._terminal.buffer.y === y - (this._terminal.buffer.ybase - this._terminal.buffer.ydisp) &&\n this._terminal.cursorState &&\n !this._terminal.cursorHidden) {\n x = this._terminal.buffer.x;\n } else {\n x = -1;\n }\n\n let attr = this._terminal.defAttr;\n\n const documentFragment = document.createDocumentFragment();\n let innerHTML = '';\n let currentElement;\n\n // Return the row's spans to the pool\n while (this._terminal.children[y].children.length) {\n const child = this._terminal.children[y].children[0];\n this._terminal.children[y].removeChild(child);\n this._spanElementObjectPool.release(child);\n }\n\n for (let i = 0; i < width; i++) {\n // TODO: Could data be a more specific type?\n let data: any = line[i][0];\n const ch = line[i][1];\n const ch_width: any = line[i][2];\n const isCursor: boolean = i === x;\n if (!ch_width) {\n continue;\n }\n\n if (data !== attr || isCursor) {\n if (attr !== this._terminal.defAttr && !isCursor) {\n if (innerHTML) {\n currentElement.innerHTML = innerHTML;\n innerHTML = '';\n }\n documentFragment.appendChild(currentElement);\n currentElement = null;\n }\n if (data !== this._terminal.defAttr || isCursor) {\n if (innerHTML && !currentElement) {\n currentElement = this._spanElementObjectPool.acquire();\n }\n if (currentElement) {\n if (innerHTML) {\n currentElement.innerHTML = innerHTML;\n innerHTML = '';\n }\n documentFragment.appendChild(currentElement);\n }\n currentElement = this._spanElementObjectPool.acquire();\n\n let bg = data & 0x1ff;\n let fg = (data >> 9) & 0x1ff;\n let flags = data >> 18;\n\n if (isCursor) {\n currentElement.classList.add('reverse-video');\n currentElement.classList.add('terminal-cursor');\n }\n\n if (flags & FLAGS.BOLD) {\n if (!brokenBold) {\n currentElement.classList.add('xterm-bold');\n }\n // See: XTerm*boldColors\n if (fg < 8) {\n fg += 8;\n }\n }\n\n if (flags & FLAGS.UNDERLINE) {\n currentElement.classList.add('xterm-underline');\n }\n\n if (flags & FLAGS.BLINK) {\n currentElement.classList.add('xterm-blink');\n }\n\n // If inverse flag is on, then swap the foreground and background variables.\n if (flags & FLAGS.INVERSE) {\n let temp = bg;\n bg = fg;\n fg = temp;\n // Should inverse just be before the above boldColors effect instead?\n if ((flags & 1) && fg < 8) {\n fg += 8;\n }\n }\n\n if (flags & FLAGS.INVISIBLE && !isCursor) {\n currentElement.classList.add('xterm-hidden');\n }\n\n /**\n * Weird situation: Invert flag used black foreground and white background results\n * in invalid background color, positioned at the 256 index of the 256 terminal\n * color map. Pin the colors manually in such a case.\n *\n * Source: https://github.com/sourcelair/xterm.js/issues/57\n */\n if (flags & FLAGS.INVERSE) {\n if (bg === 257) {\n bg = 15;\n }\n if (fg === 256) {\n fg = 0;\n }\n }\n\n if (bg < 256) {\n currentElement.classList.add(`xterm-bg-color-${bg}`);\n }\n\n if (fg < 256) {\n currentElement.classList.add(`xterm-color-${fg}`);\n }\n\n }\n }\n\n if (ch_width === 2) {\n // Wrap wide characters so they're sized correctly. It's more difficult to release these\n // from the object pool so just create new ones via innerHTML.\n innerHTML += `${ch}`;\n } else if (ch.charCodeAt(0) > 255) {\n // Wrap any non-wide unicode character as some fonts size them badly\n innerHTML += `${ch}`;\n } else {\n switch (ch) {\n case '&':\n innerHTML += '&';\n break;\n case '<':\n innerHTML += '<';\n break;\n case '>':\n innerHTML += '>';\n break;\n default:\n if (ch <= ' ') {\n innerHTML += ' ';\n } else {\n innerHTML += ch;\n }\n break;\n }\n }\n\n // The cursor needs its own element, therefore we set attr to -1\n // which will cause the next character to be rendered in a new element\n attr = isCursor ? -1 : data;\n\n }\n\n if (innerHTML && !currentElement) {\n currentElement = this._spanElementObjectPool.acquire();\n }\n if (currentElement) {\n if (innerHTML) {\n currentElement.innerHTML = innerHTML;\n innerHTML = '';\n }\n documentFragment.appendChild(currentElement);\n currentElement = null;\n }\n\n this._terminal.children[y].appendChild(documentFragment);\n }\n\n if (parent) {\n this._terminal.element.appendChild(this._terminal.rowContainer);\n }\n\n this._terminal.emit('refresh', {element: this._terminal.element, start: start, end: end});\n };\n\n /**\n * Refreshes the selection in the DOM.\n * @param start The selection start.\n * @param end The selection end.\n */\n public refreshSelection(start: [number, number], end: [number, number]) {\n // Remove all selections\n while (this._terminal.selectionContainer.children.length) {\n this._terminal.selectionContainer.removeChild(this._terminal.selectionContainer.children[0]);\n }\n\n // Selection does not exist\n if (!start || !end) {\n return;\n }\n\n // Translate from buffer position to viewport position\n const viewportStartRow = start[1] - this._terminal.buffer.ydisp;\n const viewportEndRow = end[1] - this._terminal.buffer.ydisp;\n const viewportCappedStartRow = Math.max(viewportStartRow, 0);\n const viewportCappedEndRow = Math.min(viewportEndRow, this._terminal.rows - 1);\n\n // No need to draw the selection\n if (viewportCappedStartRow >= this._terminal.rows || viewportCappedEndRow < 0) {\n return;\n }\n\n // Create the selections\n const documentFragment = document.createDocumentFragment();\n // Draw first row\n const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0;\n const endCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : this._terminal.cols;\n documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow, startCol, endCol));\n // Draw middle rows\n const middleRowsCount = viewportCappedEndRow - viewportCappedStartRow - 1;\n documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow + 1, 0, this._terminal.cols, middleRowsCount));\n // Draw final row\n if (viewportCappedStartRow !== viewportCappedEndRow) {\n // Only draw viewportEndRow if it's not the same as viewporttartRow\n const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : this._terminal.cols;\n documentFragment.appendChild(this._createSelectionElement(viewportCappedEndRow, 0, endCol));\n }\n this._terminal.selectionContainer.appendChild(documentFragment);\n }\n\n /**\n * Creates a selection element at the specified position.\n * @param row The row of the selection.\n * @param colStart The start column.\n * @param colEnd The end columns.\n */\n private _createSelectionElement(row: number, colStart: number, colEnd: number, rowCount: number = 1): HTMLElement {\n const element = document.createElement('div');\n element.style.height = `${rowCount * this._terminal.charMeasure.height}px`;\n element.style.top = `${row * this._terminal.charMeasure.height}px`;\n element.style.left = `${colStart * this._terminal.charMeasure.width}px`;\n element.style.width = `${this._terminal.charMeasure.width * (colEnd - colStart)}px`;\n return element;\n }\n}\n\n\n// If bold is broken, we can't use it in the terminal.\nfunction checkBoldBroken(terminal) {\n const document = terminal.ownerDocument;\n const el = document.createElement('span');\n el.innerHTML = 'hello world';\n terminal.appendChild(el);\n const w1 = el.offsetWidth;\n const h1 = el.offsetHeight;\n el.style.fontWeight = 'bold';\n const w2 = el.offsetWidth;\n const h2 = el.offsetHeight;\n terminal.removeChild(el);\n return w1 !== w2 || h1 !== h2;\n}\n","/**\n * @license MIT\n */\n\nimport { C0 } from './EscapeSequences';\nimport { IInputHandler } from './Interfaces';\nimport { CHARSETS, DEFAULT_CHARSET } from './Charsets';\n\nconst normalStateHandler: {[key: string]: (parser: Parser, handler: IInputHandler) => void} = {};\nnormalStateHandler[C0.BEL] = (parser, handler) => handler.bell();\nnormalStateHandler[C0.LF] = (parser, handler) => handler.lineFeed();\nnormalStateHandler[C0.VT] = normalStateHandler[C0.LF];\nnormalStateHandler[C0.FF] = normalStateHandler[C0.LF];\nnormalStateHandler[C0.CR] = (parser, handler) => handler.carriageReturn();\nnormalStateHandler[C0.BS] = (parser, handler) => handler.backspace();\nnormalStateHandler[C0.HT] = (parser, handler) => handler.tab();\nnormalStateHandler[C0.SO] = (parser, handler) => handler.shiftOut();\nnormalStateHandler[C0.SI] = (parser, handler) => handler.shiftIn();\nnormalStateHandler[C0.ESC] = (parser, handler) => parser.setState(ParserState.ESCAPED);\n\n// TODO: Remove terminal when parser owns params and currentParam\nconst escapedStateHandler: {[key: string]: (parser: Parser, terminal: any) => void} = {};\nescapedStateHandler['['] = (parser, terminal) => {\n // ESC [ Control Sequence Introducer (CSI is 0x9b)\n terminal.params = [];\n terminal.currentParam = 0;\n parser.setState(ParserState.CSI_PARAM);\n};\nescapedStateHandler[']'] = (parser, terminal) => {\n // ESC ] Operating System Command (OSC is 0x9d)\n terminal.params = [];\n terminal.currentParam = 0;\n parser.setState(ParserState.OSC);\n};\nescapedStateHandler['P'] = (parser, terminal) => {\n // ESC P Device Control String (DCS is 0x90)\n terminal.params = [];\n terminal.currentParam = 0;\n parser.setState(ParserState.DCS);\n};\nescapedStateHandler['_'] = (parser, terminal) => {\n // ESC _ Application Program Command ( APC is 0x9f).\n parser.setState(ParserState.IGNORE);\n};\nescapedStateHandler['^'] = (parser, terminal) => {\n // ESC ^ Privacy Message ( PM is 0x9e).\n parser.setState(ParserState.IGNORE);\n};\nescapedStateHandler['c'] = (parser, terminal) => {\n // ESC c Full Reset (RIS).\n terminal.reset();\n};\nescapedStateHandler['E'] = (parser, terminal) => {\n // ESC E Next Line ( NEL is 0x85).\n terminal.buffer.x = 0;\n terminal.index();\n parser.setState(ParserState.NORMAL);\n};\nescapedStateHandler['D'] = (parser, terminal) => {\n // ESC D Index ( IND is 0x84).\n terminal.index();\n parser.setState(ParserState.NORMAL);\n};\nescapedStateHandler['M'] = (parser, terminal) => {\n // ESC M Reverse Index ( RI is 0x8d).\n terminal.reverseIndex();\n parser.setState(ParserState.NORMAL);\n};\nescapedStateHandler['%'] = (parser, terminal) => {\n // ESC % Select default/utf-8 character set.\n // @ = default, G = utf-8\n terminal.setgLevel(0);\n terminal.setgCharset(0, DEFAULT_CHARSET); // US (default)\n parser.setState(ParserState.NORMAL);\n parser.skipNextChar();\n};\nescapedStateHandler[C0.CAN] = (parser) => parser.setState(ParserState.NORMAL);\n\nconst csiParamStateHandler: {[key: string]: (parser: Parser) => void} = {};\ncsiParamStateHandler['?'] = (parser) => parser.setPrefix('?');\ncsiParamStateHandler['>'] = (parser) => parser.setPrefix('>');\ncsiParamStateHandler['!'] = (parser) => parser.setPrefix('!');\ncsiParamStateHandler['0'] = (parser) => parser.setParam(parser.getParam() * 10);\ncsiParamStateHandler['1'] = (parser) => parser.setParam(parser.getParam() * 10 + 1);\ncsiParamStateHandler['2'] = (parser) => parser.setParam(parser.getParam() * 10 + 2);\ncsiParamStateHandler['3'] = (parser) => parser.setParam(parser.getParam() * 10 + 3);\ncsiParamStateHandler['4'] = (parser) => parser.setParam(parser.getParam() * 10 + 4);\ncsiParamStateHandler['5'] = (parser) => parser.setParam(parser.getParam() * 10 + 5);\ncsiParamStateHandler['6'] = (parser) => parser.setParam(parser.getParam() * 10 + 6);\ncsiParamStateHandler['7'] = (parser) => parser.setParam(parser.getParam() * 10 + 7);\ncsiParamStateHandler['8'] = (parser) => parser.setParam(parser.getParam() * 10 + 8);\ncsiParamStateHandler['9'] = (parser) => parser.setParam(parser.getParam() * 10 + 9);\ncsiParamStateHandler['$'] = (parser) => parser.setPostfix('$');\ncsiParamStateHandler['\"'] = (parser) => parser.setPostfix('\"');\ncsiParamStateHandler[' '] = (parser) => parser.setPostfix(' ');\ncsiParamStateHandler['\\''] = (parser) => parser.setPostfix('\\'');\ncsiParamStateHandler[';'] = (parser) => parser.finalizeParam();\ncsiParamStateHandler[C0.CAN] = (parser) => parser.setState(ParserState.NORMAL);\n\nconst csiStateHandler: {[key: string]: (handler: IInputHandler, params: number[], prefix: string, postfix: string, parser: Parser) => void} = {};\ncsiStateHandler['@'] = (handler, params, prefix) => handler.insertChars(params);\ncsiStateHandler['A'] = (handler, params, prefix) => handler.cursorUp(params);\ncsiStateHandler['B'] = (handler, params, prefix) => handler.cursorDown(params);\ncsiStateHandler['C'] = (handler, params, prefix) => handler.cursorForward(params);\ncsiStateHandler['D'] = (handler, params, prefix) => handler.cursorBackward(params);\ncsiStateHandler['E'] = (handler, params, prefix) => handler.cursorNextLine(params);\ncsiStateHandler['F'] = (handler, params, prefix) => handler.cursorPrecedingLine(params);\ncsiStateHandler['G'] = (handler, params, prefix) => handler.cursorCharAbsolute(params);\ncsiStateHandler['H'] = (handler, params, prefix) => handler.cursorPosition(params);\ncsiStateHandler['I'] = (handler, params, prefix) => handler.cursorForwardTab(params);\ncsiStateHandler['J'] = (handler, params, prefix) => handler.eraseInDisplay(params);\ncsiStateHandler['K'] = (handler, params, prefix) => handler.eraseInLine(params);\ncsiStateHandler['L'] = (handler, params, prefix) => handler.insertLines(params);\ncsiStateHandler['M'] = (handler, params, prefix) => handler.deleteLines(params);\ncsiStateHandler['P'] = (handler, params, prefix) => handler.deleteChars(params);\ncsiStateHandler['S'] = (handler, params, prefix) => handler.scrollUp(params);\ncsiStateHandler['T'] = (handler, params, prefix) => {\n if (params.length < 2 && !prefix) {\n handler.scrollDown(params);\n }\n};\ncsiStateHandler['X'] = (handler, params, prefix) => handler.eraseChars(params);\ncsiStateHandler['Z'] = (handler, params, prefix) => handler.cursorBackwardTab(params);\ncsiStateHandler['`'] = (handler, params, prefix) => handler.charPosAbsolute(params);\ncsiStateHandler['a'] = (handler, params, prefix) => handler.HPositionRelative(params);\ncsiStateHandler['b'] = (handler, params, prefix) => handler.repeatPrecedingCharacter(params);\ncsiStateHandler['c'] = (handler, params, prefix) => handler.sendDeviceAttributes(params);\ncsiStateHandler['d'] = (handler, params, prefix) => handler.linePosAbsolute(params);\ncsiStateHandler['e'] = (handler, params, prefix) => handler.VPositionRelative(params);\ncsiStateHandler['f'] = (handler, params, prefix) => handler.HVPosition(params);\ncsiStateHandler['g'] = (handler, params, prefix) => handler.tabClear(params);\ncsiStateHandler['h'] = (handler, params, prefix) => handler.setMode(params);\ncsiStateHandler['l'] = (handler, params, prefix) => handler.resetMode(params);\ncsiStateHandler['m'] = (handler, params, prefix) => handler.charAttributes(params);\ncsiStateHandler['n'] = (handler, params, prefix) => handler.deviceStatus(params);\ncsiStateHandler['p'] = (handler, params, prefix) => {\n switch (prefix) {\n case '!': handler.softReset(params); break;\n }\n};\ncsiStateHandler['q'] = (handler, params, prefix, postfix) => {\n if (postfix === ' ') {\n handler.setCursorStyle(params);\n }\n};\ncsiStateHandler['r'] = (handler, params) => handler.setScrollRegion(params);\ncsiStateHandler['s'] = (handler, params) => handler.saveCursor(params);\ncsiStateHandler['u'] = (handler, params) => handler.restoreCursor(params);\ncsiStateHandler[C0.CAN] = (handler, params, prefix, postfix, parser) => parser.setState(ParserState.NORMAL);\n\nenum ParserState {\n NORMAL = 0,\n ESCAPED = 1,\n CSI_PARAM = 2,\n CSI = 3,\n OSC = 4,\n CHARSET = 5,\n DCS = 6,\n IGNORE = 7\n}\n\n/**\n * The terminal's parser, all input into the terminal goes through the parser\n * which parses and defers the actual input handling the the IInputHandler\n * specified in the constructor.\n */\nexport class Parser {\n private _state: ParserState;\n private _position: number;\n\n // TODO: Remove terminal when handler can do everything\n constructor(\n private _inputHandler: IInputHandler,\n private _terminal: any\n ) {\n this._state = ParserState.NORMAL;\n }\n\n /**\n * Parse and handle data.\n *\n * @param data The data to parse.\n */\n public parse(data: string): ParserState {\n let l = data.length, j, cs, ch, code, low;\n\n if (this._terminal.debug) {\n this._terminal.log('data: ' + data);\n }\n\n this._position = 0;\n // apply leftover surrogate high from last write\n if (this._terminal.surrogate_high) {\n data = this._terminal.surrogate_high + data;\n this._terminal.surrogate_high = '';\n }\n\n for (; this._position < l; this._position++) {\n ch = data[this._position];\n\n // FIXME: higher chars than 0xa0 are not allowed in escape sequences\n // --> maybe move to default\n code = data.charCodeAt(this._position);\n if (0xD800 <= code && code <= 0xDBFF) {\n // we got a surrogate high\n // get surrogate low (next 2 bytes)\n low = data.charCodeAt(this._position + 1);\n if (isNaN(low)) {\n // end of data stream, save surrogate high\n this._terminal.surrogate_high = ch;\n continue;\n }\n code = ((code - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000;\n ch += data.charAt(this._position + 1);\n }\n // surrogate low - already handled above\n if (0xDC00 <= code && code <= 0xDFFF)\n continue;\n\n switch (this._state) {\n case ParserState.NORMAL:\n if (ch in normalStateHandler) {\n normalStateHandler[ch](this, this._inputHandler);\n } else {\n this._inputHandler.addChar(ch, code);\n }\n break;\n case ParserState.ESCAPED:\n if (ch in escapedStateHandler) {\n escapedStateHandler[ch](this, this._terminal);\n // Skip switch as it was just handled\n break;\n }\n switch (ch) {\n\n // ESC (,),*,+,-,. Designate G0-G2 Character Set.\n case '(': // <-- this seems to get all the attention\n case ')':\n case '*':\n case '+':\n case '-':\n case '.':\n switch (ch) {\n case '(':\n this._terminal.gcharset = 0;\n break;\n case ')':\n this._terminal.gcharset = 1;\n break;\n case '*':\n this._terminal.gcharset = 2;\n break;\n case '+':\n this._terminal.gcharset = 3;\n break;\n case '-':\n this._terminal.gcharset = 1;\n break;\n case '.':\n this._terminal.gcharset = 2;\n break;\n }\n this._state = ParserState.CHARSET;\n break;\n\n // Designate G3 Character Set (VT300).\n // A = ISO Latin-1 Supplemental.\n // Not implemented.\n case '/':\n this._terminal.gcharset = 3;\n this._state = ParserState.CHARSET;\n this._position--;\n break;\n\n // ESC N\n // Single Shift Select of G2 Character Set\n // ( SS2 is 0x8e). This affects next character only.\n case 'N':\n break;\n // ESC O\n // Single Shift Select of G3 Character Set\n // ( SS3 is 0x8f). This affects next character only.\n case 'O':\n break;\n // ESC n\n // Invoke the G2 Character Set as GL (LS2).\n case 'n':\n this._terminal.setgLevel(2);\n break;\n // ESC o\n // Invoke the G3 Character Set as GL (LS3).\n case 'o':\n this._terminal.setgLevel(3);\n break;\n // ESC |\n // Invoke the G3 Character Set as GR (LS3R).\n case '|':\n this._terminal.setgLevel(3);\n break;\n // ESC }\n // Invoke the G2 Character Set as GR (LS2R).\n case '}':\n this._terminal.setgLevel(2);\n break;\n // ESC ~\n // Invoke the G1 Character Set as GR (LS1R).\n case '~':\n this._terminal.setgLevel(1);\n break;\n\n // ESC 7 Save Cursor (DECSC).\n case '7':\n this._inputHandler.saveCursor();\n this._state = ParserState.NORMAL;\n break;\n\n // ESC 8 Restore Cursor (DECRC).\n case '8':\n this._inputHandler.restoreCursor();\n this._state = ParserState.NORMAL;\n break;\n\n // ESC # 3 DEC line height/width\n case '#':\n this._state = ParserState.NORMAL;\n this._position++;\n break;\n\n // ESC H Tab Set (HTS is 0x88).\n case 'H':\n this._terminal.tabSet();\n this._state = ParserState.NORMAL;\n break;\n\n // ESC = Application Keypad (DECKPAM).\n case '=':\n this._terminal.log('Serial port requested application keypad.');\n this._terminal.applicationKeypad = true;\n this._terminal.viewport.syncScrollArea();\n this._state = ParserState.NORMAL;\n break;\n\n // ESC > Normal Keypad (DECKPNM).\n case '>':\n this._terminal.log('Switching back to normal keypad.');\n this._terminal.applicationKeypad = false;\n this._terminal.viewport.syncScrollArea();\n this._state = ParserState.NORMAL;\n break;\n\n default:\n this._state = ParserState.NORMAL;\n this._terminal.error('Unknown ESC control: %s.', ch);\n break;\n }\n break;\n\n case ParserState.CHARSET:\n if (ch in CHARSETS) {\n cs = CHARSETS[ch];\n if (ch === '/') { // ISOLatin is actually /A\n this.skipNextChar();\n }\n } else {\n cs = DEFAULT_CHARSET;\n }\n this._terminal.setgCharset(this._terminal.gcharset, cs);\n this._terminal.gcharset = null;\n this._state = ParserState.NORMAL;\n break;\n\n case ParserState.OSC:\n // OSC Ps ; Pt ST\n // OSC Ps ; Pt BEL\n // Set Text Parameters.\n if (ch === C0.ESC || ch === C0.BEL) {\n if (ch === C0.ESC) this._position++;\n\n this._terminal.params.push(this._terminal.currentParam);\n\n switch (this._terminal.params[0]) {\n case 0:\n case 1:\n case 2:\n if (this._terminal.params[1]) {\n this._terminal.title = this._terminal.params[1];\n this._terminal.handleTitle(this._terminal.title);\n }\n break;\n case 3:\n // set X property\n break;\n case 4:\n case 5:\n // change dynamic colors\n break;\n case 10:\n case 11:\n case 12:\n case 13:\n case 14:\n case 15:\n case 16:\n case 17:\n case 18:\n case 19:\n // change dynamic ui colors\n break;\n case 46:\n // change log file\n break;\n case 50:\n // dynamic font\n break;\n case 51:\n // emacs shell\n break;\n case 52:\n // manipulate selection data\n break;\n case 104:\n case 105:\n case 110:\n case 111:\n case 112:\n case 113:\n case 114:\n case 115:\n case 116:\n case 117:\n case 118:\n // reset colors\n break;\n }\n\n this._terminal.params = [];\n this._terminal.currentParam = 0;\n this._state = ParserState.NORMAL;\n } else {\n if (!this._terminal.params.length) {\n if (ch >= '0' && ch <= '9') {\n this._terminal.currentParam =\n this._terminal.currentParam * 10 + ch.charCodeAt(0) - 48;\n } else if (ch === ';') {\n this._terminal.params.push(this._terminal.currentParam);\n this._terminal.currentParam = '';\n }\n } else {\n this._terminal.currentParam += ch;\n }\n }\n break;\n\n case ParserState.CSI_PARAM:\n if (ch in csiParamStateHandler) {\n csiParamStateHandler[ch](this);\n break;\n }\n this.finalizeParam();\n // Fall through the CSI as this character should be the CSI code.\n this._state = ParserState.CSI;\n\n case ParserState.CSI:\n if (ch in csiStateHandler) {\n if (this._terminal.debug) {\n this._terminal.log(`CSI ${this._terminal.prefix ? this._terminal.prefix : ''} ${this._terminal.params ? this._terminal.params.join(';') : ''} ${this._terminal.postfix ? this._terminal.postfix : ''} ${ch}`);\n }\n csiStateHandler[ch](this._inputHandler, this._terminal.params, this._terminal.prefix, this._terminal.postfix, this);\n } else {\n this._terminal.error('Unknown CSI code: %s.', ch);\n }\n\n this._state = ParserState.NORMAL;\n this._terminal.prefix = '';\n this._terminal.postfix = '';\n break;\n\n case ParserState.DCS:\n if (ch === C0.ESC || ch === C0.BEL) {\n if (ch === C0.ESC) this._position++;\n let pt;\n let valid: boolean;\n\n switch (this._terminal.prefix) {\n // User-Defined Keys (DECUDK).\n case '':\n break;\n\n // Request Status String (DECRQSS).\n // test: echo -e '\\eP$q\"p\\e\\\\'\n case '$q':\n pt = this._terminal.currentParam;\n valid = false;\n\n switch (pt) {\n // DECSCA\n case '\"q':\n pt = '0\"q';\n break;\n\n // DECSCL\n case '\"p':\n pt = '61\"p';\n break;\n\n // DECSTBM\n case 'r':\n pt = ''\n + (this._terminal.buffer.scrollTop + 1)\n + ';'\n + (this._terminal.buffer.scrollBottom + 1)\n + 'r';\n break;\n\n // SGR\n case 'm':\n pt = '0m';\n break;\n\n default:\n this._terminal.error('Unknown DCS Pt: %s.', pt);\n pt = '';\n break;\n }\n\n this._terminal.send(C0.ESC + 'P' + +valid + '$r' + pt + C0.ESC + '\\\\');\n break;\n\n // Set Termcap/Terminfo Data (xterm, experimental).\n case '+p':\n break;\n\n // Request Termcap/Terminfo String (xterm, experimental)\n // Regular xterm does not even respond to this sequence.\n // This can cause a small glitch in vim.\n // test: echo -ne '\\eP+q6b64\\e\\\\'\n case '+q':\n pt = this._terminal.currentParam;\n valid = false;\n\n this._terminal.send(C0.ESC + 'P' + +valid + '+r' + pt + C0.ESC + '\\\\');\n break;\n\n default:\n this._terminal.error('Unknown DCS prefix: %s.', this._terminal.prefix);\n break;\n }\n\n this._terminal.currentParam = 0;\n this._terminal.prefix = '';\n this._state = ParserState.NORMAL;\n } else if (!this._terminal.currentParam) {\n if (!this._terminal.prefix && ch !== '$' && ch !== '+') {\n this._terminal.currentParam = ch;\n } else if (this._terminal.prefix.length === 2) {\n this._terminal.currentParam = ch;\n } else {\n this._terminal.prefix += ch;\n }\n } else {\n this._terminal.currentParam += ch;\n }\n break;\n\n case ParserState.IGNORE:\n // For PM and APC.\n if (ch === C0.ESC || ch === C0.BEL) {\n if (ch === C0.ESC) this._position++;\n this._state = ParserState.NORMAL;\n }\n break;\n }\n }\n return this._state;\n }\n\n /**\n * Set the parser's current parsing state.\n *\n * @param state The new state.\n */\n public setState(state: ParserState): void {\n this._state = state;\n }\n\n /**\n * Sets the parsier's current prefix. CSI codes can have prefixes of '?', '>'\n * or '!'.\n *\n * @param prefix The prefix.\n */\n public setPrefix(prefix: string): void {\n this._terminal.prefix = prefix;\n }\n\n /**\n * Sets the parsier's current prefix. CSI codes can have postfixes of '$',\n * '\"', ' ', '\\''.\n *\n * @param postfix The postfix.\n */\n public setPostfix(postfix: string): void {\n this._terminal.postfix = postfix;\n }\n\n /**\n * Sets the parser's current parameter.\n *\n * @param param the parameter.\n */\n public setParam(param: number) {\n this._terminal.currentParam = param;\n }\n\n /**\n * Gets the parser's current parameter.\n */\n public getParam(): number {\n return this._terminal.currentParam;\n }\n\n /**\n * Finalizes the parser's current parameter, adding it to the list of\n * parameters and setting the new current parameter to 0.\n */\n public finalizeParam(): void {\n this._terminal.params.push(this._terminal.currentParam);\n this._terminal.currentParam = 0;\n }\n\n /**\n * Tell the parser to skip the next character.\n */\n public skipNextChar(): void {\n this._position++;\n }\n\n /**\n * Tell the parser to repeat parsing the current character (for example if it\n * needs parsing using a different state.\n */\n // public repeatChar(): void {\n // this._position--;\n // }\n}\n","/**\n * @license MIT\n */\n\nimport { LinkMatcherOptions } from './Interfaces';\nimport { LinkMatcher, LinkMatcherHandler, LinkMatcherValidationCallback } from './Types';\n\nconst INVALID_LINK_CLASS = 'xterm-invalid-link';\n\nconst protocolClause = '(https?:\\\\/\\\\/)';\nconst domainCharacterSet = '[\\\\da-z\\\\.-]+';\nconst negatedDomainCharacterSet = '[^\\\\da-z\\\\.-]+';\nconst domainBodyClause = '(' + domainCharacterSet + ')';\nconst tldClause = '([a-z\\\\.]{2,6})';\nconst ipClause = '((\\\\d{1,3}\\\\.){3}\\\\d{1,3})';\nconst localHostClause = '(localhost)';\nconst portClause = '(:\\\\d{1,5})';\nconst hostClause = '((' + domainBodyClause + '\\\\.' + tldClause + ')|' + ipClause + '|' + localHostClause + ')' + portClause + '?';\nconst pathClause = '(\\\\/[\\\\/\\\\w\\\\.\\\\-%~]*)*';\nconst queryStringHashFragmentCharacterSet = '[0-9\\\\w\\\\[\\\\]\\\\(\\\\)\\\\/\\\\?\\\\!#@$%&\\'*+,:;~\\\\=\\\\.\\\\-]*';\nconst queryStringClause = '(\\\\?' + queryStringHashFragmentCharacterSet + ')?';\nconst hashFragmentClause = '(#' + queryStringHashFragmentCharacterSet + ')?';\nconst negatedPathCharacterSet = '[^\\\\/\\\\w\\\\.\\\\-%]+';\nconst bodyClause = hostClause + pathClause + queryStringClause + hashFragmentClause;\nconst start = '(?:^|' + negatedDomainCharacterSet + ')(';\nconst end = ')($|' + negatedPathCharacterSet + ')';\nconst strictUrlRegex = new RegExp(start + protocolClause + bodyClause + end);\n\n/**\n * The ID of the built in http(s) link matcher.\n */\nconst HYPERTEXT_LINK_MATCHER_ID = 0;\n\n/**\n * The Linkifier applies links to rows shortly after they have been refreshed.\n */\nexport class Linkifier {\n /**\n * The time to wait after a row is changed before it is linkified. This prevents\n * the costly operation of searching every row multiple times, potentially a\n * huge amount of times.\n */\n protected static TIME_BEFORE_LINKIFY = 200;\n\n protected _linkMatchers: LinkMatcher[];\n\n private _document: Document;\n private _rows: HTMLElement[];\n private _rowTimeoutIds: number[];\n private _nextLinkMatcherId = HYPERTEXT_LINK_MATCHER_ID;\n\n constructor() {\n this._rowTimeoutIds = [];\n this._linkMatchers = [];\n this.registerLinkMatcher(strictUrlRegex, null, { matchIndex: 1 });\n }\n\n /**\n * Attaches the linkifier to the DOM, enabling linkification.\n * @param document The document object.\n * @param rows The array of rows to apply links to.\n */\n public attachToDom(document: Document, rows: HTMLElement[]) {\n this._document = document;\n this._rows = rows;\n }\n\n /**\n * Queues a row for linkification.\n * @param {number} rowIndex The index of the row to linkify.\n */\n public linkifyRow(rowIndex: number): void {\n // Don't attempt linkify if not yet attached to DOM\n if (!this._document) {\n return;\n }\n\n const timeoutId = this._rowTimeoutIds[rowIndex];\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n this._rowTimeoutIds[rowIndex] = setTimeout(this._linkifyRow.bind(this, rowIndex), Linkifier.TIME_BEFORE_LINKIFY);\n }\n\n /**\n * Attaches a handler for hypertext links, overriding default behavior\n * for standard http(s) links.\n * @param {LinkHandler} handler The handler to use, this can be cleared with\n * null.\n */\n public setHypertextLinkHandler(handler: LinkMatcherHandler): void {\n this._linkMatchers[HYPERTEXT_LINK_MATCHER_ID].handler = handler;\n }\n\n /**\n * Attaches a validation callback for hypertext links.\n * @param {LinkMatcherValidationCallback} callback The callback to use, this\n * can be cleared with null.\n */\n public setHypertextValidationCallback(callback: LinkMatcherValidationCallback): void {\n this._linkMatchers[HYPERTEXT_LINK_MATCHER_ID].validationCallback = callback;\n }\n\n /**\n * Registers a link matcher, allowing custom link patterns to be matched and\n * handled.\n * @param {RegExp} regex The regular expression to search for, specifically\n * this searches the textContent of the rows. You will want to use \\s to match\n * a space ' ' character for example.\n * @param {LinkHandler} handler The callback when the link is called.\n * @param {LinkMatcherOptions} [options] Options for the link matcher.\n * @return {number} The ID of the new matcher, this can be used to deregister.\n */\n public registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options: LinkMatcherOptions = {}): number {\n if (this._nextLinkMatcherId !== HYPERTEXT_LINK_MATCHER_ID && !handler) {\n throw new Error('handler must be defined');\n }\n const matcher: LinkMatcher = {\n id: this._nextLinkMatcherId++,\n regex,\n handler,\n matchIndex: options.matchIndex,\n validationCallback: options.validationCallback,\n priority: options.priority || 0\n };\n this._addLinkMatcherToList(matcher);\n return matcher.id;\n }\n\n /**\n * Inserts a link matcher to the list in the correct position based on the\n * priority of each link matcher. New link matchers of equal priority are\n * considered after older link matchers.\n * @param matcher The link matcher to be added.\n */\n private _addLinkMatcherToList(matcher: LinkMatcher): void {\n if (this._linkMatchers.length === 0) {\n this._linkMatchers.push(matcher);\n return;\n }\n\n for (let i = this._linkMatchers.length - 1; i >= 0; i--) {\n if (matcher.priority <= this._linkMatchers[i].priority) {\n this._linkMatchers.splice(i + 1, 0, matcher);\n return;\n }\n }\n\n this._linkMatchers.splice(0, 0, matcher);\n }\n\n /**\n * Deregisters a link matcher if it has been registered.\n * @param {number} matcherId The link matcher's ID (returned after register)\n * @return {boolean} Whether a link matcher was found and deregistered.\n */\n public deregisterLinkMatcher(matcherId: number): boolean {\n // ID 0 is the hypertext link matcher which cannot be deregistered\n for (let i = 1; i < this._linkMatchers.length; i++) {\n if (this._linkMatchers[i].id === matcherId) {\n this._linkMatchers.splice(i, 1);\n return true;\n }\n }\n return false;\n }\n\n /**\n * Linkifies a row.\n * @param {number} rowIndex The index of the row to linkify.\n */\n private _linkifyRow(rowIndex: number): void {\n const row = this._rows[rowIndex];\n if (!row) {\n return;\n }\n const text = row.textContent;\n for (let i = 0; i < this._linkMatchers.length; i++) {\n const matcher = this._linkMatchers[i];\n const linkElements = this._doLinkifyRow(row, matcher);\n if (linkElements.length > 0) {\n // Fire validation callback\n if (matcher.validationCallback) {\n for (let j = 0; j < linkElements.length; j++) {\n const element = linkElements[j];\n matcher.validationCallback(element.textContent, element, isValid => {\n if (!isValid) {\n element.classList.add(INVALID_LINK_CLASS);\n }\n });\n }\n }\n // Only allow a single LinkMatcher to trigger on any given row.\n return;\n }\n }\n }\n\n /**\n * Linkifies a row given a specific handler.\n * @param {HTMLElement} row The row to linkify.\n * @param {LinkMatcher} matcher The link matcher for this line.\n * @return The link element(s) that were added.\n */\n private _doLinkifyRow(row: HTMLElement, matcher: LinkMatcher): HTMLElement[] {\n // Iterate over nodes as we want to consider text nodes\n let result = [];\n const isHttpLinkMatcher = matcher.id === HYPERTEXT_LINK_MATCHER_ID;\n const nodes = row.childNodes;\n\n // Find the first match\n let match = row.textContent.match(matcher.regex);\n if (!match || match.length === 0) {\n return result;\n }\n let uri = match[typeof matcher.matchIndex !== 'number' ? 0 : matcher.matchIndex];\n // Set the next searches start index\n let rowStartIndex = match.index + uri.length;\n\n for (let i = 0; i < nodes.length; i++) {\n const node = nodes[i];\n const searchIndex = node.textContent.indexOf(uri);\n if (searchIndex >= 0) {\n const linkElement = this._createAnchorElement(uri, matcher.handler, isHttpLinkMatcher);\n if (node.textContent.length === uri.length) {\n // Matches entire string\n if (node.nodeType === 3 /*Node.TEXT_NODE*/) {\n this._replaceNode(node, linkElement);\n } else {\n const element = (node);\n if (element.nodeName === 'A') {\n // This row has already been linkified\n return result;\n }\n element.innerHTML = '';\n element.appendChild(linkElement);\n }\n } else if (node.childNodes.length > 1) {\n // Matches part of string in an element with multiple child nodes\n for (let j = 0; j < node.childNodes.length; j++) {\n const childNode = node.childNodes[j];\n const childSearchIndex = childNode.textContent.indexOf(uri);\n if (childSearchIndex !== -1) {\n // Match found in currentNode\n this._replaceNodeSubstringWithNode(childNode, linkElement, uri, childSearchIndex);\n // Don't need to count nodesAdded by replacing the node as this\n // is a child node, not a top-level node.\n break;\n }\n }\n } else {\n // Matches part of string in a single text node\n const nodesAdded = this._replaceNodeSubstringWithNode(node, linkElement, uri, searchIndex);\n // No need to consider the new nodes\n i += nodesAdded;\n }\n result.push(linkElement);\n\n // Find the next match\n match = row.textContent.substring(rowStartIndex).match(matcher.regex);\n if (!match || match.length === 0) {\n return result;\n }\n uri = match[typeof matcher.matchIndex !== 'number' ? 0 : matcher.matchIndex];\n rowStartIndex += match.index + uri.length;\n }\n }\n return result;\n }\n\n /**\n * Creates a link anchor element.\n * @param {string} uri The uri of the link.\n * @return {HTMLAnchorElement} The link.\n */\n private _createAnchorElement(uri: string, handler: LinkMatcherHandler, isHypertextLinkHandler: boolean): HTMLAnchorElement {\n const element = this._document.createElement('a');\n element.textContent = uri;\n element.draggable = false;\n if (isHypertextLinkHandler) {\n element.href = uri;\n // Force link on another tab so work is not lost\n element.target = '_blank';\n element.addEventListener('click', (event: MouseEvent) => {\n if (handler) {\n return handler(event, uri);\n }\n });\n } else {\n element.addEventListener('click', (event: MouseEvent) => {\n // Don't execute the handler if the link is flagged as invalid\n if (element.classList.contains(INVALID_LINK_CLASS)) {\n return;\n }\n return handler(event, uri);\n });\n }\n return element;\n }\n\n /**\n * Replace a node with 1 or more other nodes.\n * @param {Node} oldNode The node to replace.\n * @param {Node[]} newNodes The new nodes to insert in order.\n */\n private _replaceNode(oldNode: Node, ...newNodes: Node[]): void {\n const parent = oldNode.parentNode;\n for (let i = 0; i < newNodes.length; i++) {\n parent.insertBefore(newNodes[i], oldNode);\n }\n parent.removeChild(oldNode);\n }\n\n /**\n * Replace a substring within a node with a new node.\n * @param {Node} targetNode The target node; either a text node or a \n * containing a single text node.\n * @param {Node} newNode The new node to insert.\n * @param {string} substring The substring to replace.\n * @param {number} substringIndex The index of the substring within the string.\n * @return The number of nodes to skip when searching for the next uri.\n */\n private _replaceNodeSubstringWithNode(targetNode: Node, newNode: Node, substring: string, substringIndex: number): number {\n // If the targetNode is a non-text node with a single child, make the child\n // the new targetNode.\n if (targetNode.childNodes.length === 1) {\n targetNode = targetNode.childNodes[0];\n }\n\n // The targetNode will be either a text node or a . The text node\n // (targetNode or its only-child) needs to be replaced with newNode plus new\n // text nodes potentially on either side.\n if (targetNode.nodeType !== 3/*Node.TEXT_NODE*/) {\n throw new Error('targetNode must be a text node or only contain a single text node');\n }\n\n const fullText = targetNode.textContent;\n\n if (substringIndex === 0) {\n // Replace with \n const rightText = fullText.substring(substring.length);\n const rightTextNode = this._document.createTextNode(rightText);\n this._replaceNode(targetNode, newNode, rightTextNode);\n return 0;\n }\n\n if (substringIndex === targetNode.textContent.length - substring.length) {\n // Replace with \n const leftText = fullText.substring(0, substringIndex);\n const leftTextNode = this._document.createTextNode(leftText);\n this._replaceNode(targetNode, leftTextNode, newNode);\n return 0;\n }\n\n // Replace with \n const leftText = fullText.substring(0, substringIndex);\n const leftTextNode = this._document.createTextNode(leftText);\n const rightText = fullText.substring(substringIndex + substring.length);\n const rightTextNode = this._document.createTextNode(rightText);\n this._replaceNode(targetNode, leftTextNode, newNode, rightTextNode);\n return 1;\n }\n}\n","/**\n * @license MIT\n */\n\nimport { IInputHandler, ITerminal } from './Interfaces';\nimport { C0 } from './EscapeSequences';\nimport { DEFAULT_CHARSET } from './Charsets';\n\n/**\n * The terminal's standard implementation of IInputHandler, this handles all\n * input from the Parser.\n *\n * Refer to http://invisible-island.net/xterm/ctlseqs/ctlseqs.html to understand\n * each function's header comment.\n */\nexport class InputHandler implements IInputHandler {\n // TODO: We want to type _terminal when it's pulled into TS\n constructor(private _terminal: any) { }\n\n public addChar(char: string, code: number): void {\n if (char >= ' ') {\n // calculate print space\n // expensive call, therefore we save width in line buffer\n const ch_width = wcwidth(code);\n\n if (this._terminal.charset && this._terminal.charset[char]) {\n char = this._terminal.charset[char];\n }\n\n let row = this._terminal.buffer.y + this._terminal.buffer.ybase;\n\n // insert combining char in last cell\n // FIXME: needs handling after cursor jumps\n if (!ch_width && this._terminal.buffer.x) {\n // dont overflow left\n if (this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1]) {\n if (!this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][2]) {\n\n // found empty cell after fullwidth, need to go 2 cells back\n if (this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 2])\n this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 2][1] += char;\n\n } else {\n this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][1] += char;\n }\n this._terminal.updateRange(this._terminal.buffer.y);\n }\n return;\n }\n\n // goto next line if ch would overflow\n // TODO: needs a global min terminal width of 2\n if (this._terminal.buffer.x + ch_width - 1 >= this._terminal.cols) {\n // autowrap - DECAWM\n if (this._terminal.wraparoundMode) {\n this._terminal.buffer.x = 0;\n this._terminal.buffer.y++;\n if (this._terminal.buffer.y > this._terminal.buffer.scrollBottom) {\n this._terminal.buffer.y--;\n this._terminal.scroll(true);\n } else {\n // The line already exists (eg. the initial viewport), mark it as a\n // wrapped line\n this._terminal.buffer.lines.get(this._terminal.buffer.y).isWrapped = true;\n }\n } else {\n if (ch_width === 2) // FIXME: check for xterm behavior\n return;\n }\n }\n row = this._terminal.buffer.y + this._terminal.buffer.ybase;\n\n // insert mode: move characters to right\n if (this._terminal.insertMode) {\n // do this twice for a fullwidth char\n for (let moves = 0; moves < ch_width; ++moves) {\n // remove last cell, if it's width is 0\n // we have to adjust the second last cell as well\n const removed = this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).pop();\n if (removed[2] === 0\n && this._terminal.buffer.lines.get(row)[this._terminal.cols - 2]\n && this._terminal.buffer.lines.get(row)[this._terminal.cols - 2][2] === 2) {\n this._terminal.buffer.lines.get(row)[this._terminal.cols - 2] = [this._terminal.curAttr, ' ', 1];\n }\n\n // insert empty cell at cursor\n this._terminal.buffer.lines.get(row).splice(this._terminal.buffer.x, 0, [this._terminal.curAttr, ' ', 1]);\n }\n }\n\n this._terminal.buffer.lines.get(row)[this._terminal.buffer.x] = [this._terminal.curAttr, char, ch_width];\n this._terminal.buffer.x++;\n this._terminal.updateRange(this._terminal.buffer.y);\n\n // fullwidth char - set next cell width to zero and advance cursor\n if (ch_width === 2) {\n this._terminal.buffer.lines.get(row)[this._terminal.buffer.x] = [this._terminal.curAttr, '', 0];\n this._terminal.buffer.x++;\n }\n }\n }\n\n /**\n * BEL\n * Bell (Ctrl-G).\n */\n public bell(): void {\n if (!this._terminal.visualBell) {\n return;\n }\n this._terminal.element.style.borderColor = 'white';\n setTimeout(() => this._terminal.element.style.borderColor = '', 10);\n if (this._terminal.popOnBell) {\n this._terminal.focus();\n }\n }\n\n /**\n * LF\n * Line Feed or New Line (NL). (LF is Ctrl-J).\n */\n public lineFeed(): void {\n if (this._terminal.convertEol) {\n this._terminal.buffer.x = 0;\n }\n this._terminal.buffer.y++;\n if (this._terminal.buffer.y > this._terminal.buffer.scrollBottom) {\n this._terminal.buffer.y--;\n this._terminal.scroll();\n }\n // If the end of the line is hit, prevent this action from wrapping around to the next line.\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x--;\n }\n /**\n * This event is emitted whenever the terminal outputs a LF or NL.\n *\n * @event lineFeed\n */\n this._terminal.emit('lineFeed');\n }\n\n /**\n * CR\n * Carriage Return (Ctrl-M).\n */\n public carriageReturn(): void {\n this._terminal.buffer.x = 0;\n }\n\n /**\n * BS\n * Backspace (Ctrl-H).\n */\n public backspace(): void {\n if (this._terminal.buffer.x > 0) {\n this._terminal.buffer.x--;\n }\n }\n\n /**\n * TAB\n * Horizontal Tab (HT) (Ctrl-I).\n */\n public tab(): void {\n this._terminal.buffer.x = this._terminal.nextStop();\n }\n\n /**\n * SO\n * Shift Out (Ctrl-N) -> Switch to Alternate Character Set. This invokes the\n * G1 character set.\n */\n public shiftOut(): void {\n this._terminal.setgLevel(1);\n }\n\n /**\n * SI\n * Shift In (Ctrl-O) -> Switch to Standard Character Set. This invokes the G0\n * character set (the default).\n */\n public shiftIn(): void {\n this._terminal.setgLevel(0);\n }\n\n /**\n * CSI Ps @\n * Insert Ps (Blank) Character(s) (default = 1) (ICH).\n */\n public insertChars(params: number[]): void {\n let param, row, j, ch;\n\n param = params[0];\n if (param < 1) param = 1;\n\n row = this._terminal.buffer.y + this._terminal.buffer.ybase;\n j = this._terminal.buffer.x;\n ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm\n\n while (param-- && j < this._terminal.cols) {\n this._terminal.buffer.lines.get(row).splice(j++, 0, ch);\n this._terminal.buffer.lines.get(row).pop();\n }\n }\n\n /**\n * CSI Ps A\n * Cursor Up Ps Times (default = 1) (CUU).\n */\n public cursorUp(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.y -= param;\n if (this._terminal.buffer.y < 0) {\n this._terminal.buffer.y = 0;\n }\n }\n\n /**\n * CSI Ps B\n * Cursor Down Ps Times (default = 1) (CUD).\n */\n public cursorDown(params: number[]) {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.y += param;\n if (this._terminal.buffer.y >= this._terminal.rows) {\n this._terminal.buffer.y = this._terminal.rows - 1;\n }\n // If the end of the line is hit, prevent this action from wrapping around to the next line.\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x--;\n }\n }\n\n /**\n * CSI Ps C\n * Cursor Forward Ps Times (default = 1) (CUF).\n */\n public cursorForward(params: number[]) {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.x += param;\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x = this._terminal.cols - 1;\n }\n }\n\n /**\n * CSI Ps D\n * Cursor Backward Ps Times (default = 1) (CUB).\n */\n public cursorBackward(params: number[]) {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n // If the end of the line is hit, prevent this action from wrapping around to the next line.\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x--;\n }\n this._terminal.buffer.x -= param;\n if (this._terminal.buffer.x < 0) {\n this._terminal.buffer.x = 0;\n }\n }\n\n /**\n * CSI Ps E\n * Cursor Next Line Ps Times (default = 1) (CNL).\n * same as CSI Ps B ?\n */\n public cursorNextLine(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.y += param;\n if (this._terminal.buffer.y >= this._terminal.rows) {\n this._terminal.buffer.y = this._terminal.rows - 1;\n }\n this._terminal.buffer.x = 0;\n }\n\n\n /**\n * CSI Ps F\n * Cursor Preceding Line Ps Times (default = 1) (CNL).\n * reuse CSI Ps A ?\n */\n public cursorPrecedingLine(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.y -= param;\n if (this._terminal.buffer.y < 0) {\n this._terminal.buffer.y = 0;\n }\n this._terminal.buffer.x = 0;\n }\n\n\n /**\n * CSI Ps G\n * Cursor Character Absolute [column] (default = [row,1]) (CHA).\n */\n public cursorCharAbsolute(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.x = param - 1;\n }\n\n /**\n * CSI Ps ; Ps H\n * Cursor Position [row;column] (default = [1,1]) (CUP).\n */\n public cursorPosition(params: number[]): void {\n let row, col;\n\n row = params[0] - 1;\n\n if (params.length >= 2) {\n col = params[1] - 1;\n } else {\n col = 0;\n }\n\n if (row < 0) {\n row = 0;\n } else if (row >= this._terminal.rows) {\n row = this._terminal.rows - 1;\n }\n\n if (col < 0) {\n col = 0;\n } else if (col >= this._terminal.cols) {\n col = this._terminal.cols - 1;\n }\n\n this._terminal.buffer.x = col;\n this._terminal.buffer.y = row;\n }\n\n /**\n * CSI Ps I\n * Cursor Forward Tabulation Ps tab stops (default = 1) (CHT).\n */\n public cursorForwardTab(params: number[]): void {\n let param = params[0] || 1;\n while (param--) {\n this._terminal.buffer.x = this._terminal.nextStop();\n }\n }\n\n /**\n * CSI Ps J Erase in Display (ED).\n * Ps = 0 -> Erase Below (default).\n * Ps = 1 -> Erase Above.\n * Ps = 2 -> Erase All.\n * Ps = 3 -> Erase Saved Lines (xterm).\n * CSI ? Ps J\n * Erase in Display (DECSED).\n * Ps = 0 -> Selective Erase Below (default).\n * Ps = 1 -> Selective Erase Above.\n * Ps = 2 -> Selective Erase All.\n */\n public eraseInDisplay(params: number[]): void {\n let j;\n switch (params[0]) {\n case 0:\n this._terminal.eraseRight(this._terminal.buffer.x, this._terminal.buffer.y);\n j = this._terminal.buffer.y + 1;\n for (; j < this._terminal.rows; j++) {\n this._terminal.eraseLine(j);\n }\n break;\n case 1:\n this._terminal.eraseLeft(this._terminal.buffer.x, this._terminal.buffer.y);\n j = this._terminal.buffer.y;\n while (j--) {\n this._terminal.eraseLine(j);\n }\n break;\n case 2:\n j = this._terminal.rows;\n while (j--) this._terminal.eraseLine(j);\n break;\n case 3:\n // Clear scrollback (everything not in viewport)\n const scrollBackSize = this._terminal.buffer.lines.length - this._terminal.rows;\n if (scrollBackSize > 0) {\n this._terminal.buffer.lines.trimStart(scrollBackSize);\n this._terminal.buffer.ybase = Math.max(this._terminal.buffer.ybase - scrollBackSize, 0);\n this._terminal.buffer.ydisp = Math.max(this._terminal.buffer.ydisp - scrollBackSize, 0);\n // Force a scroll event to refresh viewport\n this._terminal.emit('scroll', 0);\n }\n break;\n }\n }\n\n /**\n * CSI Ps K Erase in Line (EL).\n * Ps = 0 -> Erase to Right (default).\n * Ps = 1 -> Erase to Left.\n * Ps = 2 -> Erase All.\n * CSI ? Ps K\n * Erase in Line (DECSEL).\n * Ps = 0 -> Selective Erase to Right (default).\n * Ps = 1 -> Selective Erase to Left.\n * Ps = 2 -> Selective Erase All.\n */\n public eraseInLine(params: number[]): void {\n switch (params[0]) {\n case 0:\n this._terminal.eraseRight(this._terminal.buffer.x, this._terminal.buffer.y);\n break;\n case 1:\n this._terminal.eraseLeft(this._terminal.buffer.x, this._terminal.buffer.y);\n break;\n case 2:\n this._terminal.eraseLine(this._terminal.buffer.y);\n break;\n }\n }\n\n /**\n * CSI Ps L\n * Insert Ps Line(s) (default = 1) (IL).\n */\n public insertLines(params: number[]): void {\n let param, row, j;\n\n param = params[0];\n if (param < 1) {\n param = 1;\n }\n row = this._terminal.buffer.y + this._terminal.buffer.ybase;\n\n j = this._terminal.rows - 1 - this._terminal.buffer.scrollBottom;\n j = this._terminal.rows - 1 + this._terminal.buffer.ybase - j + 1;\n\n while (param--) {\n if (this._terminal.buffer.lines.length === this._terminal.buffer.lines.maxLength) {\n // Trim the start of lines to make room for the new line\n this._terminal.buffer.lines.trimStart(1);\n this._terminal.buffer.ybase--;\n this._terminal.buffer.ydisp--;\n row--;\n j--;\n }\n // test: echo -e '\\e[44m\\e[1L\\e[0m'\n // blankLine(true) - xterm/linux behavior\n this._terminal.buffer.lines.splice(row, 0, this._terminal.blankLine(true));\n this._terminal.buffer.lines.splice(j, 1);\n }\n\n // this.maxRange();\n this._terminal.updateRange(this._terminal.buffer.y);\n this._terminal.updateRange(this._terminal.buffer.scrollBottom);\n }\n\n /**\n * CSI Ps M\n * Delete Ps Line(s) (default = 1) (DL).\n */\n public deleteLines(params: number[]): void {\n let param, row, j;\n\n param = params[0];\n if (param < 1) {\n param = 1;\n }\n row = this._terminal.buffer.y + this._terminal.buffer.ybase;\n\n j = this._terminal.rows - 1 - this._terminal.buffer.scrollBottom;\n j = this._terminal.rows - 1 + this._terminal.buffer.ybase - j;\n\n while (param--) {\n if (this._terminal.buffer.lines.length === this._terminal.buffer.lines.maxLength) {\n // Trim the start of lines to make room for the new line\n this._terminal.buffer.lines.trimStart(1);\n this._terminal.buffer.ybase -= 1;\n this._terminal.buffer.ydisp -= 1;\n }\n // test: echo -e '\\e[44m\\e[1M\\e[0m'\n // blankLine(true) - xterm/linux behavior\n this._terminal.buffer.lines.splice(j + 1, 0, this._terminal.blankLine(true));\n this._terminal.buffer.lines.splice(row, 1);\n }\n\n // this.maxRange();\n this._terminal.updateRange(this._terminal.buffer.y);\n this._terminal.updateRange(this._terminal.buffer.scrollBottom);\n }\n\n /**\n * CSI Ps P\n * Delete Ps Character(s) (default = 1) (DCH).\n */\n public deleteChars(params: number[]): void {\n let param, row, ch;\n\n param = params[0];\n if (param < 1) {\n param = 1;\n }\n\n row = this._terminal.buffer.y + this._terminal.buffer.ybase;\n ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm\n\n while (param--) {\n this._terminal.buffer.lines.get(row).splice(this._terminal.buffer.x, 1);\n this._terminal.buffer.lines.get(row).push(ch);\n }\n }\n\n /**\n * CSI Ps S Scroll up Ps lines (default = 1) (SU).\n */\n public scrollUp(params: number[]): void {\n let param = params[0] || 1;\n while (param--) {\n this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollTop, 1);\n this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollBottom, 0, this._terminal.blankLine());\n }\n // this.maxRange();\n this._terminal.updateRange(this._terminal.buffer.scrollTop);\n this._terminal.updateRange(this._terminal.buffer.scrollBottom);\n }\n\n /**\n * CSI Ps T Scroll down Ps lines (default = 1) (SD).\n */\n public scrollDown(params: number[]): void {\n let param = params[0] || 1;\n while (param--) {\n this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollBottom, 1);\n this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollTop, 0, this._terminal.blankLine());\n }\n // this.maxRange();\n this._terminal.updateRange(this._terminal.buffer.scrollTop);\n this._terminal.updateRange(this._terminal.buffer.scrollBottom);\n }\n\n /**\n * CSI Ps X\n * Erase Ps Character(s) (default = 1) (ECH).\n */\n public eraseChars(params: number[]): void {\n let param, row, j, ch;\n\n param = params[0];\n if (param < 1) {\n param = 1;\n }\n\n row = this._terminal.buffer.y + this._terminal.buffer.ybase;\n j = this._terminal.buffer.x;\n ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm\n\n while (param-- && j < this._terminal.cols) {\n this._terminal.buffer.lines.get(row)[j++] = ch;\n }\n }\n\n /**\n * CSI Ps Z Cursor Backward Tabulation Ps tab stops (default = 1) (CBT).\n */\n public cursorBackwardTab(params: number[]): void {\n let param = params[0] || 1;\n while (param--) {\n this._terminal.buffer.x = this._terminal.prevStop();\n }\n }\n\n /**\n * CSI Pm ` Character Position Absolute\n * [column] (default = [row,1]) (HPA).\n */\n public charPosAbsolute(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.x = param - 1;\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x = this._terminal.cols - 1;\n }\n }\n\n /**\n * CSI Pm a Character Position Relative\n * [columns] (default = [row,col+1]) (HPR)\n * reuse CSI Ps C ?\n */\n public HPositionRelative(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.x += param;\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x = this._terminal.cols - 1;\n }\n }\n\n /**\n * CSI Ps b Repeat the preceding graphic character Ps times (REP).\n */\n public repeatPrecedingCharacter(params: number[]): void {\n let param = params[0] || 1\n , line = this._terminal.buffer.lines.get(this._terminal.buffer.ybase + this._terminal.buffer.y)\n , ch = line[this._terminal.buffer.x - 1] || [this._terminal.defAttr, ' ', 1];\n\n while (param--) {\n line[this._terminal.buffer.x++] = ch;\n }\n }\n\n /**\n * CSI Ps c Send Device Attributes (Primary DA).\n * Ps = 0 or omitted -> request attributes from terminal. The\n * response depends on the decTerminalID resource setting.\n * -> CSI ? 1 ; 2 c (``VT100 with Advanced Video Option'')\n * -> CSI ? 1 ; 0 c (``VT101 with No Options'')\n * -> CSI ? 6 c (``VT102'')\n * -> CSI ? 6 0 ; 1 ; 2 ; 6 ; 8 ; 9 ; 1 5 ; c (``VT220'')\n * The VT100-style response parameters do not mean anything by\n * themselves. VT220 parameters do, telling the host what fea-\n * tures the terminal supports:\n * Ps = 1 -> 132-columns.\n * Ps = 2 -> Printer.\n * Ps = 6 -> Selective erase.\n * Ps = 8 -> User-defined keys.\n * Ps = 9 -> National replacement character sets.\n * Ps = 1 5 -> Technical characters.\n * Ps = 2 2 -> ANSI color, e.g., VT525.\n * Ps = 2 9 -> ANSI text locator (i.e., DEC Locator mode).\n * CSI > Ps c\n * Send Device Attributes (Secondary DA).\n * Ps = 0 or omitted -> request the terminal's identification\n * code. The response depends on the decTerminalID resource set-\n * ting. It should apply only to VT220 and up, but xterm extends\n * this to VT100.\n * -> CSI > Pp ; Pv ; Pc c\n * where Pp denotes the terminal type\n * Pp = 0 -> ``VT100''.\n * Pp = 1 -> ``VT220''.\n * and Pv is the firmware version (for xterm, this was originally\n * the XFree86 patch number, starting with 95). In a DEC termi-\n * nal, Pc indicates the ROM cartridge registration number and is\n * always zero.\n * More information:\n * xterm/charproc.c - line 2012, for more information.\n * vim responds with ^[[?0c or ^[[?1c after the terminal's response (?)\n */\n public sendDeviceAttributes(params: number[]): void {\n if (params[0] > 0) {\n return;\n }\n\n if (!this._terminal.prefix) {\n if (this._terminal.is('xterm') || this._terminal.is('rxvt-unicode') || this._terminal.is('screen')) {\n this._terminal.send(C0.ESC + '[?1;2c');\n } else if (this._terminal.is('linux')) {\n this._terminal.send(C0.ESC + '[?6c');\n }\n } else if (this._terminal.prefix === '>') {\n // xterm and urxvt\n // seem to spit this\n // out around ~370 times (?).\n if (this._terminal.is('xterm')) {\n this._terminal.send(C0.ESC + '[>0;276;0c');\n } else if (this._terminal.is('rxvt-unicode')) {\n this._terminal.send(C0.ESC + '[>85;95;0c');\n } else if (this._terminal.is('linux')) {\n // not supported by linux console.\n // linux console echoes parameters.\n this._terminal.send(params[0] + 'c');\n } else if (this._terminal.is('screen')) {\n this._terminal.send(C0.ESC + '[>83;40003;0c');\n }\n }\n }\n\n /**\n * CSI Pm d Vertical Position Absolute (VPA)\n * [row] (default = [1,column])\n */\n public linePosAbsolute(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.y = param - 1;\n if (this._terminal.buffer.y >= this._terminal.rows) {\n this._terminal.buffer.y = this._terminal.rows - 1;\n }\n }\n\n /**\n * CSI Pm e Vertical Position Relative (VPR)\n * [rows] (default = [row+1,column])\n * reuse CSI Ps B ?\n */\n public VPositionRelative(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.y += param;\n if (this._terminal.buffer.y >= this._terminal.rows) {\n this._terminal.buffer.y = this._terminal.rows - 1;\n }\n // If the end of the line is hit, prevent this action from wrapping around to the next line.\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x--;\n }\n }\n\n /**\n * CSI Ps ; Ps f\n * Horizontal and Vertical Position [row;column] (default =\n * [1,1]) (HVP).\n */\n public HVPosition(params: number[]): void {\n if (params[0] < 1) params[0] = 1;\n if (params[1] < 1) params[1] = 1;\n\n this._terminal.buffer.y = params[0] - 1;\n if (this._terminal.buffer.y >= this._terminal.rows) {\n this._terminal.buffer.y = this._terminal.rows - 1;\n }\n\n this._terminal.buffer.x = params[1] - 1;\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x = this._terminal.cols - 1;\n }\n }\n\n /**\n * CSI Ps g Tab Clear (TBC).\n * Ps = 0 -> Clear Current Column (default).\n * Ps = 3 -> Clear All.\n * Potentially:\n * Ps = 2 -> Clear Stops on Line.\n * http://vt100.net/annarbor/aaa-ug/section6.html\n */\n public tabClear(params: number[]): void {\n let param = params[0];\n if (param <= 0) {\n delete this._terminal.buffer.tabs[this._terminal.buffer.x];\n } else if (param === 3) {\n this._terminal.buffer.tabs = {};\n }\n }\n\n /**\n * CSI Pm h Set Mode (SM).\n * Ps = 2 -> Keyboard Action Mode (AM).\n * Ps = 4 -> Insert Mode (IRM).\n * Ps = 1 2 -> Send/receive (SRM).\n * Ps = 2 0 -> Automatic Newline (LNM).\n * CSI ? Pm h\n * DEC Private Mode Set (DECSET).\n * Ps = 1 -> Application Cursor Keys (DECCKM).\n * Ps = 2 -> Designate USASCII for character sets G0-G3\n * (DECANM), and set VT100 mode.\n * Ps = 3 -> 132 Column Mode (DECCOLM).\n * Ps = 4 -> Smooth (Slow) Scroll (DECSCLM).\n * Ps = 5 -> Reverse Video (DECSCNM).\n * Ps = 6 -> Origin Mode (DECOM).\n * Ps = 7 -> Wraparound Mode (DECAWM).\n * Ps = 8 -> Auto-repeat Keys (DECARM).\n * Ps = 9 -> Send Mouse X & Y on button press. See the sec-\n * tion Mouse Tracking.\n * Ps = 1 0 -> Show toolbar (rxvt).\n * Ps = 1 2 -> Start Blinking Cursor (att610).\n * Ps = 1 8 -> Print form feed (DECPFF).\n * Ps = 1 9 -> Set print extent to full screen (DECPEX).\n * Ps = 2 5 -> Show Cursor (DECTCEM).\n * Ps = 3 0 -> Show scrollbar (rxvt).\n * Ps = 3 5 -> Enable font-shifting functions (rxvt).\n * Ps = 3 8 -> Enter Tektronix Mode (DECTEK).\n * Ps = 4 0 -> Allow 80 -> 132 Mode.\n * Ps = 4 1 -> more(1) fix (see curses resource).\n * Ps = 4 2 -> Enable Nation Replacement Character sets (DECN-\n * RCM).\n * Ps = 4 4 -> Turn On Margin Bell.\n * Ps = 4 5 -> Reverse-wraparound Mode.\n * Ps = 4 6 -> Start Logging. This is normally disabled by a\n * compile-time option.\n * Ps = 4 7 -> Use Alternate Screen Buffer. (This may be dis-\n * abled by the titeInhibit resource).\n * Ps = 6 6 -> Application keypad (DECNKM).\n * Ps = 6 7 -> Backarrow key sends backspace (DECBKM).\n * Ps = 1 0 0 0 -> Send Mouse X & Y on button press and\n * release. See the section Mouse Tracking.\n * Ps = 1 0 0 1 -> Use Hilite Mouse Tracking.\n * Ps = 1 0 0 2 -> Use Cell Motion Mouse Tracking.\n * Ps = 1 0 0 3 -> Use All Motion Mouse Tracking.\n * Ps = 1 0 0 4 -> Send FocusIn/FocusOut events.\n * Ps = 1 0 0 5 -> Enable Extended Mouse Mode.\n * Ps = 1 0 1 0 -> Scroll to bottom on tty output (rxvt).\n * Ps = 1 0 1 1 -> Scroll to bottom on key press (rxvt).\n * Ps = 1 0 3 4 -> Interpret \"meta\" key, sets eighth bit.\n * (enables the eightBitInput resource).\n * Ps = 1 0 3 5 -> Enable special modifiers for Alt and Num-\n * Lock keys. (This enables the numLock resource).\n * Ps = 1 0 3 6 -> Send ESC when Meta modifies a key. (This\n * enables the metaSendsEscape resource).\n * Ps = 1 0 3 7 -> Send DEL from the editing-keypad Delete\n * key.\n * Ps = 1 0 3 9 -> Send ESC when Alt modifies a key. (This\n * enables the altSendsEscape resource).\n * Ps = 1 0 4 0 -> Keep selection even if not highlighted.\n * (This enables the keepSelection resource).\n * Ps = 1 0 4 1 -> Use the CLIPBOARD selection. (This enables\n * the selectToClipboard resource).\n * Ps = 1 0 4 2 -> Enable Urgency window manager hint when\n * Control-G is received. (This enables the bellIsUrgent\n * resource).\n * Ps = 1 0 4 3 -> Enable raising of the window when Control-G\n * is received. (enables the popOnBell resource).\n * Ps = 1 0 4 7 -> Use Alternate Screen Buffer. (This may be\n * disabled by the titeInhibit resource).\n * Ps = 1 0 4 8 -> Save cursor as in DECSC. (This may be dis-\n * abled by the titeInhibit resource).\n * Ps = 1 0 4 9 -> Save cursor as in DECSC and use Alternate\n * Screen Buffer, clearing it first. (This may be disabled by\n * the titeInhibit resource). This combines the effects of the 1\n * 0 4 7 and 1 0 4 8 modes. Use this with terminfo-based\n * applications rather than the 4 7 mode.\n * Ps = 1 0 5 0 -> Set terminfo/termcap function-key mode.\n * Ps = 1 0 5 1 -> Set Sun function-key mode.\n * Ps = 1 0 5 2 -> Set HP function-key mode.\n * Ps = 1 0 5 3 -> Set SCO function-key mode.\n * Ps = 1 0 6 0 -> Set legacy keyboard emulation (X11R6).\n * Ps = 1 0 6 1 -> Set VT220 keyboard emulation.\n * Ps = 2 0 0 4 -> Set bracketed paste mode.\n * Modes:\n * http: *vt100.net/docs/vt220-rm/chapter4.html\n */\n public setMode(params: number[]): void {\n if (params.length > 1) {\n for (let i = 0; i < params.length; i++) {\n this.setMode([params[i]]);\n }\n\n return;\n }\n\n if (!this._terminal.prefix) {\n switch (params[0]) {\n case 4:\n this._terminal.insertMode = true;\n break;\n case 20:\n // this._terminal.convertEol = true;\n break;\n }\n } else if (this._terminal.prefix === '?') {\n switch (params[0]) {\n case 1:\n this._terminal.applicationCursor = true;\n break;\n case 2:\n this._terminal.setgCharset(0, DEFAULT_CHARSET);\n this._terminal.setgCharset(1, DEFAULT_CHARSET);\n this._terminal.setgCharset(2, DEFAULT_CHARSET);\n this._terminal.setgCharset(3, DEFAULT_CHARSET);\n // set VT100 mode here\n break;\n case 3: // 132 col mode\n this._terminal.savedCols = this._terminal.cols;\n this._terminal.resize(132, this._terminal.rows);\n break;\n case 6:\n this._terminal.originMode = true;\n break;\n case 7:\n this._terminal.wraparoundMode = true;\n break;\n case 12:\n // this.cursorBlink = true;\n break;\n case 66:\n this._terminal.log('Serial port requested application keypad.');\n this._terminal.applicationKeypad = true;\n this._terminal.viewport.syncScrollArea();\n break;\n case 9: // X10 Mouse\n // no release, no motion, no wheel, no modifiers.\n case 1000: // vt200 mouse\n // no motion.\n // no modifiers, except control on the wheel.\n case 1002: // button event mouse\n case 1003: // any event mouse\n // any event - sends motion events,\n // even if there is no button held down.\n\n // TODO: Why are params[0] compares nested within a switch for params[0]?\n\n this._terminal.x10Mouse = params[0] === 9;\n this._terminal.vt200Mouse = params[0] === 1000;\n this._terminal.normalMouse = params[0] > 1000;\n this._terminal.mouseEvents = true;\n this._terminal.element.classList.add('enable-mouse-events');\n this._terminal.selectionManager.disable();\n this._terminal.log('Binding to mouse events.');\n break;\n case 1004: // send focusin/focusout events\n // focusin: ^[[I\n // focusout: ^[[O\n this._terminal.sendFocus = true;\n break;\n case 1005: // utf8 ext mode mouse\n this._terminal.utfMouse = true;\n // for wide terminals\n // simply encodes large values as utf8 characters\n break;\n case 1006: // sgr ext mode mouse\n this._terminal.sgrMouse = true;\n // for wide terminals\n // does not add 32 to fields\n // press: ^[[ Keyboard Action Mode (AM).\n * Ps = 4 -> Replace Mode (IRM).\n * Ps = 1 2 -> Send/receive (SRM).\n * Ps = 2 0 -> Normal Linefeed (LNM).\n * CSI ? Pm l\n * DEC Private Mode Reset (DECRST).\n * Ps = 1 -> Normal Cursor Keys (DECCKM).\n * Ps = 2 -> Designate VT52 mode (DECANM).\n * Ps = 3 -> 80 Column Mode (DECCOLM).\n * Ps = 4 -> Jump (Fast) Scroll (DECSCLM).\n * Ps = 5 -> Normal Video (DECSCNM).\n * Ps = 6 -> Normal Cursor Mode (DECOM).\n * Ps = 7 -> No Wraparound Mode (DECAWM).\n * Ps = 8 -> No Auto-repeat Keys (DECARM).\n * Ps = 9 -> Don't send Mouse X & Y on button press.\n * Ps = 1 0 -> Hide toolbar (rxvt).\n * Ps = 1 2 -> Stop Blinking Cursor (att610).\n * Ps = 1 8 -> Don't print form feed (DECPFF).\n * Ps = 1 9 -> Limit print to scrolling region (DECPEX).\n * Ps = 2 5 -> Hide Cursor (DECTCEM).\n * Ps = 3 0 -> Don't show scrollbar (rxvt).\n * Ps = 3 5 -> Disable font-shifting functions (rxvt).\n * Ps = 4 0 -> Disallow 80 -> 132 Mode.\n * Ps = 4 1 -> No more(1) fix (see curses resource).\n * Ps = 4 2 -> Disable Nation Replacement Character sets (DEC-\n * NRCM).\n * Ps = 4 4 -> Turn Off Margin Bell.\n * Ps = 4 5 -> No Reverse-wraparound Mode.\n * Ps = 4 6 -> Stop Logging. (This is normally disabled by a\n * compile-time option).\n * Ps = 4 7 -> Use Normal Screen Buffer.\n * Ps = 6 6 -> Numeric keypad (DECNKM).\n * Ps = 6 7 -> Backarrow key sends delete (DECBKM).\n * Ps = 1 0 0 0 -> Don't send Mouse X & Y on button press and\n * release. See the section Mouse Tracking.\n * Ps = 1 0 0 1 -> Don't use Hilite Mouse Tracking.\n * Ps = 1 0 0 2 -> Don't use Cell Motion Mouse Tracking.\n * Ps = 1 0 0 3 -> Don't use All Motion Mouse Tracking.\n * Ps = 1 0 0 4 -> Don't send FocusIn/FocusOut events.\n * Ps = 1 0 0 5 -> Disable Extended Mouse Mode.\n * Ps = 1 0 1 0 -> Don't scroll to bottom on tty output\n * (rxvt).\n * Ps = 1 0 1 1 -> Don't scroll to bottom on key press (rxvt).\n * Ps = 1 0 3 4 -> Don't interpret \"meta\" key. (This disables\n * the eightBitInput resource).\n * Ps = 1 0 3 5 -> Disable special modifiers for Alt and Num-\n * Lock keys. (This disables the numLock resource).\n * Ps = 1 0 3 6 -> Don't send ESC when Meta modifies a key.\n * (This disables the metaSendsEscape resource).\n * Ps = 1 0 3 7 -> Send VT220 Remove from the editing-keypad\n * Delete key.\n * Ps = 1 0 3 9 -> Don't send ESC when Alt modifies a key.\n * (This disables the altSendsEscape resource).\n * Ps = 1 0 4 0 -> Do not keep selection when not highlighted.\n * (This disables the keepSelection resource).\n * Ps = 1 0 4 1 -> Use the PRIMARY selection. (This disables\n * the selectToClipboard resource).\n * Ps = 1 0 4 2 -> Disable Urgency window manager hint when\n * Control-G is received. (This disables the bellIsUrgent\n * resource).\n * Ps = 1 0 4 3 -> Disable raising of the window when Control-\n * G is received. (This disables the popOnBell resource).\n * Ps = 1 0 4 7 -> Use Normal Screen Buffer, clearing screen\n * first if in the Alternate Screen. (This may be disabled by\n * the titeInhibit resource).\n * Ps = 1 0 4 8 -> Restore cursor as in DECRC. (This may be\n * disabled by the titeInhibit resource).\n * Ps = 1 0 4 9 -> Use Normal Screen Buffer and restore cursor\n * as in DECRC. (This may be disabled by the titeInhibit\n * resource). This combines the effects of the 1 0 4 7 and 1 0\n * 4 8 modes. Use this with terminfo-based applications rather\n * than the 4 7 mode.\n * Ps = 1 0 5 0 -> Reset terminfo/termcap function-key mode.\n * Ps = 1 0 5 1 -> Reset Sun function-key mode.\n * Ps = 1 0 5 2 -> Reset HP function-key mode.\n * Ps = 1 0 5 3 -> Reset SCO function-key mode.\n * Ps = 1 0 6 0 -> Reset legacy keyboard emulation (X11R6).\n * Ps = 1 0 6 1 -> Reset keyboard emulation to Sun/PC style.\n * Ps = 2 0 0 4 -> Reset bracketed paste mode.\n */\n public resetMode(params: number[]): void {\n if (params.length > 1) {\n for (let i = 0; i < params.length; i++) {\n this.resetMode([params[i]]);\n }\n\n return;\n }\n\n if (!this._terminal.prefix) {\n switch (params[0]) {\n case 4:\n this._terminal.insertMode = false;\n break;\n case 20:\n // this._terminal.convertEol = false;\n break;\n }\n } else if (this._terminal.prefix === '?') {\n switch (params[0]) {\n case 1:\n this._terminal.applicationCursor = false;\n break;\n case 3:\n if (this._terminal.cols === 132 && this._terminal.savedCols) {\n this._terminal.resize(this._terminal.savedCols, this._terminal.rows);\n }\n delete this._terminal.savedCols;\n break;\n case 6:\n this._terminal.originMode = false;\n break;\n case 7:\n this._terminal.wraparoundMode = false;\n break;\n case 12:\n // this.cursorBlink = false;\n break;\n case 66:\n this._terminal.log('Switching back to normal keypad.');\n this._terminal.applicationKeypad = false;\n this._terminal.viewport.syncScrollArea();\n break;\n case 9: // X10 Mouse\n case 1000: // vt200 mouse\n case 1002: // button event mouse\n case 1003: // any event mouse\n this._terminal.x10Mouse = false;\n this._terminal.vt200Mouse = false;\n this._terminal.normalMouse = false;\n this._terminal.mouseEvents = false;\n this._terminal.element.classList.remove('enable-mouse-events');\n this._terminal.selectionManager.enable();\n break;\n case 1004: // send focusin/focusout events\n this._terminal.sendFocus = false;\n break;\n case 1005: // utf8 ext mode mouse\n this._terminal.utfMouse = false;\n break;\n case 1006: // sgr ext mode mouse\n this._terminal.sgrMouse = false;\n break;\n case 1015: // urxvt ext mode mouse\n this._terminal.urxvtMouse = false;\n break;\n case 25: // hide cursor\n this._terminal.cursorHidden = true;\n break;\n case 1049: // alt screen buffer cursor\n // FALL-THROUGH\n case 47: // normal screen buffer\n case 1047: // normal screen buffer - clearing it first\n // Ensure the selection manager has the correct buffer\n this._terminal.buffers.activateNormalBuffer();\n // TODO: Not sure if we need to save/restore after switching the buffer\n // if (params[0] === 1049) {\n // this.restoreCursor(params);\n // }\n this._terminal.selectionManager.setBuffer(this._terminal.buffer.lines);\n this._terminal.refresh(0, this._terminal.rows - 1);\n this._terminal.viewport.syncScrollArea();\n this._terminal.showCursor();\n break;\n }\n }\n }\n\n /**\n * CSI Pm m Character Attributes (SGR).\n * Ps = 0 -> Normal (default).\n * Ps = 1 -> Bold.\n * Ps = 4 -> Underlined.\n * Ps = 5 -> Blink (appears as Bold).\n * Ps = 7 -> Inverse.\n * Ps = 8 -> Invisible, i.e., hidden (VT300).\n * Ps = 2 2 -> Normal (neither bold nor faint).\n * Ps = 2 4 -> Not underlined.\n * Ps = 2 5 -> Steady (not blinking).\n * Ps = 2 7 -> Positive (not inverse).\n * Ps = 2 8 -> Visible, i.e., not hidden (VT300).\n * Ps = 3 0 -> Set foreground color to Black.\n * Ps = 3 1 -> Set foreground color to Red.\n * Ps = 3 2 -> Set foreground color to Green.\n * Ps = 3 3 -> Set foreground color to Yellow.\n * Ps = 3 4 -> Set foreground color to Blue.\n * Ps = 3 5 -> Set foreground color to Magenta.\n * Ps = 3 6 -> Set foreground color to Cyan.\n * Ps = 3 7 -> Set foreground color to White.\n * Ps = 3 9 -> Set foreground color to default (original).\n * Ps = 4 0 -> Set background color to Black.\n * Ps = 4 1 -> Set background color to Red.\n * Ps = 4 2 -> Set background color to Green.\n * Ps = 4 3 -> Set background color to Yellow.\n * Ps = 4 4 -> Set background color to Blue.\n * Ps = 4 5 -> Set background color to Magenta.\n * Ps = 4 6 -> Set background color to Cyan.\n * Ps = 4 7 -> Set background color to White.\n * Ps = 4 9 -> Set background color to default (original).\n *\n * If 16-color support is compiled, the following apply. Assume\n * that xterm's resources are set so that the ISO color codes are\n * the first 8 of a set of 16. Then the aixterm colors are the\n * bright versions of the ISO colors:\n * Ps = 9 0 -> Set foreground color to Black.\n * Ps = 9 1 -> Set foreground color to Red.\n * Ps = 9 2 -> Set foreground color to Green.\n * Ps = 9 3 -> Set foreground color to Yellow.\n * Ps = 9 4 -> Set foreground color to Blue.\n * Ps = 9 5 -> Set foreground color to Magenta.\n * Ps = 9 6 -> Set foreground color to Cyan.\n * Ps = 9 7 -> Set foreground color to White.\n * Ps = 1 0 0 -> Set background color to Black.\n * Ps = 1 0 1 -> Set background color to Red.\n * Ps = 1 0 2 -> Set background color to Green.\n * Ps = 1 0 3 -> Set background color to Yellow.\n * Ps = 1 0 4 -> Set background color to Blue.\n * Ps = 1 0 5 -> Set background color to Magenta.\n * Ps = 1 0 6 -> Set background color to Cyan.\n * Ps = 1 0 7 -> Set background color to White.\n *\n * If xterm is compiled with the 16-color support disabled, it\n * supports the following, from rxvt:\n * Ps = 1 0 0 -> Set foreground and background color to\n * default.\n *\n * If 88- or 256-color support is compiled, the following apply.\n * Ps = 3 8 ; 5 ; Ps -> Set foreground color to the second\n * Ps.\n * Ps = 4 8 ; 5 ; Ps -> Set background color to the second\n * Ps.\n */\n public charAttributes(params: number[]): void {\n // Optimize a single SGR0.\n if (params.length === 1 && params[0] === 0) {\n this._terminal.curAttr = this._terminal.defAttr;\n return;\n }\n\n let l = params.length\n , i = 0\n , flags = this._terminal.curAttr >> 18\n , fg = (this._terminal.curAttr >> 9) & 0x1ff\n , bg = this._terminal.curAttr & 0x1ff\n , p;\n\n for (; i < l; i++) {\n p = params[i];\n if (p >= 30 && p <= 37) {\n // fg color 8\n fg = p - 30;\n } else if (p >= 40 && p <= 47) {\n // bg color 8\n bg = p - 40;\n } else if (p >= 90 && p <= 97) {\n // fg color 16\n p += 8;\n fg = p - 90;\n } else if (p >= 100 && p <= 107) {\n // bg color 16\n p += 8;\n bg = p - 100;\n } else if (p === 0) {\n // default\n flags = this._terminal.defAttr >> 18;\n fg = (this._terminal.defAttr >> 9) & 0x1ff;\n bg = this._terminal.defAttr & 0x1ff;\n // flags = 0;\n // fg = 0x1ff;\n // bg = 0x1ff;\n } else if (p === 1) {\n // bold text\n flags |= 1;\n } else if (p === 4) {\n // underlined text\n flags |= 2;\n } else if (p === 5) {\n // blink\n flags |= 4;\n } else if (p === 7) {\n // inverse and positive\n // test with: echo -e '\\e[31m\\e[42mhello\\e[7mworld\\e[27mhi\\e[m'\n flags |= 8;\n } else if (p === 8) {\n // invisible\n flags |= 16;\n } else if (p === 22) {\n // not bold\n flags &= ~1;\n } else if (p === 24) {\n // not underlined\n flags &= ~2;\n } else if (p === 25) {\n // not blink\n flags &= ~4;\n } else if (p === 27) {\n // not inverse\n flags &= ~8;\n } else if (p === 28) {\n // not invisible\n flags &= ~16;\n } else if (p === 39) {\n // reset fg\n fg = (this._terminal.defAttr >> 9) & 0x1ff;\n } else if (p === 49) {\n // reset bg\n bg = this._terminal.defAttr & 0x1ff;\n } else if (p === 38) {\n // fg color 256\n if (params[i + 1] === 2) {\n i += 2;\n fg = this._terminal.matchColor(\n params[i] & 0xff,\n params[i + 1] & 0xff,\n params[i + 2] & 0xff);\n if (fg === -1) fg = 0x1ff;\n i += 2;\n } else if (params[i + 1] === 5) {\n i += 2;\n p = params[i] & 0xff;\n fg = p;\n }\n } else if (p === 48) {\n // bg color 256\n if (params[i + 1] === 2) {\n i += 2;\n bg = this._terminal.matchColor(\n params[i] & 0xff,\n params[i + 1] & 0xff,\n params[i + 2] & 0xff);\n if (bg === -1) bg = 0x1ff;\n i += 2;\n } else if (params[i + 1] === 5) {\n i += 2;\n p = params[i] & 0xff;\n bg = p;\n }\n } else if (p === 100) {\n // reset fg/bg\n fg = (this._terminal.defAttr >> 9) & 0x1ff;\n bg = this._terminal.defAttr & 0x1ff;\n } else {\n this._terminal.error('Unknown SGR attribute: %d.', p);\n }\n }\n\n this._terminal.curAttr = (flags << 18) | (fg << 9) | bg;\n }\n\n /**\n * CSI Ps n Device Status Report (DSR).\n * Ps = 5 -> Status Report. Result (``OK'') is\n * CSI 0 n\n * Ps = 6 -> Report Cursor Position (CPR) [row;column].\n * Result is\n * CSI r ; c R\n * CSI ? Ps n\n * Device Status Report (DSR, DEC-specific).\n * Ps = 6 -> Report Cursor Position (CPR) [row;column] as CSI\n * ? r ; c R (assumes page is zero).\n * Ps = 1 5 -> Report Printer status as CSI ? 1 0 n (ready).\n * or CSI ? 1 1 n (not ready).\n * Ps = 2 5 -> Report UDK status as CSI ? 2 0 n (unlocked)\n * or CSI ? 2 1 n (locked).\n * Ps = 2 6 -> Report Keyboard status as\n * CSI ? 2 7 ; 1 ; 0 ; 0 n (North American).\n * The last two parameters apply to VT400 & up, and denote key-\n * board ready and LK01 respectively.\n * Ps = 5 3 -> Report Locator status as\n * CSI ? 5 3 n Locator available, if compiled-in, or\n * CSI ? 5 0 n No Locator, if not.\n */\n public deviceStatus(params: number[]): void {\n if (!this._terminal.prefix) {\n switch (params[0]) {\n case 5:\n // status report\n this._terminal.send(C0.ESC + '[0n');\n break;\n case 6:\n // cursor position\n this._terminal.send(C0.ESC + '['\n + (this._terminal.buffer.y + 1)\n + ';'\n + (this._terminal.buffer.x + 1)\n + 'R');\n break;\n }\n } else if (this._terminal.prefix === '?') {\n // modern xterm doesnt seem to\n // respond to any of these except ?6, 6, and 5\n switch (params[0]) {\n case 6:\n // cursor position\n this._terminal.send(C0.ESC + '[?'\n + (this._terminal.buffer.y + 1)\n + ';'\n + (this._terminal.buffer.x + 1)\n + 'R');\n break;\n case 15:\n // no printer\n // this.send(C0.ESC + '[?11n');\n break;\n case 25:\n // dont support user defined keys\n // this.send(C0.ESC + '[?21n');\n break;\n case 26:\n // north american keyboard\n // this.send(C0.ESC + '[?27;1;0;0n');\n break;\n case 53:\n // no dec locator/mouse\n // this.send(C0.ESC + '[?50n');\n break;\n }\n }\n }\n\n /**\n * CSI ! p Soft terminal reset (DECSTR).\n * http://vt100.net/docs/vt220-rm/table4-10.html\n */\n public softReset(params: number[]): void {\n this._terminal.cursorHidden = false;\n this._terminal.insertMode = false;\n this._terminal.originMode = false;\n this._terminal.wraparoundMode = true; // defaults: xterm - true, vt100 - false\n this._terminal.applicationKeypad = false; // ?\n this._terminal.viewport.syncScrollArea();\n this._terminal.applicationCursor = false;\n this._terminal.buffer.scrollTop = 0;\n this._terminal.buffer.scrollBottom = this._terminal.rows - 1;\n this._terminal.curAttr = this._terminal.defAttr;\n this._terminal.buffer.x = this._terminal.buffer.y = 0; // ?\n this._terminal.charset = null;\n this._terminal.glevel = 0; // ??\n this._terminal.charsets = [null]; // ??\n }\n\n /**\n * CSI Ps SP q Set cursor style (DECSCUSR, VT520).\n * Ps = 0 -> blinking block.\n * Ps = 1 -> blinking block (default).\n * Ps = 2 -> steady block.\n * Ps = 3 -> blinking underline.\n * Ps = 4 -> steady underline.\n * Ps = 5 -> blinking bar (xterm).\n * Ps = 6 -> steady bar (xterm).\n */\n public setCursorStyle(params?: number[]): void {\n const param = params[0] < 1 ? 1 : params[0];\n switch (param) {\n case 1:\n case 2:\n this._terminal.setOption('cursorStyle', 'block');\n break;\n case 3:\n case 4:\n this._terminal.setOption('cursorStyle', 'underline');\n break;\n case 5:\n case 6:\n this._terminal.setOption('cursorStyle', 'bar');\n break;\n }\n const isBlinking = param % 2 === 1;\n this._terminal.setOption('cursorBlink', isBlinking);\n }\n\n /**\n * CSI Ps ; Ps r\n * Set Scrolling Region [top;bottom] (default = full size of win-\n * dow) (DECSTBM).\n * CSI ? Pm r\n */\n public setScrollRegion(params: number[]): void {\n if (this._terminal.prefix) return;\n this._terminal.buffer.scrollTop = (params[0] || 1) - 1;\n this._terminal.buffer.scrollBottom = (params[1] && params[1] <= this._terminal.rows ? params[1] : this._terminal.rows) - 1;\n this._terminal.buffer.x = 0;\n this._terminal.buffer.y = 0;\n }\n\n\n /**\n * CSI s\n * Save cursor (ANSI.SYS).\n */\n public saveCursor(params: number[]): void {\n this._terminal.buffer.savedX = this._terminal.buffer.x;\n this._terminal.buffer.savedY = this._terminal.buffer.y;\n }\n\n\n /**\n * CSI u\n * Restore cursor (ANSI.SYS).\n */\n public restoreCursor(params: number[]): void {\n this._terminal.buffer.x = this._terminal.buffer.savedX || 0;\n this._terminal.buffer.y = this._terminal.buffer.savedY || 0;\n }\n}\n\nexport const wcwidth = (function(opts) {\n // extracted from https://www.cl.cam.ac.uk/%7Emgk25/ucs/wcwidth.c\n // combining characters\n const COMBINING_BMP = [\n [0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489],\n [0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2],\n [0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603],\n [0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670],\n [0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED],\n [0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A],\n [0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902],\n [0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D],\n [0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981],\n [0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD],\n [0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C],\n [0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D],\n [0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC],\n [0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD],\n [0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C],\n [0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D],\n [0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0],\n [0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48],\n [0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC],\n [0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD],\n [0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D],\n [0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6],\n [0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E],\n [0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC],\n [0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35],\n [0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E],\n [0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97],\n [0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030],\n [0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039],\n [0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F],\n [0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753],\n [0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD],\n [0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD],\n [0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922],\n [0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B],\n [0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34],\n [0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42],\n [0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF],\n [0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063],\n [0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F],\n [0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B],\n [0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F],\n [0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB],\n ];\n const COMBINING_HIGH = [\n [0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F],\n [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169],\n [0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD],\n [0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F],\n [0xE0100, 0xE01EF]\n ];\n // binary search\n function bisearch(ucs, data) {\n let min = 0;\n let max = data.length - 1;\n let mid;\n if (ucs < data[0][0] || ucs > data[max][1])\n return false;\n while (max >= min) {\n mid = (min + max) >> 1;\n if (ucs > data[mid][1])\n min = mid + 1;\n else if (ucs < data[mid][0])\n max = mid - 1;\n else\n return true;\n }\n return false;\n }\n function wcwidthBMP(ucs) {\n // test for 8-bit control characters\n if (ucs === 0)\n return opts.nul;\n if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0))\n return opts.control;\n // binary search in table of non-spacing characters\n if (bisearch(ucs, COMBINING_BMP))\n return 0;\n // if we arrive here, ucs is not a combining or C0/C1 control character\n if (isWideBMP(ucs)) {\n return 2;\n }\n return 1;\n }\n function isWideBMP(ucs) {\n return (\n ucs >= 0x1100 && (\n ucs <= 0x115f || // Hangul Jamo init. consonants\n ucs === 0x2329 ||\n ucs === 0x232a ||\n (ucs >= 0x2e80 && ucs <= 0xa4cf && ucs !== 0x303f) || // CJK..Yi\n (ucs >= 0xac00 && ucs <= 0xd7a3) || // Hangul Syllables\n (ucs >= 0xf900 && ucs <= 0xfaff) || // CJK Compat Ideographs\n (ucs >= 0xfe10 && ucs <= 0xfe19) || // Vertical forms\n (ucs >= 0xfe30 && ucs <= 0xfe6f) || // CJK Compat Forms\n (ucs >= 0xff00 && ucs <= 0xff60) || // Fullwidth Forms\n (ucs >= 0xffe0 && ucs <= 0xffe6)));\n }\n function wcwidthHigh(ucs) {\n if (bisearch(ucs, COMBINING_HIGH))\n return 0;\n if ((ucs >= 0x20000 && ucs <= 0x2fffd) || (ucs >= 0x30000 && ucs <= 0x3fffd)) {\n return 2;\n }\n return 1;\n }\n const control = opts.control | 0;\n let table = null;\n function init_table() {\n // lookup table for BMP\n const CODEPOINTS = 65536; // BMP holds 65536 codepoints\n const BITWIDTH = 2; // a codepoint can have a width of 0, 1 or 2\n const ITEMSIZE = 32; // using uint32_t\n const CONTAINERSIZE = CODEPOINTS * BITWIDTH / ITEMSIZE;\n const CODEPOINTS_PER_ITEM = ITEMSIZE / BITWIDTH;\n table = (typeof Uint32Array === 'undefined')\n ? new Array(CONTAINERSIZE)\n : new Uint32Array(CONTAINERSIZE);\n for (let i = 0; i < CONTAINERSIZE; ++i) {\n let num = 0;\n let pos = CODEPOINTS_PER_ITEM;\n while (pos--)\n num = (num << 2) | wcwidthBMP(CODEPOINTS_PER_ITEM * i + pos);\n table[i] = num;\n }\n return table;\n }\n // get width from lookup table\n // position in container : num / CODEPOINTS_PER_ITEM\n // ==> n = table[Math.floor(num / 16)]\n // ==> n = table[num >> 4]\n // 16 codepoints per number: FFEEDDCCBBAA99887766554433221100\n // position in number : (num % CODEPOINTS_PER_ITEM) * BITWIDTH\n // ==> m = (n % 16) * 2\n // ==> m = (num & 15) << 1\n // right shift to position m\n // ==> n = n >> m e.g. m=12 000000000000FFEEDDCCBBAA99887766\n // we are only interested in 2 LSBs, cut off higher bits\n // ==> n = n & 3 e.g. 000000000000000000000000000000XX\n return function (num) {\n num = num | 0; // get asm.js like optimization under V8\n if (num < 32)\n return control | 0;\n if (num < 127)\n return 1;\n let t = table || init_table();\n if (num < 65536)\n return t[num >> 4] >> ((num & 15) << 1) & 3;\n // do a full search for high codepoints\n return wcwidthHigh(num);\n };\n})({nul: 0, control: 0}); // configurable options\n","/**\n * @license MIT\n */\n\nimport { IEventEmitter } from './Interfaces';\n\ninterface ListenerType {\n (): void;\n listener?: () => void;\n};\n\nexport class EventEmitter implements IEventEmitter {\n private _events: {[type: string]: ListenerType[]};\n\n constructor() {\n // Restore the previous events if available, this will happen if the\n // constructor is called multiple times on the same object (terminal reset).\n this._events = this._events || {};\n }\n\n public on(type, listener): void {\n this._events[type] = this._events[type] || [];\n this._events[type].push(listener);\n }\n\n public off(type, listener): void {\n if (!this._events[type]) {\n return;\n }\n\n let obj = this._events[type];\n let i = obj.length;\n\n while (i--) {\n if (obj[i] === listener || obj[i].listener === listener) {\n obj.splice(i, 1);\n return;\n }\n }\n }\n\n public removeAllListeners(type): void {\n if (this._events[type]) {\n delete this._events[type];\n }\n }\n\n public once(type, listener): any {\n function on() {\n let args = Array.prototype.slice.call(arguments);\n this.off(type, on);\n return listener.apply(this, args);\n }\n (on).listener = listener;\n return this.on(type, on);\n }\n\n public emit(type: string, ...args: any[]): void {\n if (!this._events[type]) {\n return;\n }\n let obj = this._events[type];\n for (let i = 0; i < obj.length; i++) {\n obj[i].apply(this, args);\n }\n }\n\n public listeners(type): ListenerType[] {\n return this._events[type] || [];\n }\n}\n","/**\n * @license MIT\n */\n\n/**\n * C0 control codes\n * See = https://en.wikipedia.org/wiki/C0_and_C1_control_codes\n */\nexport namespace C0 {\n /** Null (Caret = ^@, C = \\0) */\n export const NUL = '\\x00';\n /** Start of Heading (Caret = ^A) */\n export const SOH = '\\x01';\n /** Start of Text (Caret = ^B) */\n export const STX = '\\x02';\n /** End of Text (Caret = ^C) */\n export const ETX = '\\x03';\n /** End of Transmission (Caret = ^D) */\n export const EOT = '\\x04';\n /** Enquiry (Caret = ^E) */\n export const ENQ = '\\x05';\n /** Acknowledge (Caret = ^F) */\n export const ACK = '\\x06';\n /** Bell (Caret = ^G, C = \\a) */\n export const BEL = '\\x07';\n /** Backspace (Caret = ^H, C = \\b) */\n export const BS = '\\x08';\n /** Character Tabulation, Horizontal Tabulation (Caret = ^I, C = \\t) */\n export const HT = '\\x09';\n /** Line Feed (Caret = ^J, C = \\n) */\n export const LF = '\\x0a';\n /** Line Tabulation, Vertical Tabulation (Caret = ^K, C = \\v) */\n export const VT = '\\x0b';\n /** Form Feed (Caret = ^L, C = \\f) */\n export const FF = '\\x0c';\n /** Carriage Return (Caret = ^M, C = \\r) */\n export const CR = '\\x0d';\n /** Shift Out (Caret = ^N) */\n export const SO = '\\x0e';\n /** Shift In (Caret = ^O) */\n export const SI = '\\x0f';\n /** Data Link Escape (Caret = ^P) */\n export const DLE = '\\x10';\n /** Device Control One (XON) (Caret = ^Q) */\n export const DC1 = '\\x11';\n /** Device Control Two (Caret = ^R) */\n export const DC2 = '\\x12';\n /** Device Control Three (XOFF) (Caret = ^S) */\n export const DC3 = '\\x13';\n /** Device Control Four (Caret = ^T) */\n export const DC4 = '\\x14';\n /** Negative Acknowledge (Caret = ^U) */\n export const NAK = '\\x15';\n /** Synchronous Idle (Caret = ^V) */\n export const SYN = '\\x16';\n /** End of Transmission Block (Caret = ^W) */\n export const ETB = '\\x17';\n /** Cancel (Caret = ^X) */\n export const CAN = '\\x18';\n /** End of Medium (Caret = ^Y) */\n export const EM = '\\x19';\n /** Substitute (Caret = ^Z) */\n export const SUB = '\\x1a';\n /** Escape (Caret = ^[, C = \\e) */\n export const ESC = '\\x1b';\n /** File Separator (Caret = ^\\) */\n export const FS = '\\x1c';\n /** Group Separator (Caret = ^]) */\n export const GS = '\\x1d';\n /** Record Separator (Caret = ^^) */\n export const RS = '\\x1e';\n /** Unit Separator (Caret = ^_) */\n export const US = '\\x1f';\n /** Space */\n export const SP = '\\x20';\n /** Delete (Caret = ^?) */\n export const DEL = '\\x7f';\n};\n","/**\n * @license MIT\n */\n\nimport { ITerminal } from './Interfaces';\n\ninterface IPosition {\n start: number;\n end: number;\n}\n\n/**\n * Encapsulates the logic for handling compositionstart, compositionupdate and compositionend\n * events, displaying the in-progress composition to the UI and forwarding the final composition\n * to the handler.\n */\nexport class CompositionHelper {\n /**\n * Whether input composition is currently happening, eg. via a mobile keyboard, speech input or\n * IME. This variable determines whether the compositionText should be displayed on the UI.\n */\n private isComposing: boolean;\n\n /**\n * The position within the input textarea's value of the current composition.\n */\n private compositionPosition: IPosition;\n\n /**\n * Whether a composition is in the process of being sent, setting this to false will cancel any\n * in-progress composition.\n */\n private isSendingComposition: boolean;\n\n /**\n * Creates a new CompositionHelper.\n * @param textarea The textarea that xterm uses for input.\n * @param compositionView The element to display the in-progress composition in.\n * @param terminal The Terminal to forward the finished composition to.\n */\n constructor(\n private textarea: HTMLTextAreaElement,\n private compositionView: HTMLElement,\n private terminal: ITerminal\n ) {\n this.isComposing = false;\n this.isSendingComposition = false;\n this.compositionPosition = { start: null, end: null };\n }\n\n /**\n * Handles the compositionstart event, activating the composition view.\n */\n public compositionstart() {\n this.isComposing = true;\n this.compositionPosition.start = this.textarea.value.length;\n this.compositionView.textContent = '';\n this.compositionView.classList.add('active');\n }\n\n /**\n * Handles the compositionupdate event, updating the composition view.\n * @param {CompositionEvent} ev The event.\n */\n public compositionupdate(ev: CompositionEvent) {\n this.compositionView.textContent = ev.data;\n this.updateCompositionElements();\n setTimeout(() => {\n this.compositionPosition.end = this.textarea.value.length;\n }, 0);\n }\n\n /**\n * Handles the compositionend event, hiding the composition view and sending the composition to\n * the handler.\n */\n public compositionend() {\n this.finalizeComposition(true);\n }\n\n /**\n * Handles the keydown event, routing any necessary events to the CompositionHelper functions.\n * @param ev The keydown event.\n * @return Whether the Terminal should continue processing the keydown event.\n */\n public keydown(ev: KeyboardEvent) {\n if (this.isComposing || this.isSendingComposition) {\n if (ev.keyCode === 229) {\n // Continue composing if the keyCode is the \"composition character\"\n return false;\n } else if (ev.keyCode === 16 || ev.keyCode === 17 || ev.keyCode === 18) {\n // Continue composing if the keyCode is a modifier key\n return false;\n } else {\n // Finish composition immediately. This is mainly here for the case where enter is\n // pressed and the handler needs to be triggered before the command is executed.\n this.finalizeComposition(false);\n }\n }\n\n if (ev.keyCode === 229) {\n // If the \"composition character\" is used but gets to this point it means a non-composition\n // character (eg. numbers and punctuation) was pressed when the IME was active.\n this.handleAnyTextareaChanges();\n return false;\n }\n\n return true;\n }\n\n /**\n * Finalizes the composition, resuming regular input actions. This is called when a composition\n * is ending.\n * @param waitForPropogation Whether to wait for events to propogate before sending\n * the input. This should be false if a non-composition keystroke is entered before the\n * compositionend event is triggered, such as enter, so that the composition is send before\n * the command is executed.\n */\n private finalizeComposition(waitForPropogation: boolean) {\n this.compositionView.classList.remove('active');\n this.isComposing = false;\n this.clearTextareaPosition();\n\n if (!waitForPropogation) {\n // Cancel any delayed composition send requests and send the input immediately.\n this.isSendingComposition = false;\n const input = this.textarea.value.substring(this.compositionPosition.start, this.compositionPosition.end);\n this.terminal.handler(input);\n } else {\n // Make a deep copy of the composition position here as a new compositionstart event may\n // fire before the setTimeout executes.\n const currentCompositionPosition = {\n start: this.compositionPosition.start,\n end: this.compositionPosition.end,\n };\n\n // Since composition* events happen before the changes take place in the textarea on most\n // browsers, use a setTimeout with 0ms time to allow the native compositionend event to\n // complete. This ensures the correct character is retrieved, this solution was used\n // because:\n // - The compositionend event's data property is unreliable, at least on Chromium\n // - The last compositionupdate event's data property does not always accurately describe\n // the character, a counter example being Korean where an ending consonsant can move to\n // the following character if the following input is a vowel.\n this.isSendingComposition = true;\n setTimeout(() => {\n // Ensure that the input has not already been sent\n if (this.isSendingComposition) {\n this.isSendingComposition = false;\n let input;\n if (this.isComposing) {\n // Use the end position to get the string if a new composition has started.\n input = this.textarea.value.substring(currentCompositionPosition.start, currentCompositionPosition.end);\n } else {\n // Don't use the end position here in order to pick up any characters after the\n // composition has finished, for example when typing a non-composition character\n // (eg. 2) after a composition character.\n input = this.textarea.value.substring(currentCompositionPosition.start);\n }\n this.terminal.handler(input);\n }\n }, 0);\n }\n }\n\n /**\n * Apply any changes made to the textarea after the current event chain is allowed to complete.\n * This should be called when not currently composing but a keydown event with the \"composition\n * character\" (229) is triggered, in order to allow non-composition text to be entered when an\n * IME is active.\n */\n private handleAnyTextareaChanges() {\n const oldValue = this.textarea.value;\n setTimeout(() => {\n // Ignore if a composition has started since the timeout\n if (!this.isComposing) {\n const newValue = this.textarea.value;\n const diff = newValue.replace(oldValue, '');\n if (diff.length > 0) {\n this.terminal.handler(diff);\n }\n }\n }, 0);\n }\n\n /**\n * Positions the composition view on top of the cursor and the textarea just below it (so the\n * IME helper dialog is positioned correctly).\n * @param dontRecurse Whether to use setTimeout to recursively trigger another update, this is\n * necessary as the IME events across browsers are not consistently triggered.\n */\n public updateCompositionElements(dontRecurse?: boolean) {\n if (!this.isComposing) {\n return;\n }\n const cursor = this.terminal.element.querySelector('.terminal-cursor');\n if (cursor) {\n // Take .xterm-rows offsetTop into account as well in case it's positioned absolutely within\n // the .xterm element.\n const xtermRows = this.terminal.element.querySelector('.xterm-rows');\n const cursorTop = xtermRows.offsetTop + cursor.offsetTop;\n\n this.compositionView.style.left = cursor.offsetLeft + 'px';\n this.compositionView.style.top = cursorTop + 'px';\n this.compositionView.style.height = cursor.offsetHeight + 'px';\n this.compositionView.style.lineHeight = cursor.offsetHeight + 'px';\n // Sync the textarea to the exact position of the composition view so the IME knows where the\n // text is.\n const compositionViewBounds = this.compositionView.getBoundingClientRect();\n this.textarea.style.left = cursor.offsetLeft + 'px';\n this.textarea.style.top = cursorTop + 'px';\n this.textarea.style.width = compositionViewBounds.width + 'px';\n this.textarea.style.height = compositionViewBounds.height + 'px';\n this.textarea.style.lineHeight = compositionViewBounds.height + 'px';\n }\n if (!dontRecurse) {\n setTimeout(() => this.updateCompositionElements(true), 0);\n }\n };\n\n /**\n * Clears the textarea's position so that the cursor does not blink on IE.\n * @private\n */\n private clearTextareaPosition() {\n this.textarea.style.left = '';\n this.textarea.style.top = '';\n };\n}\n","/**\n * @license MIT\n */\n\n/**\n * The character sets supported by the terminal. These enable several languages\n * to be represented within the terminal with only 8-bit encoding. See ISO 2022\n * for a discussion on character sets. Only VT100 character sets are supported.\n */\nexport const CHARSETS: {[key: string]: {[key: string]: string}} = {};\n\n/**\n * The default character set, US.\n */\nexport const DEFAULT_CHARSET = CHARSETS['B'];\n\n/**\n * DEC Special Character and Line Drawing Set.\n * Reference: http://vt100.net/docs/vt102-ug/table5-13.html\n * A lot of curses apps use this if they see TERM=xterm.\n * testing: echo -e '\\e(0a\\e(B'\n * The xterm output sometimes seems to conflict with the\n * reference above. xterm seems in line with the reference\n * when running vttest however.\n * The table below now uses xterm's output from vttest.\n */\nCHARSETS['0'] = {\n '`': '\\u25c6', // '◆'\n 'a': '\\u2592', // '▒'\n 'b': '\\u0009', // '\\t'\n 'c': '\\u000c', // '\\f'\n 'd': '\\u000d', // '\\r'\n 'e': '\\u000a', // '\\n'\n 'f': '\\u00b0', // '°'\n 'g': '\\u00b1', // '±'\n 'h': '\\u2424', // '\\u2424' (NL)\n 'i': '\\u000b', // '\\v'\n 'j': '\\u2518', // '┘'\n 'k': '\\u2510', // '┐'\n 'l': '\\u250c', // '┌'\n 'm': '\\u2514', // '└'\n 'n': '\\u253c', // '┼'\n 'o': '\\u23ba', // '⎺'\n 'p': '\\u23bb', // '⎻'\n 'q': '\\u2500', // '─'\n 'r': '\\u23bc', // '⎼'\n 's': '\\u23bd', // '⎽'\n 't': '\\u251c', // '├'\n 'u': '\\u2524', // '┤'\n 'v': '\\u2534', // '┴'\n 'w': '\\u252c', // '┬'\n 'x': '\\u2502', // '│'\n 'y': '\\u2264', // '≤'\n 'z': '\\u2265', // '≥'\n '{': '\\u03c0', // 'π'\n '|': '\\u2260', // '≠'\n '}': '\\u00a3', // '£'\n '~': '\\u00b7' // '·'\n};\n\n/**\n * British character set\n * ESC (A\n * Reference: http://vt100.net/docs/vt220-rm/table2-5.html\n */\nCHARSETS['A'] = {\n '#': '£'\n};\n\n/**\n * United States character set\n * ESC (B\n */\nCHARSETS['B'] = null;\n\n/**\n * Dutch character set\n * ESC (4\n * Reference: http://vt100.net/docs/vt220-rm/table2-6.html\n */\nCHARSETS['4'] = {\n '#': '£',\n '@': '¾',\n '[': 'ij',\n '\\\\': '½',\n ']': '|',\n '{': '¨',\n '|': 'f',\n '}': '¼',\n '~': '´'\n};\n\n/**\n * Finnish character set\n * ESC (C or ESC (5\n * Reference: http://vt100.net/docs/vt220-rm/table2-7.html\n */\nCHARSETS['C'] =\nCHARSETS['5'] = {\n '[': 'Ä',\n '\\\\': 'Ö',\n ']': 'Å',\n '^': 'Ü',\n '`': 'é',\n '{': 'ä',\n '|': 'ö',\n '}': 'å',\n '~': 'ü'\n};\n\n/**\n * French character set\n * ESC (R\n * Reference: http://vt100.net/docs/vt220-rm/table2-8.html\n */\nCHARSETS['R'] = {\n '#': '£',\n '@': 'à',\n '[': '°',\n '\\\\': 'ç',\n ']': '§',\n '{': 'é',\n '|': 'ù',\n '}': 'è',\n '~': '¨'\n};\n\n/**\n * French Canadian character set\n * ESC (Q\n * Reference: http://vt100.net/docs/vt220-rm/table2-9.html\n */\nCHARSETS['Q'] = {\n '@': 'à',\n '[': 'â',\n '\\\\': 'ç',\n ']': 'ê',\n '^': 'î',\n '`': 'ô',\n '{': 'é',\n '|': 'ù',\n '}': 'è',\n '~': 'û'\n};\n\n/**\n * German character set\n * ESC (K\n * Reference: http://vt100.net/docs/vt220-rm/table2-10.html\n */\nCHARSETS['K'] = {\n '@': '§',\n '[': 'Ä',\n '\\\\': 'Ö',\n ']': 'Ü',\n '{': 'ä',\n '|': 'ö',\n '}': 'ü',\n '~': 'ß'\n};\n\n/**\n * Italian character set\n * ESC (Y\n * Reference: http://vt100.net/docs/vt220-rm/table2-11.html\n */\nCHARSETS['Y'] = {\n '#': '£',\n '@': '§',\n '[': '°',\n '\\\\': 'ç',\n ']': 'é',\n '`': 'ù',\n '{': 'à',\n '|': 'ò',\n '}': 'è',\n '~': 'ì'\n};\n\n/**\n * Norwegian/Danish character set\n * ESC (E or ESC (6\n * Reference: http://vt100.net/docs/vt220-rm/table2-12.html\n */\nCHARSETS['E'] =\nCHARSETS['6'] = {\n '@': 'Ä',\n '[': 'Æ',\n '\\\\': 'Ø',\n ']': 'Å',\n '^': 'Ü',\n '`': 'ä',\n '{': 'æ',\n '|': 'ø',\n '}': 'å',\n '~': 'ü'\n};\n\n/**\n * Spanish character set\n * ESC (Z\n * Reference: http://vt100.net/docs/vt220-rm/table2-13.html\n */\nCHARSETS['Z'] = {\n '#': '£',\n '@': '§',\n '[': '¡',\n '\\\\': 'Ñ',\n ']': '¿',\n '{': '°',\n '|': 'ñ',\n '}': 'ç'\n};\n\n/**\n * Swedish character set\n * ESC (H or ESC (7\n * Reference: http://vt100.net/docs/vt220-rm/table2-14.html\n */\nCHARSETS['H'] =\nCHARSETS['7'] = {\n '@': 'É',\n '[': 'Ä',\n '\\\\': 'Ö',\n ']': 'Å',\n '^': 'Ü',\n '`': 'é',\n '{': 'ä',\n '|': 'ö',\n '}': 'å',\n '~': 'ü'\n};\n\n/**\n * Swiss character set\n * ESC (=\n * Reference: http://vt100.net/docs/vt220-rm/table2-15.html\n */\nCHARSETS['='] = {\n '#': 'ù',\n '@': 'à',\n '[': 'é',\n '\\\\': 'ç',\n ']': 'ê',\n '^': 'î',\n '_': 'è',\n '`': 'ô',\n '{': 'ä',\n '|': 'ö',\n '}': 'ü',\n '~': 'û'\n};\n","/**\n * @license MIT\n */\n\nimport { ITerminal, IBufferSet } from './Interfaces';\nimport { Buffer } from './Buffer';\nimport { EventEmitter } from './EventEmitter';\n\n/**\n * The BufferSet represents the set of two buffers used by xterm terminals (normal and alt) and\n * provides also utilities for working with them.\n */\nexport class BufferSet extends EventEmitter implements IBufferSet {\n private _normal: Buffer;\n private _alt: Buffer;\n private _activeBuffer: Buffer;\n\n /**\n * Create a new BufferSet for the given terminal.\n * @param {Terminal} terminal - The terminal the BufferSet will belong to\n */\n constructor(private _terminal: ITerminal) {\n super();\n this._normal = new Buffer(this._terminal);\n this._normal.fillViewportRows();\n this._alt = new Buffer(this._terminal);\n this._activeBuffer = this._normal;\n }\n\n /**\n * Returns the alt Buffer of the BufferSet\n * @returns {Buffer}\n */\n public get alt(): Buffer {\n return this._alt;\n }\n\n /**\n * Returns the normal Buffer of the BufferSet\n * @returns {Buffer}\n */\n public get active(): Buffer {\n return this._activeBuffer;\n }\n\n /**\n * Returns the currently active Buffer of the BufferSet\n * @returns {Buffer}\n */\n public get normal(): Buffer {\n return this._normal;\n }\n\n /**\n * Sets the normal Buffer of the BufferSet as its currently active Buffer\n */\n public activateNormalBuffer(): void {\n // The alt buffer should always be cleared when we switch to the normal\n // buffer. This frees up memory since the alt buffer should always be new\n // when activated.\n this._alt.clear();\n\n this._activeBuffer = this._normal;\n this.emit('activate', this._normal);\n }\n\n /**\n * Sets the alt Buffer of the BufferSet as its currently active Buffer\n */\n public activateAltBuffer(): void {\n // Since the alt buffer is always cleared when the normal buffer is\n // activated, we want to fill it when switching to it.\n this._alt.fillViewportRows();\n\n this._activeBuffer = this._alt;\n this.emit('activate', this._alt);\n }\n\n /**\n * Resizes both normal and alt buffers, adjusting their data accordingly.\n * @param newCols The new number of columns.\n * @param newRows The new number of rows.\n */\n public resize(newCols: number, newRows: number): void {\n this._normal.resize(newCols, newRows);\n this._alt.resize(newCols, newRows);\n }\n}\n","/**\n * @license MIT\n */\n\nimport { ITerminal, IBuffer } from './Interfaces';\nimport { CircularList } from './utils/CircularList';\n\n/**\n * This class represents a terminal buffer (an internal state of the terminal), where the\n * following information is stored (in high-level):\n * - text content of this particular buffer\n * - cursor position\n * - scroll position\n */\nexport class Buffer implements IBuffer {\n private _lines: CircularList<[number, string, number][]>;\n\n public ydisp: number;\n public ybase: number;\n public y: number;\n public x: number;\n public scrollBottom: number;\n public scrollTop: number;\n public tabs: any;\n public savedY: number;\n public savedX: number;\n\n /**\n * Create a new Buffer.\n * @param {Terminal} _terminal - The terminal the Buffer will belong to\n * @param {number} ydisp - The scroll position of the Buffer in the viewport\n * @param {number} ybase - The scroll position of the y cursor (ybase + y = the y position within the Buffer)\n * @param {number} y - The cursor's y position after ybase\n * @param {number} x - The cursor's x position after ybase\n */\n constructor(\n private _terminal: ITerminal\n ) {\n this.clear();\n }\n\n public get lines(): CircularList<[number, string, number][]> {\n return this._lines;\n }\n\n /**\n * Fills the buffer's viewport with blank lines.\n */\n public fillViewportRows(): void {\n if (this._lines.length === 0) {\n let i = this._terminal.rows;\n while (i--) {\n this.lines.push(this._terminal.blankLine());\n }\n }\n }\n\n /**\n * Clears the buffer to it's initial state, discarding all previous data.\n */\n public clear(): void {\n this.ydisp = 0;\n this.ybase = 0;\n this.y = 0;\n this.x = 0;\n this.scrollBottom = 0;\n this.scrollTop = 0;\n this.tabs = {};\n this._lines = new CircularList<[number, string, number][]>(this._terminal.scrollback);\n this.scrollBottom = this._terminal.rows - 1;\n }\n\n /**\n * Resizes the buffer, adjusting its data accordingly.\n * @param newCols The new number of columns.\n * @param newRows The new number of rows.\n */\n public resize(newCols: number, newRows: number): void {\n // Don't resize the buffer if it's empty and hasn't been used yet.\n if (this._lines.length === 0) {\n return;\n }\n\n // Deal with columns increasing (we don't do anything when columns reduce)\n if (this._terminal.cols < newCols) {\n const ch: [number, string, number] = [this._terminal.defAttr, ' ', 1]; // does xterm use the default attr?\n for (let i = 0; i < this._lines.length; i++) {\n if (this._lines.get(i) === undefined) {\n this._lines.set(i, this._terminal.blankLine());\n }\n while (this._lines.get(i).length < newCols) {\n this._lines.get(i).push(ch);\n }\n }\n }\n\n // Resize rows in both directions as needed\n let addToY = 0;\n if (this._terminal.rows < newRows) {\n for (let y = this._terminal.rows; y < newRows; y++) {\n if (this._lines.length < newRows + this.ybase) {\n if (this.ybase > 0 && this._lines.length <= this.ybase + this.y + addToY + 1) {\n // There is room above the buffer and there are no empty elements below the line,\n // scroll up\n this.ybase--;\n addToY++;\n if (this.ydisp > 0) {\n // Viewport is at the top of the buffer, must increase downwards\n this.ydisp--;\n }\n } else {\n // Add a blank line if there is no buffer left at the top to scroll to, or if there\n // are blank lines after the cursor\n this._lines.push(this._terminal.blankLine());\n }\n }\n }\n } else { // (this._terminal.rows >= newRows)\n for (let y = this._terminal.rows; y > newRows; y--) {\n if (this._lines.length > newRows + this.ybase) {\n if (this._lines.length > this.ybase + this.y + 1) {\n // The line is a blank line below the cursor, remove it\n this._lines.pop();\n } else {\n // The line is the cursor, scroll down\n this.ybase++;\n this.ydisp++;\n }\n }\n }\n }\n\n // Make sure that the cursor stays on screen\n if (this.y >= newRows) {\n this.y = newRows - 1;\n }\n if (addToY) {\n this.y += addToY;\n }\n\n if (this.x >= newCols) {\n this.x = newCols - 1;\n }\n\n this.scrollTop = 0;\n this.scrollBottom = newRows - 1;\n }\n}\n",null],"names":[],"mappings":"AsBAA;;;ADKA;AASA;AAqBA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AAAA;AAGA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AArIa;;;;;;;;;;;;;;;;;ADTb;AACA;AAMA;AAAA;AASA;AAAA;AAAA;AAEA;AACA;AACA;AACA;;AACA;AAMA;AAAA;AACA;AACA;;;AAAA;AAMA;AAAA;AACA;AACA;;;AAAA;AAMA;AAAA;AACA;AACA;;;AAAA;AAKA;AAIA;AAEA;AACA;AACA;AAKA;AAGA;AAEA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AAAA;AA3Ea;;;;;;;ADHA;AAKA;AAYb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AAMA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;AD3OA;AAwBA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAMA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAOA;AACA;AACA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAGA;AACA;AACA;AAEA;AAGA;AACA;AACA;AAEA;AACA;AAUA;AAAA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAAA;AAGA;AACA;AACA;AACA;AAUA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAAA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AAAA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAMA;AACA;AACA;AACA;AAAA;AACA;AAAA;AApNa;;;;;;;ADRb;AAAA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AACA;AAAC;;;;;;;ADpEA;AAED;AAGA;AAGA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AAAA;AAAA;;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AA3Da;;;;;;;ADNb;AACA;AASA;AAEA;AAAA;AAAA;AAEA;AACA;AAGA;AAEA;AACA;AACA;AAEA;AAIA;AAEA;AACA;AAGA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAIA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAGA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAGA;AAEA;AAGA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAMA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAMA;AACA;AAMA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAOA;AACA;AACA;AAOA;AACA;AACA;AAMA;AACA;AAEA;AACA;AAAA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AAEA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AAcA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAaA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AAMA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AAMA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAMA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AAIA;AACA;AACA;AACA;AAuCA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AAIA;AACA;AACA;AAAA;AACA;AACA;AAAA;AAGA;AACA;AAAA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAOA;AACA;AAAA;AACA;AAAA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAUA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAwFA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AAKA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAoFA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAkEA;AAEA;AACA;AACA;AACA;AAEA;AAOA;AACA;AACA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AACA;AAAA;AAEA;AACA;AACA;AAAA;AAEA;AACA;AACA;AAIA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAGA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AACA;AAIA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAAA;AAEA;AACA;AACA;AAIA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAAA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAyBA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAGA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAGA;AACA;AAGA;AACA;AAGA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAYA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AAAA;AA96Ca;AAg7CA;AAGb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAaA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;;;;;;;ADnlDA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AAKA;AAeA;AAFA;AAGA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AAMA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AAOA;AACA;AACA;AAYA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAOA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA;AAAA;AAOA;AACA;AAEA;AACA;AACA;AACA;AAQA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AAEA;AACA;AACA;AACA;AAEA;AAGA;AACA;AACA;AACA;AAAA;AAEA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AAAA;AAAA;AAAA;;AACA;AACA;AACA;AACA;AACA;AACA;AAWA;AAGA;AACA;AACA;AAKA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAhUmB;AANN;;;;;;;ADhCb;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AAKA;AACA;AACA;AAEA;AACA;AAOA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAIA;AACA;AAGA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AAKA;AACA;AAIA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAIA;AACA;AAAA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AAEA;AACA;AAIA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAGA;AACA;AAMA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AAQA;AACA;AACA;AAQA;AACA;AACA;AAOA;AACA;AACA;AAKA;AACA;AACA;AAMA;AACA;AACA;AACA;AAKA;AACA;AACA;AASA;AAAA;AA9da;;;;;;;ADjKb;AAOA;AAKA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAAC;AAED;AAEA;AAQA;AAAA;AANA;AACA;AACA;AAEA;AAKA;AACA;AACA;AACA;AAKA;AAQA;AACA;AACA;AACA;AACA;AACA;AAMA;AAKA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAuBA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAGA;AACA;AAAA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAIA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAAA;AAOA;AAEA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AAGA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAQA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AApWa;AAwWb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;AD3YA;AACA;AAGA;AAEA;AACA;AAMA;AAKA;AAKA;AAMA;AAIA;AACA;AAEA;AACA;AAaA;AAAA;AACA;AACA;AACA;AACA;AAUA;AAAA;AAiCA;AAAA;AACA;AACA;AACA;AACA;AATA;AAYA;AACA;AAEA;AACA;;AACA;AAKA;AAAA;AACA;AACA;AAEA;AAMA;AACA;AAMA;AACA;AACA;AACA;AAKA;AACA;AACA;AAOA;AACA;AACA;AACA;AAEA;AAAA;;;AAAA;AACA;AAAA;;;AAAA;AAKA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAAA;AAKA;AAAA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAIA;AACA;AACA;AAEA;AACA;;;AAAA;AAKA;AACA;AACA;AACA;AACA;AAOA;AAAA;AAEA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAGA;AACA;AAEA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAMA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AAEA;AACA;AACA;AAGA;AACA;AAGA;AAGA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AAKA;AAAA;AAEA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AAGA;AAGA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AAGA;AAIA;AACA;AACA;AAAA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AAGA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAKA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAAA;AA1lBa;;;;;;;AD1Db;AAuBA;AACA;AAEA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAKA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;;;AAAA;AAMA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AACA;AAGA;AAEA;AACA;AACA;AACA;AACA;AACA;;;AAAA;AAKA;AACA;AACA;AACA;AACA;AAOA;AAEA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAAA;AArHa;;;;;;;ADCb;AAaA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AAEA;AACA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AAQA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAAA;AAMA;AACA;AACA;AAAA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AAAA;AArIa;;;;;;;ADWb;AACA;AACA;AACA;AACA;AACA;AALA;AAWA;AACA;AACA;AACA;AAAA;AACA;AACA;AAGA;AACA;AATA;AAgBA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAzBA;AAgCA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AApBA;AA4BA;AACA;AAGA;AACA;AACA;AANA;;;;;;;ADvGA;AAEA;AACA;AACA;AAEa;AACA;AAKA;AACA;AACA;AACA;AACA;;;;;;;ADhBb;AACA;AAYA;AAAA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAnCA;;;;;;;;;;;;;;;;;ADdA;AAKA;AAAA;AAOA;AAAA;AAEA;AACA;;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAnDa;;;;;;;;;;;;;;;;;ADJb;AAGA;AAAA;AAKA;AAAA;AAEA;AACA;AACA;;AACA;AAEA;AAAA;AACA;AACA;AAEA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAXA;AAaA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AATA;AAWA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAAA;AAUA;AACA;AACA;AAUA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAMA;AACA;AACA;AAWA;AAAA;AAAA;AAAA;;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AAAA;AAhMa;;;;;;;ADAb;AAYA;AAAA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAAA;AAhE0B;AAET;AAHJ;;;;;;;ADEb;AACA;AACA;AAFA;AAEC;;;;;;;ADPD;AAEA;AACA;AACA;AAEA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AAjBA;AAgCA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AAGA;AACA;AAEA;AACA;AApBA;AAgCA;AACA;AACA;AACA;AAGA;AACA;AAEA;AACA;AAVA;;;;;;;AD1DA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAcA;AAOA;AAMA;AAOA;AAkBA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AAEA;AAAA;AACA;AAEA;AACA;AAEA;AAIA;AAIA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAGA;AACA;AAQA;AAGA;AAGA;AAGA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAEA;AAGA;AACA;AAEA;AAKA;AAEA;AACA;AAOA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAIA;AACA;AAKA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AAKA;AACA;AACA;AAMA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AAAA;AACA;AAEA;AACA;AACA;AAGA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAKA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAQA;AAAA;AACA;AAEA;AAEA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AAIA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AAGA;AAIA;AAMA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAIA;AAOA;AACA;AAQA;AACA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AAYA;AACA;AAKA;AACA;AAIA;AAGA;AACA;AAAA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAIA;AACA;AACA;AAIA;AACA;AAGA;AACA;AAAA;AAIA;AAEA;AACA;AAIA;AACA;AACA;AAAA;AACA;AAAA;AACA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAQA;AAOA;AAGA;AACA;AACA;AACA;AACA;AAAA;AACA;AAAA;AACA;AAAA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAAA;AACA;AAAA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAaA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AAGA;AAEA;AACA;AAAA;AACA;AACA;AAGA;AAEA;AACA;AAEA;AAIA;AACA;AAEA;AAAA;AAGA;AAIA;AACA;AACA;AACA;AACA;AAGA;AAAA;AAGA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AAMA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAIA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAQA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AACA;AAGA;AAGA;AAEA;AAEA;AACA;AAAA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAQA;AACA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAMA;AACA;AACA;AAKA;AACA;AACA;AAKA;AACA;AACA;AAMA;AACA;AAKA;AAGA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AAEA;AACA;AAOA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAMA;AACA;AACA;AAQA;AACA;AACA;AACA;AACA;AAUA;AACA;AACA;AAQA;AACA;AACA;AACA;AACA;AAEA;AACA;AAQA;AACA;AACA;AACA;AACA;AAEA;AACA;AAYA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AAMA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AASA;AACA;AAGA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAIA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AAIA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AAGA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AAGA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AACA;AAAA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAMA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AAEA;AAGA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAMA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAMA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AAKA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AAKA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAOA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AAAA;AAEA;AAGA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AAMA;AACA;AAAA;AACA;AAAA;AAOA;AAKA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAOA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AAOA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAQA;AACA;AACA;AACA;AAEA;AAMA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAOA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AAOA;AAEA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAOA;AAOA;AACA;AAUA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAQA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AAGA;AACA;AAGA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AASA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAGA;AACA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAQA;AACA;AACA;AAEA;;;"} \ No newline at end of file From 48b9173f2e2dd21f764c02e6aaad051f02abcc58 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 12 Aug 2017 13:54:51 -0700 Subject: [PATCH 044/378] Remove bower.json Part of #406 --- bower.json | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 bower.json diff --git a/bower.json b/bower.json deleted file mode 100644 index 512324cc..00000000 --- a/bower.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "xterm.js", - "version": "2.9.1", - "ignore": ["demo", "test", ".gitignore"], - "main": [ - "dist/xterm.js", - "dist/xterm.css" - ] -} From fcd2ba78bb341614deb128a86c717cef249985b5 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 12 Aug 2017 14:44:49 -0700 Subject: [PATCH 045/378] Remove bower from README and reorganize/clean up --- README.md | 97 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 59 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index e1217bcd..77825a5d 100644 --- a/README.md +++ b/README.md @@ -59,43 +59,22 @@ Since xterm.js is typically implemented as a developer tool, only modern browser Xterm.js works seamlessly in Electron apps and may even work on earlier versions of the browsers but these are the browsers we strive to keep working. -## Demo - -### Linux or macOS - -First, be sure that a C++ compiler such as GCC-C++ or Clang is installed - -Then run the following commands: - -``` -$ npm install -$ npm start -``` - -Then open http://0.0.0.0:3000 in a web browser. - -### Windows - -First, ensure [node-gyp](https://github.com/nodejs/node-gyp) is installed and configured correctly, then run these commands. - -Note: Do not use ConEmu, as it seems to break the demo for some reason. - -``` -> npm install -> npm start -``` - -Then open http://127.0.0.1:3000 in a web browser. - ## Getting Started +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 +``` + 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. + ```html - - + +
@@ -107,37 +86,79 @@ To start using xterm.js on your browser, add the `xterm.js` and `xterm.css` to t ``` + Finally instantiate the `Terminal` object and then call the `open` function with the DOM object of the `div`. -## Addons +### Addons Addons are JavaScript modules that attach functions to the `Terminal` prototype to extend its functionality. There are a handful available in the main repository in the `dist/addons` directory, you can even write your own (though they may break when the internals of xterm.js change across versions). To use an addon, just include the JavaScript file after xterm.js and before the `Terminal` object has been instantiated. The function should then be exposed on the `Terminal` object: ```html - - + + ``` ```js +// Instantiate the terminal and call fit var xterm = new Terminal(); -// init code... xterm.fit(); ``` -## CommonJS +### Importing -Importing xterm.js in a CommonJS environment (eg. [Electron](https://electron.atom.io/)) can be done like so: +If the environment allows it, you can import xterm.js like so: ```ts -// JavaScript +// CommonJS var Terminal = require('xterm').Terminal; -// TypeScript +// ES6 / TypeScript import { Terminal } from 'xterm'; ``` +Importing addons in this environment can be done using a `Terminal.loadAddon` call: + +```ts +import { Terminal } from 'xterm'; + +// Notice it's called statically on the type, not an object +Terminal.loadAddon('fit'); + +// Instantiate the terminal and call fit +var xterm = new Terminal(); +xterm.fit(); +``` + +Note for TypeScript users there is currently no solution for addon + +## Demo + +### Linux or macOS + +First, be sure that a C++ compiler such as GCC-C++ or Clang is installed, then run these commands: + +``` +npm install +npm start +``` + +Then open http://0.0.0.0:3000 in a web browser. + +### Windows + +First, ensure [node-gyp](https://github.com/nodejs/node-gyp) is installed and configured correctly, then run these commands. + +Note: Do not use ConEmu, as it seems to break the demo for some reason. + +``` +npm install +npm start +``` + +Then open http://127.0.0.1:3000 in a web browser. + ## Releases Xterm.js follows a monthly release cycle roughly. From 7c3189379c8e1c7c832ae42ecc91a14db56b7743 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 12 Aug 2017 14:45:00 -0700 Subject: [PATCH 046/378] Remove bower from .vscode/settings.json --- .vscode/settings.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 657f9915..f78b3f81 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,9 +1,8 @@ { "search.exclude": { "**/node_modules": true, - "**/bower_components": true, "**/dist": true, "**/build": true, "**/lib": true } -} \ No newline at end of file +} From a3f7b369862b085c77c35bcf315f155a32c1604f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 12 Aug 2017 14:45:21 -0700 Subject: [PATCH 047/378] Remove bower from prepare-release --- bin/prepare-release | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/bin/prepare-release b/bin/prepare-release index 464e65b4..fed7919e 100755 --- a/bin/prepare-release +++ b/bin/prepare-release @@ -12,12 +12,6 @@ CURRENT_PACKAGE_JSON_VERSION=$(cat package.json \ | awk -F: '{ print $2 }' \ | sed 's/[",]//g' \ | tr -d '[[:space:]]') -CURRENT_BOWER_JSON_VERSION=$(cat bower.json \ - | grep version \ - | head -1 \ - | awk -F: '{ print $2 }' \ - | sed 's/[",]//g' \ - | tr -d '[[:space:]]') # Build xterm.js into `dist` export BUILD_DIR=dist @@ -26,9 +20,8 @@ npm run build # Update AUTHORS file sh bin/generate-authors -# Update version in package.json and bower.json +# Update version in package.json sed -i "s/\"version\": \"$CURRENT_PACKAGE_JSON_VERSION\"/\"version\": \"$NEW_VERSION\"/g" package.json -sed -i "s/\"version\": \"$CURRENT_BOWER_JSON_VERSION\"/\"version\": \"$NEW_VERSION\"/g" bower.json git commit -S -s -a -m "Bump version to $NEW_VERSION" git tag $NEW_VERSION From d583163ac8587d644d9701e6e0b5987ee923cc4f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 12 Aug 2017 14:48:38 -0700 Subject: [PATCH 048/378] Move uses and browser support sections down --- README.md | 86 +++++++++++++++++++++++++++---------------------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 77825a5d..edfa1169 100644 --- a/README.md +++ b/README.md @@ -19,46 +19,6 @@ It enables applications to provide fully featured terminals to their users and c - Xterm.js is not a terminal application that you can download and use on your computer - Xterm.js is not `bash`. Xterm.js can be connected to processes like `bash` and let you interact with them (provide input, receive output) -## Real-world uses -Xterm.js is used in several world-class applications to provide great terminal experiences. - -- [**SourceLair**](https://www.sourcelair.com/): In-browser IDE that provides its users with fully-featured Linux terminals based on xterm.js -- [**Microsoft Visual Studio Code**](http://code.visualstudio.com/): Modern, versatile and powerful open source code editor that provides an integrated terminal based on xterm.js -- [**ttyd**](https://github.com/tsl0922/ttyd): A command-line tool for sharing terminal over the web, with fully-featured terminal emulation based on xterm.js -- [**Katacoda**](https://www.katacoda.com/): Katacoda is an Interactive Learning Platform for software developers, covering the latest Cloud Native technologies. -- [**Eclipse Che**](http://www.eclipse.org/che): Developer workspace server, cloud IDE, and Eclipse next-generation IDE. -- [**Codenvy**](http://www.codenvy.com): Cloud workspaces for development teams. -- [**CoderPad**](https://coderpad.io): Online interviewing platform for programmers. Run code in many programming languages, with results displayed by `xterm.js`. -- [**WebSSH2**](https://github.com/billchurch/WebSSH2): A web based SSH2 client using `xterm.js`, socket.io, and ssh2. -- [**Spyder Terminal**](https://github.com/spyder-ide/spyder-terminal): A full fledged system terminal embedded on Spyder IDE. -- [**Cloud Commander**](https://cloudcmd.io "Cloud Commander"): Orthodox web file manager with console and editor. -- [**Codevolve**](https://www.codevolve.com "Codevolve"): Online platform for interactive coding and web development courses. Live container-backed terminal uses `xterm.js`. -- [**RStudio**](https://www.rstudio.com/products/RStudio "RStudio"): RStudio is an integrated development environment (IDE) for R. -- [**Terminal for Atom**](https://github.com/jsmecham/atom-terminal-tab): A simple terminal for the Atom text editor. -- [**Eclipse Orion**](https://orionhub.org): A modern, open source software development environment that runs in the cloud. Code, deploy and run in the cloud. -- [**Gravitational Teleport**](https://github.com/gravitational/teleport): Gravitational Teleport is a modern SSH server for remotely accessing clusters of Linux servers via SSH or HTTPS. -- [**Hexlet**](https://en.hexlet.io): Practical programming courses (JavaScript, PHP, Unix, databases, functional programming). A steady path from the first line of code to the first job. -- [**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 -computational environment for Jupyter, supporting interactive data science and scientific computing across all programming languages. - -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. - -## Browser Support - -Since xterm.js is typically implemented as a developer tool, only modern browsers are supported officially. Here is a list of the versions we aim to support: - -- Chrome 48+ -- Edge 13+ -- Firefox 44+ -- Internet Explorer 11+ -- Opera 35+ -- Safari 8+ - -Xterm.js works seamlessly in Electron apps and may even work on earlier versions of the browsers but these are the browsers we strive to keep working. - ## Getting Started 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: @@ -131,7 +91,47 @@ var xterm = new Terminal(); xterm.fit(); ``` -Note for TypeScript users there is currently no solution for addon +*Note: There are currently no typings for addons so you will need to upcast if using TypeScript, eg. `(xterm).fit()`* + +## Browser Support + +Since xterm.js is typically implemented as a developer tool, only modern browsers are supported officially. Here is a list of the versions we aim to support: + +- Chrome 48+ +- Edge 13+ +- Firefox 44+ +- Internet Explorer 11+ +- Opera 35+ +- Safari 8+ + +Xterm.js works seamlessly in Electron apps and may even work on earlier versions of the browsers but these are the browsers we strive to keep working. + +## Real-world uses +Xterm.js is used in several world-class applications to provide great terminal experiences. + +- [**SourceLair**](https://www.sourcelair.com/): In-browser IDE that provides its users with fully-featured Linux terminals based on xterm.js +- [**Microsoft Visual Studio Code**](http://code.visualstudio.com/): Modern, versatile and powerful open source code editor that provides an integrated terminal based on xterm.js +- [**ttyd**](https://github.com/tsl0922/ttyd): A command-line tool for sharing terminal over the web, with fully-featured terminal emulation based on xterm.js +- [**Katacoda**](https://www.katacoda.com/): Katacoda is an Interactive Learning Platform for software developers, covering the latest Cloud Native technologies. +- [**Eclipse Che**](http://www.eclipse.org/che): Developer workspace server, cloud IDE, and Eclipse next-generation IDE. +- [**Codenvy**](http://www.codenvy.com): Cloud workspaces for development teams. +- [**CoderPad**](https://coderpad.io): Online interviewing platform for programmers. Run code in many programming languages, with results displayed by `xterm.js`. +- [**WebSSH2**](https://github.com/billchurch/WebSSH2): A web based SSH2 client using `xterm.js`, socket.io, and ssh2. +- [**Spyder Terminal**](https://github.com/spyder-ide/spyder-terminal): A full fledged system terminal embedded on Spyder IDE. +- [**Cloud Commander**](https://cloudcmd.io "Cloud Commander"): Orthodox web file manager with console and editor. +- [**Codevolve**](https://www.codevolve.com "Codevolve"): Online platform for interactive coding and web development courses. Live container-backed terminal uses `xterm.js`. +- [**RStudio**](https://www.rstudio.com/products/RStudio "RStudio"): RStudio is an integrated development environment (IDE) for R. +- [**Terminal for Atom**](https://github.com/jsmecham/atom-terminal-tab): A simple terminal for the Atom text editor. +- [**Eclipse Orion**](https://orionhub.org): A modern, open source software development environment that runs in the cloud. Code, deploy and run in the cloud. +- [**Gravitational Teleport**](https://github.com/gravitational/teleport): Gravitational Teleport is a modern SSH server for remotely accessing clusters of Linux servers via SSH or HTTPS. +- [**Hexlet**](https://en.hexlet.io): Practical programming courses (JavaScript, PHP, Unix, databases, functional programming). A steady path from the first line of code to the first job. +- [**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 +computational environment for Jupyter, supporting interactive data science and scientific computing across all programming languages. + +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. ## Demo @@ -150,8 +150,6 @@ Then open http://0.0.0.0:3000 in a web browser. First, ensure [node-gyp](https://github.com/nodejs/node-gyp) is installed and configured correctly, then run these commands. -Note: Do not use ConEmu, as it seems to break the demo for some reason. - ``` npm install npm start @@ -159,6 +157,8 @@ npm start Then open http://127.0.0.1:3000 in a web browser. +*Note: Do not use ConEmu, as it seems to break the demo for some reason.* + ## Releases Xterm.js follows a monthly release cycle roughly. From 7b0d0e810c7ff509406afbf532063eb6b35b9261 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 16 Aug 2017 08:45:30 -0700 Subject: [PATCH 049/378] _isAltBuffer -> _hasScrollback --- src/Buffer.test.ts | 8 ++++---- src/Buffer.ts | 11 +++++------ src/BufferSet.ts | 7 +++++-- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/Buffer.test.ts b/src/Buffer.test.ts index 9a3d9168..6baa230d 100644 --- a/src/Buffer.test.ts +++ b/src/Buffer.test.ts @@ -20,7 +20,7 @@ describe('Buffer', () => { terminal.cols = INIT_COLS; terminal.rows = INIT_ROWS; terminal.options.scrollback = 1000; - buffer = new Buffer(terminal, false); + buffer = new Buffer(terminal, true); }); describe('constructor', () => { @@ -92,7 +92,7 @@ describe('Buffer', () => { describe('no scrollback', () => { it('should trim from the top of the buffer when the cursor reaches the bottom', () => { terminal.options.scrollback = 0; - buffer = new Buffer(terminal, false); + buffer = new Buffer(terminal, true); assert.equal(buffer.lines.maxLength, INIT_ROWS); buffer.y = INIT_ROWS - 1; buffer.fillViewportRows(); @@ -172,11 +172,11 @@ describe('Buffer', () => { }); }); - describe('alt buffer', () => { + describe('buffer marked to have no scrollback', () => { it('should always have a scrollback of 0', () => { assert.equal(terminal.options.scrollback, 1000); // Test size on initialization - buffer = new Buffer(terminal, true); + buffer = new Buffer(terminal, false); buffer.fillViewportRows(); assert.equal(buffer.lines.maxLength, INIT_ROWS); // Test size on buffer increase diff --git a/src/Buffer.ts b/src/Buffer.ts index bf2ceb71..fd6f8aef 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -32,11 +32,12 @@ export class Buffer implements IBuffer { /** * Create a new Buffer. * @param _terminal The terminal the Buffer will belong to. - * @param _isAltBuffer Whether the buffer is the alt buffer. + * @param _hasScrollback Whether the buffer should respecr the scrollback of + * the terminal.. */ constructor( private _terminal: ITerminal, - private _isAltBuffer: boolean + private _hasScrollback: boolean ) { this.clear(); } @@ -47,13 +48,11 @@ export class Buffer implements IBuffer { /** * Gets the correct buffer length based on the rows provided, the terminal's - * scrollback and whether this is an alt buffer. + * scrollback and whether this buffer is flagged to have scrollback or not. * @param rows The terminal rows to use in the calculation. */ private _getCorrectBufferLength(rows: number): number { - // The alt buffer should never have scrollback. - // See http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer - if (this._isAltBuffer) { + if (!this._hasScrollback) { return rows; } return rows + this._terminal.options.scrollback; diff --git a/src/BufferSet.ts b/src/BufferSet.ts index 851fc47d..d37a650f 100644 --- a/src/BufferSet.ts +++ b/src/BufferSet.ts @@ -21,9 +21,12 @@ export class BufferSet extends EventEmitter implements IBufferSet { */ constructor(private _terminal: ITerminal) { super(); - this._normal = new Buffer(this._terminal, false); + this._normal = new Buffer(this._terminal, true); this._normal.fillViewportRows(); - this._alt = new Buffer(this._terminal, true); + + // The alt buffer should never have scrollback. + // See http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer + this._alt = new Buffer(this._terminal, false); this._activeBuffer = this._normal; } From 245f62685aaeebff40ac6a315fee0d5bc4814947 Mon Sep 17 00:00:00 2001 From: npezza93 Date: Wed, 9 Aug 2017 20:01:08 -0400 Subject: [PATCH 050/378] If bell() gets called emit bell event and add default bell sound --- src/InputHandler.ts | 7 ++++++- src/utils/Bell.ts | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 src/utils/Bell.ts diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 61fe581e..2208d91e 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -7,6 +7,7 @@ import { C0 } from './EscapeSequences'; import { DEFAULT_CHARSET } from './Charsets'; import { CharData } from './Types'; import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from './Buffer'; +import { Bell } from './utils/Bell'; /** * The terminal's standard implementation of IInputHandler, this handles all @@ -106,7 +107,11 @@ export class InputHandler implements IInputHandler { * Bell (Ctrl-G). */ public bell(): void { - if (!this._terminal.options.visualBell) { + this._terminal.emit('bell'); + if (this._terminal.playDefaultBell) { + (new Audio(Bell)).play(); + } + if (!this._terminal.visualBell) { return; } this._terminal.element.style.borderColor = 'white'; diff --git a/src/utils/Bell.ts b/src/utils/Bell.ts new file mode 100644 index 00000000..f95bbdb0 --- /dev/null +++ b/src/utils/Bell.ts @@ -0,0 +1 @@ +export const Bell = 'data:audio/ogg;base64,T2dnUwACAAAAAAAAAADhqW5KAAAAAMFvEjYBHgF2b3JiaXMAAAAAAYC7AAAAAAAAAHcBAAAAAAC4AU9nZ1MAAAAAAAAAAAAA4aluSgEAAAAAesI3EC3//////////////////8kDdm9yYmlzHQAAAFhpcGguT3JnIGxpYlZvcmJpcyBJIDIwMDkwNzA5AAAAAAEFdm9yYmlzKUJDVgEACAAAADFMIMWA0JBVAAAQAABgJCkOk2ZJKaWUoSh5mJRISSmllMUwiZiUicUYY4wxxhhjjDHGGGOMIDRkFQAABACAKAmOo+ZJas45ZxgnjnKgOWlOOKcgB4pR4DkJwvUmY26mtKZrbs4pJQgNWQUAAAIAQEghhRRSSCGFFGKIIYYYYoghhxxyyCGnnHIKKqigggoyyCCDTDLppJNOOumoo4466ii00EILLbTSSkwx1VZjrr0GXXxzzjnnnHPOOeecc84JQkNWAQAgAAAEQgYZZBBCCCGFFFKIKaaYcgoyyIDQkFUAACAAgAAAAABHkRRJsRTLsRzN0SRP8ixREzXRM0VTVE1VVVVVdV1XdmXXdnXXdn1ZmIVbuH1ZuIVb2IVd94VhGIZhGIZhGIZh+H3f933f930gNGQVACABAKAjOZbjKaIiGqLiOaIDhIasAgBkAAAEACAJkiIpkqNJpmZqrmmbtmirtm3LsizLsgyEhqwCAAABAAQAAAAAAKBpmqZpmqZpmqZpmqZpmqZpmqZpmmZZlmVZlmVZlmVZlmVZlmVZlmVZlmVZlmVZlmVZlmVZlmVZlmVZQGjIKgBAAgBAx3Ecx3EkRVIkx3IsBwgNWQUAyAAACABAUizFcjRHczTHczzHczxHdETJlEzN9EwPCA1ZBQAAAgAIAAAAAABAMRzFcRzJ0SRPUi3TcjVXcz3Xc03XdV1XVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVYHQkFUAAAQAACGdZpZqgAgzkGEgNGQVAIAAAAAYoQhDDAgNWQUAAAQAAIih5CCa0JrzzTkOmuWgqRSb08GJVJsnuamYm3POOeecbM4Z45xzzinKmcWgmdCac85JDJqloJnQmnPOeRKbB62p0ppzzhnnnA7GGWGcc85p0poHqdlYm3POWdCa5qi5FJtzzomUmye1uVSbc84555xzzjnnnHPOqV6czsE54Zxzzonam2u5CV2cc875ZJzuzQnhnHPOOeecc84555xzzglCQ1YBAEAAAARh2BjGnYIgfY4GYhQhpiGTHnSPDpOgMcgppB6NjkZKqYNQUhknpXSC0JBVAAAgAACEEFJIIYUUUkghhRRSSCGGGGKIIaeccgoqqKSSiirKKLPMMssss8wyy6zDzjrrsMMQQwwxtNJKLDXVVmONteaec645SGultdZaK6WUUkoppSA0ZBUAAAIAQCBkkEEGGYUUUkghhphyyimnoIIKCA1ZBQAAAgAIAAAA8CTPER3RER3RER3RER3RER3P8RxREiVREiXRMi1TMz1VVFVXdm1Zl3Xbt4Vd2HXf133f141fF4ZlWZZlWZZlWZZlWZZlWZZlCUJDVgEAIAAAAEIIIYQUUkghhZRijDHHnINOQgmB0JBVAAAgAIAAAAAAR3EUx5EcyZEkS7IkTdIszfI0T/M00RNFUTRNUxVd0RV10xZlUzZd0zVl01Vl1XZl2bZlW7d9WbZ93/d93/d93/d93/d939d1IDRkFQAgAQCgIzmSIimSIjmO40iSBISGrAIAZAAABACgKI7iOI4jSZIkWZImeZZniZqpmZ7pqaIKhIasAgAAAQAEAAAAAACgaIqnmIqniIrniI4oiZZpiZqquaJsyq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7rukBoyCoAQAIAQEdyJEdyJEVSJEVyJAcIDVkFAMgAAAgAwDEcQ1Ikx7IsTfM0T/M00RM90TM9VXRFFwgNWQUAAAIACAAAAAAAwJAMS7EczdEkUVIt1VI11VItVVQ9VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV1TRN0zSB0JCVAAAZAAAjQQYZhBCKcpBCbj1YCDHmJAWhOQahxBiEpxAzDDkNInSQQSc9uJI5wwzz4FIoFURMg40lN44gDcKmXEnlOAhCQ1YEAFEAAIAxyDHEGHLOScmgRM4xCZ2UyDknpZPSSSktlhgzKSWmEmPjnKPSScmklBhLip2kEmOJrQAAgAAHAIAAC6HQkBUBQBQAAGIMUgophZRSzinmkFLKMeUcUko5p5xTzjkIHYTKMQadgxAppRxTzinHHITMQeWcg9BBKAAAIMABACDAQig0ZEUAECcA4HAkz5M0SxQlSxNFzxRl1xNN15U0zTQ1UVRVyxNV1VRV2xZNVbYlTRNNTfRUVRNFVRVV05ZNVbVtzzRl2VRV3RZV1bZl2xZ+V5Z13zNNWRZV1dZNVbV115Z9X9ZtXZg0zTQ1UVRVTRRV1VRV2zZV17Y1UXRVUVVlWVRVWXZlWfdVV9Z9SxRV1VNN2RVVVbZV2fVtVZZ94XRVXVdl2fdVWRZ+W9eF4fZ94RhV1dZN19V1VZZ9YdZlYbd13yhpmmlqoqiqmiiqqqmqtm2qrq1bouiqoqrKsmeqrqzKsq+rrmzrmiiqrqiqsiyqqiyrsqz7qizrtqiquq3KsrCbrqvrtu8LwyzrunCqrq6rsuz7qizruq3rxnHrujB8pinLpqvquqm6um7runHMtm0co6rqvirLwrDKsu/rui+0dSFRVXXdlF3jV2VZ921fd55b94WybTu/rfvKceu60vg5z28cubZtHLNuG7+t+8bzKz9hOI6lZ5q2baqqrZuqq+uybivDrOtCUVV9XZVl3zddWRdu3zeOW9eNoqrquirLvrDKsjHcxm8cuzAcXds2jlvXnbKtC31jyPcJz2vbxnH7OuP2daOvDAnHjwAAgAEHAIAAE8pAoSErAoA4AQAGIecUUxAqxSB0EFLqIKRUMQYhc05KxRyUUEpqIZTUKsYgVI5JyJyTEkpoKZTSUgehpVBKa6GU1lJrsabUYu0gpBZKaS2U0lpqqcbUWowRYxAy56RkzkkJpbQWSmktc05K56CkDkJKpaQUS0otVsxJyaCj0kFIqaQSU0mptVBKa6WkFktKMbYUW24x1hxKaS2kEltJKcYUU20txpojxiBkzknJnJMSSmktlNJa5ZiUDkJKmYOSSkqtlZJSzJyT0kFIqYOOSkkptpJKTKGU1kpKsYVSWmwx1pxSbDWU0lpJKcaSSmwtxlpbTLV1EFoLpbQWSmmttVZraq3GUEprJaUYS0qxtRZrbjHmGkppraQSW0mpxRZbji3GmlNrNabWam4x5hpbbT3WmnNKrdbUUo0txppjbb3VmnvvIKQWSmktlNJiai3G1mKtoZTWSiqxlZJabDHm2lqMOZTSYkmpxZJSjC3GmltsuaaWamwx5ppSi7Xm2nNsNfbUWqwtxppTS7XWWnOPufVWAADAgAMAQIAJZaDQkJUAQBQAAEGIUs5JaRByzDkqCULMOSepckxCKSlVzEEIJbXOOSkpxdY5CCWlFksqLcVWaykptRZrLQAAoMABACDABk2JxQEKDVkJAEQBACDGIMQYhAYZpRiD0BikFGMQIqUYc05KpRRjzknJGHMOQioZY85BKCmEUEoqKYUQSkklpQIAAAocAAACbNCUWByg0JAVAUAUAABgDGIMMYYgdFQyKhGETEonqYEQWgutddZSa6XFzFpqrbTYQAithdYySyXG1FpmrcSYWisAAOzAAQDswEIoNGQlAJAHAEAYoxRjzjlnEGLMOegcNAgx5hyEDirGnIMOQggVY85BCCGEzDkIIYQQQuYchBBCCKGDEEIIpZTSQQghhFJK6SCEEEIppXQQQgihlFIKAAAqcAAACLBRZHOCkaBCQ1YCAHkAAIAxSjkHoZRGKcYglJJSoxRjEEpJqXIMQikpxVY5B6GUlFrsIJTSWmw1dhBKaS3GWkNKrcVYa64hpdZirDXX1FqMteaaa0otxlprzbkAANwFBwCwAxtFNicYCSo0ZCUAkAcAgCCkFGOMMYYUYoox55xDCCnFmHPOKaYYc84555RijDnnnHOMMeecc845xphzzjnnHHPOOeecc44555xzzjnnnHPOOeecc84555xzzgkAACpwAAAIsFFkc4KRoEJDVgIAqQAAABFWYowxxhgbCDHGGGOMMUYSYowxxhhjbDHGGGOMMcaYYowxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGFtrrbXWWmuttdZaa6211lprrQBAvwoHAP8HG1ZHOCkaCyw0ZCUAEA4AABjDmHOOOQYdhIYp6KSEDkIIoUNKOSglhFBKKSlzTkpKpaSUWkqZc1JSKiWlllLqIKTUWkottdZaByWl1lJqrbXWOgiltNRaa6212EFIKaXWWostxlBKSq212GKMNYZSUmqtxdhirDGk0lJsLcYYY6yhlNZaazHGGGstKbXWYoy1xlprSam11mKLNdZaCwDgbnAAgEiwcYaVpLPC0eBCQ1YCACEBAARCjDnnnHMQQgghUoox56CDEEIIIURKMeYcdBBCCCGEjDHnoIMQQgghhJAx5hx0EEIIIYQQOucchBBCCKGEUkrnHHQQQgghlFBC6SCEEEIIoYRSSikdhBBCKKGEUkopJYQQQgmllFJKKaWEEEIIoYQSSimllBBCCKWUUkoppZQSQgghlFJKKaWUUkIIoZRQSimllFJKCCGEUkoppZRSSgkhhFBKKaWUUkopIYQSSimllFJKKaUAAIADBwCAACPoJKPKImw04cIDUGjISgCADAAAcdhq6ynWyCDFnISWS4SQchBiLhFSijlHsWVIGcUY1ZQxpRRTUmvonGKMUU+dY0oxw6yUVkookYLScqy1dswBAAAgCAAwECEzgUABFBjIAIADhAQpAKCwwNAxXAQE5BIyCgwKx4Rz0mkDABCEyAyRiFgMEhOqgaJiOgBYXGDIB4AMjY20iwvoMsAFXdx1IIQgBCGIxQEUkICDE2544g1PuMEJOkWlDgIAAAAA4AAAHgAAkg0gIiKaOY4Ojw+QEJERkhKTE5QAAAAAALABgA8AgCQFiIiIZo6jw+MDJERkhKTE5AQlAAAAAAAAAAAACAgIAAAAAAAEAAAACAhPZ2dTAAQYOwAAAAAAAOGpbkoCAAAAmc74DRgyNjM69TAzOTk74dnLubewsbagmZiNp4d0KbsExSY/I3XUTwJgkeZdn1HY4zoj33/q9DFtv3Ui1/jmx7lCUtPt18/sYf9MkgAsAGRBd3gMGP4sU+qCPYBy9VrA3YqJosW3W2/ef1iO/u3cg8ZG/57jU+pPmbGEJUgkfnaI39DbPqxddZphbMRmCc5rKlkUMkyx8iIoug5dJv1OYH9a59c+3Gevqc7Z2XFdDjL/qHztRfjWEWxJ/aiGezjohu9HsCZdQBKbiH0VtU/3m85lDG2T/+xkZcYnX+E+aqzv/xTgOoTFG+x7SNqQ4N+oAABSxuVXw77Jd5bmmTmuJakX7509HH0kGYKvARPpwfOSAPySPAc2EkneDwB2HwAAJlQDYK5586N79GJCjx4+p6aDUd27XSvRyXLJkIC5YZ1jLv5lpOhZTz0s+DmnF1diptrnM6UDgIW11Xh8cHTd0/SmbgOAdxcyWwMAAGIrZ3fNSfZbzKiYrK4+tPqtnMVLOeWOG2kVvUY+p2PJ/hkCl5aFRO4TLGYPZcIU3vYM1hohS4jHFlnyW/2T5J7kGsShXWT8N05V+3C/GPqJ1QdWisGPxEzHqXISBPIinWDUt7IeJv/f5OtzBxpTzZZQ+CYEhHXfqG4aABQli72GJhN4oJv+hXcApAJSErAW8G2raAX4NUcABnVt77CzZAB+LsHcVe+Q4h+QB1wh/ZrJTPxSBdI8mgTeAdTsQOoFUEng9BHcVPhxSRRYkKWZJXOFYP6V4AEripJoEjXgA2wJRZHSExmJDm8F0A6gEXsg5a4ZsALItrMB7+fh7UKLvYWSdtsDwFf1mzYzS1F82N1h2Oyt2e76B1QdS0SAsQigLPMOgJS9JRC7hFXA6kUsLFNKD5cA5cTRvgSqPc3Fl99xW3QTi/MHR8DEm6WnvaVQATwRqRKjywQ9BrrhugR2AKTsPQeQckrAOgDOhbTESyrXQ50CkNpXdtWjW7W2/3UjeX3U95gIdalfRAoAmqUEiwp53hCdcCwlg47fcbfzlmQMAgaBkh7c+fcDgF+ifwDXfzegLPcLYJsAAJQArTXjnh/uXGy3v1Hk3pV6/3t5ruW81f6prfbM2Q3WNVy98BwUtbCwhFhAWuPev6Oe/4ZaFQUcgKrVs4defzh1TADA1DEh5b3VlDaECw5b+bPfkKos3tIAue3vJZOih3ga3l6O3PSfIkrLv0PAS86PPdL7g8oc2KteNFKKzKRehOv2gJoFLBPXmaXvPBQILgJon0bbWBszrYZYYwE7jl2j+vTdU7Vpk21LiU0QajPkywAAHqbUC0/YsYOdb4e6BOp7E0cCi04Ao/TgD8ZVAMid6h/A8IeBNkp6/xsAACZELEYIk+yvI6Qz1NN6lIftB/6IMWjWJNOqPTMedAmyaj6Es0QBklJpiSWWHnQ2CoYbGWAmt+0gLQBFKCBnp2QUUQZ/1thtZDBJUpFWY82z34ocorB62oX7qB5y0oPAv/foxH25wVmgIHf2xFOr8leZcBq1Kx3ZvCq9Bga639AxuHuPNL/71YCF4EywJpqHFAX6XF0sjVbuANnvvdLcrufYwOM/iDa6iA468AYAAB6mNBMXcgTD8HSRqJ4vw8CjAlCEPACASlX/APwPOJKl9xQAAAPmnev2eWp33Xgyw3Dvfz6myGk3oyP8YTKsCOvzAgALQi0o1c6Nzs2O2Pg2h4ACIJAgAGP0aNn5x0BDgVfH7u2TtyfDcRIuYAyQhBF/lvSRAttgA6TPbWZA9gaUrZWAUEAA+Dx47Q3/r87HxUUqZmB0BmUuMlojFjHt1gDunnvuX8MImsjSq5WkzSzGS62OEIlOufWWezxWpv6FBgDgJVltfXFYtNAAnqU0xQoD0YLiXo5cF5QV4CnY1tBLAkZCOABAhbk/AM+/AwSCCdlWAAAMcFjS7owb8GVDzveDiZvznbt2tF4bL5odN1YKl88TAEABCZvufq9YCTBtMwVAQUEAwGtNltzSaHvADYC3TxLVjqiRA+OZAMhzcqEgRcAOwoCgvdTxsTHLQEF6+oOb2+PAI8ciPQcXg7pOY+LjxQSv2fjmFuj34gGwz310/bGK6z3xgT887eomWULEaDd04wHetYxdjcgV2SxvSwn0VoZXJRqkRC5ASQ/muVoAUsX7AgAQMBNaVwAAlABRxT/1PmfqLqSRNDbhXb07berpB3b94jpuWEZjBCD2OcdXFpCKEgCDfcFPMw8AAADUwT4lnUm50lmwrpMMhPQIKj6u0E8fr2vGBngMNdIlrZsigjahljud6AFVg+tzXwUnXL3TJLpajaWKA4VAAAAMiFfqJgKAZ08XrtS3dxtQNYcpPvYEG8ClvrQRJgBephwnNWJjtGqmp6VEPSvBe7EBiU3qgJbQAwD4Le8LAMDMhHbNAAAlgK+tFs5O+YyJc9yCnJa3rxLPulGnxwsXV9Fsk2k4PisCAHC8FkwbGE9gJQAAoMnyksj0CdFMZLLgoz8M+FxziwYBgIx+zHiCBAKAlBKNpF1sO9JpVcyEi9ar15YlHgrut5fPJnkdJ6vEwZPyAHQBIEDUrlMcBAAd2KAS0Qq+JwRsE4AJZtMnAD6GnOYwYlOIZvtzUNdjreB7fiMkWI0CmBB6AIAKc38A9osEFlTSGECB+cbeRDC0aRpLHqNPplcK/76Lxn2rpmqyXsYJWRi/FQAAAKBQk9MCAOibrQBQADCDsqpooPutd+05Ce9g6iEdiYXgVmQAI4+4wskEBEiBloNQ6Ki0/KTQ0QjWfjxzi+AeuXKoMjEVfQOZzr0y941qLgM2AExvbZOqcxZ6J6krlrj4y2j9AdgKDx6GnJsVLhbc42uq584+ouSdNBpoCiCVHrz+WzUA/DDtD8ATgA3h0lMCAAzcFv+S+fSSNkeYWlTpb34mf2RfmqqJeMeklhHAfu7VoAEACgAApKRktL+KkQDWMwYCUAAAAHCKsp80xhp91UjqQBw3x45cetqkjQEyu3G9B6N+R650Uq8OVig7wOm6Wun0ea4lKDPoabJs6aLqgbhPzpv4KR4iODilw88ZpY7q1IOMcbASAOAVtmcCnobcrkG4KGS7/ZnskVWRNF9J0RUHKOnByy9WA8Dv6L4AAARMCQUA4GritfVM2lcZfH3Q3T/vZ47J2YHhcmBazjfdyuV25gLAzrc0cwAAAAAYCh6PdwAAAGyWjFW4yScjaWa2mGcofHxWxewKALglWBpLUvwwk+UOh5eNGyUOs1/EF+pZr+ud5OzoGwYdAABg2p52LiSgAY/ZVlOmilEgHn6G3OcwYjzI7vOj1t6xsx4S3lBY96EUQBF6AIBAmPYH4PoGYCoJAADWe+OZJZi7/x76/yH7Lzf9M5XzRKnFPmveMsilQHwVAAAAAKB3LQD8PCIAAADga0QujBLywzeJ4a6Z/ERVBAUlAEDqvoM7BQBAuAguzFqILtmjH3Kd4wfKobnOhA3z85qWoRPm9hwoOHoDAAlCbwDAA56FHAuXflHo3fe2ttG9XUDeA9YmYCBQ0oPr/1QC8IvuCwAAApbUAQCK22MmE3O78VAbHQT9PIPNoT9zNc3l2Oe7TAVLANBufT8MAQAAAGzT4PS8AQAAoELGHb2uaCwwEv1EWhFriUkbAaAZ27/fVZnTZXbWz3BwWpjUaMZKRj7dZ0J//gUeTdpVEwAAZOFsNxKAjQSgA+ABPoY8Jj5y2wje81jsXc/1TOQWTDYZBmAkNDiqVwuA2NJ9AQAAEBKAt9Vrsfs/2N19MO91S9rd8EHTZHnzC5MYmfQEACy/FBcAAADA5c4gi4z8RANs/m6FNXVo9DV46JG1BBDukqlw/Va5G7QbuGVSI+2aZaoLXJrdVj2zlC9Z5QEAEFz/5QzgVZwAAAAA/oXcxyC6WfTu+09Ve/c766J4VTAGUFmA51+VANKi/QPoPwYgYAkA715OH4S0s5KDHvj99MMq8TPFc3roKZnGOoT1bmIhVgc7XAMBAAAAAMAW1VbQw3gapzOpJd+Kd2fc4iSO62fJv9+movui1wUNPAj059N3OVxzk4gV73PmE8FIA2F5mRq37Evc76vLXfF4rD5UJJAw46hW6LZCb5sNLdx+kzMCAAB+hfy95+965ZCLP7B3/VlTHCvDEKtQhTm4KiCgAEAbrfbWTPssAAAAXpee1tVrozYYn41wD1aeYtkKfswN5/SXPO0JDnhO/4laUortv/s412fybe/nONdncoCHnBVliu0CQGBWlPY/5Kwom2L/kruPM6Q7oz4tvDQy+bZ3HzOi+gNHA4DZEgA='; From 0eab4d68f6cbaec6e5564c5118200ee30eb8cdaf Mon Sep 17 00:00:00 2001 From: npezza93 Date: Wed, 9 Aug 2017 23:09:28 -0400 Subject: [PATCH 051/378] Rename Bell.ts to Sounds that has BellSound constant --- src/InputHandler.ts | 2 +- src/utils/Bell.ts | 1 - src/utils/Sounds.ts | 1 + 3 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 src/utils/Bell.ts create mode 100644 src/utils/Sounds.ts diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 2208d91e..4b633e7d 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -7,7 +7,7 @@ import { C0 } from './EscapeSequences'; import { DEFAULT_CHARSET } from './Charsets'; import { CharData } from './Types'; import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from './Buffer'; -import { Bell } from './utils/Bell'; +import { BellSound } from './utils/Sounds'; /** * The terminal's standard implementation of IInputHandler, this handles all diff --git a/src/utils/Bell.ts b/src/utils/Bell.ts deleted file mode 100644 index f95bbdb0..00000000 --- a/src/utils/Bell.ts +++ /dev/null @@ -1 +0,0 @@ -export const Bell = 'data:audio/ogg;base64,T2dnUwACAAAAAAAAAADhqW5KAAAAAMFvEjYBHgF2b3JiaXMAAAAAAYC7AAAAAAAAAHcBAAAAAAC4AU9nZ1MAAAAAAAAAAAAA4aluSgEAAAAAesI3EC3//////////////////8kDdm9yYmlzHQAAAFhpcGguT3JnIGxpYlZvcmJpcyBJIDIwMDkwNzA5AAAAAAEFdm9yYmlzKUJDVgEACAAAADFMIMWA0JBVAAAQAABgJCkOk2ZJKaWUoSh5mJRISSmllMUwiZiUicUYY4wxxhhjjDHGGGOMIDRkFQAABACAKAmOo+ZJas45ZxgnjnKgOWlOOKcgB4pR4DkJwvUmY26mtKZrbs4pJQgNWQUAAAIAQEghhRRSSCGFFGKIIYYYYoghhxxyyCGnnHIKKqigggoyyCCDTDLppJNOOumoo4466ii00EILLbTSSkwx1VZjrr0GXXxzzjnnnHPOOeecc84JQkNWAQAgAAAEQgYZZBBCCCGFFFKIKaaYcgoyyIDQkFUAACAAgAAAAABHkRRJsRTLsRzN0SRP8ixREzXRM0VTVE1VVVVVdV1XdmXXdnXXdn1ZmIVbuH1ZuIVb2IVd94VhGIZhGIZhGIZh+H3f933f930gNGQVACABAKAjOZbjKaIiGqLiOaIDhIasAgBkAAAEACAJkiIpkqNJpmZqrmmbtmirtm3LsizLsgyEhqwCAAABAAQAAAAAAKBpmqZpmqZpmqZpmqZpmqZpmqZpmmZZlmVZlmVZlmVZlmVZlmVZlmVZlmVZlmVZlmVZlmVZlmVZlmVZQGjIKgBAAgBAx3Ecx3EkRVIkx3IsBwgNWQUAyAAACABAUizFcjRHczTHczzHczxHdETJlEzN9EwPCA1ZBQAAAgAIAAAAAABAMRzFcRzJ0SRPUi3TcjVXcz3Xc03XdV1XVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVYHQkFUAAAQAACGdZpZqgAgzkGEgNGQVAIAAAAAYoQhDDAgNWQUAAAQAAIih5CCa0JrzzTkOmuWgqRSb08GJVJsnuamYm3POOeecbM4Z45xzzinKmcWgmdCac85JDJqloJnQmnPOeRKbB62p0ppzzhnnnA7GGWGcc85p0poHqdlYm3POWdCa5qi5FJtzzomUmye1uVSbc84555xzzjnnnHPOqV6czsE54Zxzzonam2u5CV2cc875ZJzuzQnhnHPOOeecc84555xzzglCQ1YBAEAAAARh2BjGnYIgfY4GYhQhpiGTHnSPDpOgMcgppB6NjkZKqYNQUhknpXSC0JBVAAAgAACEEFJIIYUUUkghhRRSSCGGGGKIIaeccgoqqKSSiirKKLPMMssss8wyy6zDzjrrsMMQQwwxtNJKLDXVVmONteaec645SGultdZaK6WUUkoppSA0ZBUAAAIAQCBkkEEGGYUUUkghhphyyimnoIIKCA1ZBQAAAgAIAAAA8CTPER3RER3RER3RER3RER3P8RxREiVREiXRMi1TMz1VVFVXdm1Zl3Xbt4Vd2HXf133f141fF4ZlWZZlWZZlWZZlWZZlWZZlCUJDVgEAIAAAAEIIIYQUUkghhZRijDHHnINOQgmB0JBVAAAgAIAAAAAAR3EUx5EcyZEkS7IkTdIszfI0T/M00RNFUTRNUxVd0RV10xZlUzZd0zVl01Vl1XZl2bZlW7d9WbZ93/d93/d93/d93/d939d1IDRkFQAgAQCgIzmSIimSIjmO40iSBISGrAIAZAAABACgKI7iOI4jSZIkWZImeZZniZqpmZ7pqaIKhIasAgAAAQAEAAAAAACgaIqnmIqniIrniI4oiZZpiZqquaJsyq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7rukBoyCoAQAIAQEdyJEdyJEVSJEVyJAcIDVkFAMgAAAgAwDEcQ1Ikx7IsTfM0T/M00RM90TM9VXRFFwgNWQUAAAIACAAAAAAAwJAMS7EczdEkUVIt1VI11VItVVQ9VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV1TRN0zSB0JCVAAAZAAAjQQYZhBCKcpBCbj1YCDHmJAWhOQahxBiEpxAzDDkNInSQQSc9uJI5wwzz4FIoFURMg40lN44gDcKmXEnlOAhCQ1YEAFEAAIAxyDHEGHLOScmgRM4xCZ2UyDknpZPSSSktlhgzKSWmEmPjnKPSScmklBhLip2kEmOJrQAAgAAHAIAAC6HQkBUBQBQAAGIMUgophZRSzinmkFLKMeUcUko5p5xTzjkIHYTKMQadgxAppRxTzinHHITMQeWcg9BBKAAAIMABACDAQig0ZEUAECcA4HAkz5M0SxQlSxNFzxRl1xNN15U0zTQ1UVRVyxNV1VRV2xZNVbYlTRNNTfRUVRNFVRVV05ZNVbVtzzRl2VRV3RZV1bZl2xZ+V5Z13zNNWRZV1dZNVbV115Z9X9ZtXZg0zTQ1UVRVTRRV1VRV2zZV17Y1UXRVUVVlWVRVWXZlWfdVV9Z9SxRV1VNN2RVVVbZV2fVtVZZ94XRVXVdl2fdVWRZ+W9eF4fZ94RhV1dZN19V1VZZ9YdZlYbd13yhpmmlqoqiqmiiqqqmqtm2qrq1bouiqoqrKsmeqrqzKsq+rrmzrmiiqrqiqsiyqqiyrsqz7qizrtqiquq3KsrCbrqvrtu8LwyzrunCqrq6rsuz7qizruq3rxnHrujB8pinLpqvquqm6um7runHMtm0co6rqvirLwrDKsu/rui+0dSFRVXXdlF3jV2VZ921fd55b94WybTu/rfvKceu60vg5z28cubZtHLNuG7+t+8bzKz9hOI6lZ5q2baqqrZuqq+uybivDrOtCUVV9XZVl3zddWRdu3zeOW9eNoqrquirLvrDKsjHcxm8cuzAcXds2jlvXnbKtC31jyPcJz2vbxnH7OuP2daOvDAnHjwAAgAEHAIAAE8pAoSErAoA4AQAGIecUUxAqxSB0EFLqIKRUMQYhc05KxRyUUEpqIZTUKsYgVI5JyJyTEkpoKZTSUgehpVBKa6GU1lJrsabUYu0gpBZKaS2U0lpqqcbUWowRYxAy56RkzkkJpbQWSmktc05K56CkDkJKpaQUS0otVsxJyaCj0kFIqaQSU0mptVBKa6WkFktKMbYUW24x1hxKaS2kEltJKcYUU20txpojxiBkzknJnJMSSmktlNJa5ZiUDkJKmYOSSkqtlZJSzJyT0kFIqYOOSkkptpJKTKGU1kpKsYVSWmwx1pxSbDWU0lpJKcaSSmwtxlpbTLV1EFoLpbQWSmmttVZraq3GUEprJaUYS0qxtRZrbjHmGkppraQSW0mpxRZbji3GmlNrNabWam4x5hpbbT3WmnNKrdbUUo0txppjbb3VmnvvIKQWSmktlNJiai3G1mKtoZTWSiqxlZJabDHm2lqMOZTSYkmpxZJSjC3GmltsuaaWamwx5ppSi7Xm2nNsNfbUWqwtxppTS7XWWnOPufVWAADAgAMAQIAJZaDQkJUAQBQAAEGIUs5JaRByzDkqCULMOSepckxCKSlVzEEIJbXOOSkpxdY5CCWlFksqLcVWaykptRZrLQAAoMABACDABk2JxQEKDVkJAEQBACDGIMQYhAYZpRiD0BikFGMQIqUYc05KpRRjzknJGHMOQioZY85BKCmEUEoqKYUQSkklpQIAAAocAAACbNCUWByg0JAVAUAUAABgDGIMMYYgdFQyKhGETEonqYEQWgutddZSa6XFzFpqrbTYQAithdYySyXG1FpmrcSYWisAAOzAAQDswEIoNGQlAJAHAEAYoxRjzjlnEGLMOegcNAgx5hyEDirGnIMOQggVY85BCCGEzDkIIYQQQuYchBBCCKGDEEIIpZTSQQghhFJK6SCEEEIppXQQQgihlFIKAAAqcAAACLBRZHOCkaBCQ1YCAHkAAIAxSjkHoZRGKcYglJJSoxRjEEpJqXIMQikpxVY5B6GUlFrsIJTSWmw1dhBKaS3GWkNKrcVYa64hpdZirDXX1FqMteaaa0otxlprzbkAANwFBwCwAxtFNicYCSo0ZCUAkAcAgCCkFGOMMYYUYoox55xDCCnFmHPOKaYYc84555RijDnnnHOMMeecc845xphzzjnnHHPOOeecc44555xzzjnnnHPOOeecc84555xzzgkAACpwAAAIsFFkc4KRoEJDVgIAqQAAABFWYowxxhgbCDHGGGOMMUYSYowxxhhjbDHGGGOMMcaYYowxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGFtrrbXWWmuttdZaa6211lprrQBAvwoHAP8HG1ZHOCkaCyw0ZCUAEA4AABjDmHOOOQYdhIYp6KSEDkIIoUNKOSglhFBKKSlzTkpKpaSUWkqZc1JSKiWlllLqIKTUWkottdZaByWl1lJqrbXWOgiltNRaa6212EFIKaXWWostxlBKSq212GKMNYZSUmqtxdhirDGk0lJsLcYYY6yhlNZaazHGGGstKbXWYoy1xlprSam11mKLNdZaCwDgbnAAgEiwcYaVpLPC0eBCQ1YCACEBAARCjDnnnHMQQgghUoox56CDEEIIIURKMeYcdBBCCCGEjDHnoIMQQgghhJAx5hx0EEIIIYQQOucchBBCCKGEUkrnHHQQQgghlFBC6SCEEEIIoYRSSikdhBBCKKGEUkopJYQQQgmllFJKKaWEEEIIoYQSSimllBBCCKWUUkoppZQSQgghlFJKKaWUUkIIoZRQSimllFJKCCGEUkoppZRSSgkhhFBKKaWUUkopIYQSSimllFJKKaUAAIADBwCAACPoJKPKImw04cIDUGjISgCADAAAcdhq6ynWyCDFnISWS4SQchBiLhFSijlHsWVIGcUY1ZQxpRRTUmvonGKMUU+dY0oxw6yUVkookYLScqy1dswBAAAgCAAwECEzgUABFBjIAIADhAQpAKCwwNAxXAQE5BIyCgwKx4Rz0mkDABCEyAyRiFgMEhOqgaJiOgBYXGDIB4AMjY20iwvoMsAFXdx1IIQgBCGIxQEUkICDE2544g1PuMEJOkWlDgIAAAAA4AAAHgAAkg0gIiKaOY4Ojw+QEJERkhKTE5QAAAAAALABgA8AgCQFiIiIZo6jw+MDJERkhKTE5AQlAAAAAAAAAAAACAgIAAAAAAAEAAAACAhPZ2dTAAQYOwAAAAAAAOGpbkoCAAAAmc74DRgyNjM69TAzOTk74dnLubewsbagmZiNp4d0KbsExSY/I3XUTwJgkeZdn1HY4zoj33/q9DFtv3Ui1/jmx7lCUtPt18/sYf9MkgAsAGRBd3gMGP4sU+qCPYBy9VrA3YqJosW3W2/ef1iO/u3cg8ZG/57jU+pPmbGEJUgkfnaI39DbPqxddZphbMRmCc5rKlkUMkyx8iIoug5dJv1OYH9a59c+3Gevqc7Z2XFdDjL/qHztRfjWEWxJ/aiGezjohu9HsCZdQBKbiH0VtU/3m85lDG2T/+xkZcYnX+E+aqzv/xTgOoTFG+x7SNqQ4N+oAABSxuVXw77Jd5bmmTmuJakX7509HH0kGYKvARPpwfOSAPySPAc2EkneDwB2HwAAJlQDYK5586N79GJCjx4+p6aDUd27XSvRyXLJkIC5YZ1jLv5lpOhZTz0s+DmnF1diptrnM6UDgIW11Xh8cHTd0/SmbgOAdxcyWwMAAGIrZ3fNSfZbzKiYrK4+tPqtnMVLOeWOG2kVvUY+p2PJ/hkCl5aFRO4TLGYPZcIU3vYM1hohS4jHFlnyW/2T5J7kGsShXWT8N05V+3C/GPqJ1QdWisGPxEzHqXISBPIinWDUt7IeJv/f5OtzBxpTzZZQ+CYEhHXfqG4aABQli72GJhN4oJv+hXcApAJSErAW8G2raAX4NUcABnVt77CzZAB+LsHcVe+Q4h+QB1wh/ZrJTPxSBdI8mgTeAdTsQOoFUEng9BHcVPhxSRRYkKWZJXOFYP6V4AEripJoEjXgA2wJRZHSExmJDm8F0A6gEXsg5a4ZsALItrMB7+fh7UKLvYWSdtsDwFf1mzYzS1F82N1h2Oyt2e76B1QdS0SAsQigLPMOgJS9JRC7hFXA6kUsLFNKD5cA5cTRvgSqPc3Fl99xW3QTi/MHR8DEm6WnvaVQATwRqRKjywQ9BrrhugR2AKTsPQeQckrAOgDOhbTESyrXQ50CkNpXdtWjW7W2/3UjeX3U95gIdalfRAoAmqUEiwp53hCdcCwlg47fcbfzlmQMAgaBkh7c+fcDgF+ifwDXfzegLPcLYJsAAJQArTXjnh/uXGy3v1Hk3pV6/3t5ruW81f6prfbM2Q3WNVy98BwUtbCwhFhAWuPev6Oe/4ZaFQUcgKrVs4defzh1TADA1DEh5b3VlDaECw5b+bPfkKos3tIAue3vJZOih3ga3l6O3PSfIkrLv0PAS86PPdL7g8oc2KteNFKKzKRehOv2gJoFLBPXmaXvPBQILgJon0bbWBszrYZYYwE7jl2j+vTdU7Vpk21LiU0QajPkywAAHqbUC0/YsYOdb4e6BOp7E0cCi04Ao/TgD8ZVAMid6h/A8IeBNkp6/xsAACZELEYIk+yvI6Qz1NN6lIftB/6IMWjWJNOqPTMedAmyaj6Es0QBklJpiSWWHnQ2CoYbGWAmt+0gLQBFKCBnp2QUUQZ/1thtZDBJUpFWY82z34ocorB62oX7qB5y0oPAv/foxH25wVmgIHf2xFOr8leZcBq1Kx3ZvCq9Bga639AxuHuPNL/71YCF4EywJpqHFAX6XF0sjVbuANnvvdLcrufYwOM/iDa6iA468AYAAB6mNBMXcgTD8HSRqJ4vw8CjAlCEPACASlX/APwPOJKl9xQAAAPmnev2eWp33Xgyw3Dvfz6myGk3oyP8YTKsCOvzAgALQi0o1c6Nzs2O2Pg2h4ACIJAgAGP0aNn5x0BDgVfH7u2TtyfDcRIuYAyQhBF/lvSRAttgA6TPbWZA9gaUrZWAUEAA+Dx47Q3/r87HxUUqZmB0BmUuMlojFjHt1gDunnvuX8MImsjSq5WkzSzGS62OEIlOufWWezxWpv6FBgDgJVltfXFYtNAAnqU0xQoD0YLiXo5cF5QV4CnY1tBLAkZCOABAhbk/AM+/AwSCCdlWAAAMcFjS7owb8GVDzveDiZvznbt2tF4bL5odN1YKl88TAEABCZvufq9YCTBtMwVAQUEAwGtNltzSaHvADYC3TxLVjqiRA+OZAMhzcqEgRcAOwoCgvdTxsTHLQEF6+oOb2+PAI8ciPQcXg7pOY+LjxQSv2fjmFuj34gGwz310/bGK6z3xgT887eomWULEaDd04wHetYxdjcgV2SxvSwn0VoZXJRqkRC5ASQ/muVoAUsX7AgAQMBNaVwAAlABRxT/1PmfqLqSRNDbhXb07berpB3b94jpuWEZjBCD2OcdXFpCKEgCDfcFPMw8AAADUwT4lnUm50lmwrpMMhPQIKj6u0E8fr2vGBngMNdIlrZsigjahljud6AFVg+tzXwUnXL3TJLpajaWKA4VAAAAMiFfqJgKAZ08XrtS3dxtQNYcpPvYEG8ClvrQRJgBephwnNWJjtGqmp6VEPSvBe7EBiU3qgJbQAwD4Le8LAMDMhHbNAAAlgK+tFs5O+YyJc9yCnJa3rxLPulGnxwsXV9Fsk2k4PisCAHC8FkwbGE9gJQAAoMnyksj0CdFMZLLgoz8M+FxziwYBgIx+zHiCBAKAlBKNpF1sO9JpVcyEi9ar15YlHgrut5fPJnkdJ6vEwZPyAHQBIEDUrlMcBAAd2KAS0Qq+JwRsE4AJZtMnAD6GnOYwYlOIZvtzUNdjreB7fiMkWI0CmBB6AIAKc38A9osEFlTSGECB+cbeRDC0aRpLHqNPplcK/76Lxn2rpmqyXsYJWRi/FQAAAKBQk9MCAOibrQBQADCDsqpooPutd+05Ce9g6iEdiYXgVmQAI4+4wskEBEiBloNQ6Ki0/KTQ0QjWfjxzi+AeuXKoMjEVfQOZzr0y941qLgM2AExvbZOqcxZ6J6krlrj4y2j9AdgKDx6GnJsVLhbc42uq584+ouSdNBpoCiCVHrz+WzUA/DDtD8ATgA3h0lMCAAzcFv+S+fSSNkeYWlTpb34mf2RfmqqJeMeklhHAfu7VoAEACgAApKRktL+KkQDWMwYCUAAAAHCKsp80xhp91UjqQBw3x45cetqkjQEyu3G9B6N+R650Uq8OVig7wOm6Wun0ea4lKDPoabJs6aLqgbhPzpv4KR4iODilw88ZpY7q1IOMcbASAOAVtmcCnobcrkG4KGS7/ZnskVWRNF9J0RUHKOnByy9WA8Dv6L4AAARMCQUA4GritfVM2lcZfH3Q3T/vZ47J2YHhcmBazjfdyuV25gLAzrc0cwAAAAAYCh6PdwAAAGyWjFW4yScjaWa2mGcofHxWxewKALglWBpLUvwwk+UOh5eNGyUOs1/EF+pZr+ud5OzoGwYdAABg2p52LiSgAY/ZVlOmilEgHn6G3OcwYjzI7vOj1t6xsx4S3lBY96EUQBF6AIBAmPYH4PoGYCoJAADWe+OZJZi7/x76/yH7Lzf9M5XzRKnFPmveMsilQHwVAAAAAKB3LQD8PCIAAADga0QujBLywzeJ4a6Z/ERVBAUlAEDqvoM7BQBAuAguzFqILtmjH3Kd4wfKobnOhA3z85qWoRPm9hwoOHoDAAlCbwDAA56FHAuXflHo3fe2ttG9XUDeA9YmYCBQ0oPr/1QC8IvuCwAAApbUAQCK22MmE3O78VAbHQT9PIPNoT9zNc3l2Oe7TAVLANBufT8MAQAAAGzT4PS8AQAAoELGHb2uaCwwEv1EWhFriUkbAaAZ27/fVZnTZXbWz3BwWpjUaMZKRj7dZ0J//gUeTdpVEwAAZOFsNxKAjQSgA+ABPoY8Jj5y2wje81jsXc/1TOQWTDYZBmAkNDiqVwuA2NJ9AQAAEBKAt9Vrsfs/2N19MO91S9rd8EHTZHnzC5MYmfQEACy/FBcAAADA5c4gi4z8RANs/m6FNXVo9DV46JG1BBDukqlw/Va5G7QbuGVSI+2aZaoLXJrdVj2zlC9Z5QEAEFz/5QzgVZwAAAAA/oXcxyC6WfTu+09Ve/c766J4VTAGUFmA51+VANKi/QPoPwYgYAkA715OH4S0s5KDHvj99MMq8TPFc3roKZnGOoT1bmIhVgc7XAMBAAAAAMAW1VbQw3gapzOpJd+Kd2fc4iSO62fJv9+movui1wUNPAj059N3OVxzk4gV73PmE8FIA2F5mRq37Evc76vLXfF4rD5UJJAw46hW6LZCb5sNLdx+kzMCAAB+hfy95+965ZCLP7B3/VlTHCvDEKtQhTm4KiCgAEAbrfbWTPssAAAAXpee1tVrozYYn41wD1aeYtkKfswN5/SXPO0JDnhO/4laUortv/s412fybe/nONdncoCHnBVliu0CQGBWlPY/5Kwom2L/kruPM6Q7oz4tvDQy+bZ3HzOi+gNHA4DZEgA='; diff --git a/src/utils/Sounds.ts b/src/utils/Sounds.ts new file mode 100644 index 00000000..2169aec5 --- /dev/null +++ b/src/utils/Sounds.ts @@ -0,0 +1 @@ +export const BellSound = 'data:audio/ogg;base64,T2dnUwACAAAAAAAAAADhqW5KAAAAAMFvEjYBHgF2b3JiaXMAAAAAAYC7AAAAAAAAAHcBAAAAAAC4AU9nZ1MAAAAAAAAAAAAA4aluSgEAAAAAesI3EC3//////////////////8kDdm9yYmlzHQAAAFhpcGguT3JnIGxpYlZvcmJpcyBJIDIwMDkwNzA5AAAAAAEFdm9yYmlzKUJDVgEACAAAADFMIMWA0JBVAAAQAABgJCkOk2ZJKaWUoSh5mJRISSmllMUwiZiUicUYY4wxxhhjjDHGGGOMIDRkFQAABACAKAmOo+ZJas45ZxgnjnKgOWlOOKcgB4pR4DkJwvUmY26mtKZrbs4pJQgNWQUAAAIAQEghhRRSSCGFFGKIIYYYYoghhxxyyCGnnHIKKqigggoyyCCDTDLppJNOOumoo4466ii00EILLbTSSkwx1VZjrr0GXXxzzjnnnHPOOeecc84JQkNWAQAgAAAEQgYZZBBCCCGFFFKIKaaYcgoyyIDQkFUAACAAgAAAAABHkRRJsRTLsRzN0SRP8ixREzXRM0VTVE1VVVVVdV1XdmXXdnXXdn1ZmIVbuH1ZuIVb2IVd94VhGIZhGIZhGIZh+H3f933f930gNGQVACABAKAjOZbjKaIiGqLiOaIDhIasAgBkAAAEACAJkiIpkqNJpmZqrmmbtmirtm3LsizLsgyEhqwCAAABAAQAAAAAAKBpmqZpmqZpmqZpmqZpmqZpmqZpmmZZlmVZlmVZlmVZlmVZlmVZlmVZlmVZlmVZlmVZlmVZlmVZlmVZQGjIKgBAAgBAx3Ecx3EkRVIkx3IsBwgNWQUAyAAACABAUizFcjRHczTHczzHczxHdETJlEzN9EwPCA1ZBQAAAgAIAAAAAABAMRzFcRzJ0SRPUi3TcjVXcz3Xc03XdV1XVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVYHQkFUAAAQAACGdZpZqgAgzkGEgNGQVAIAAAAAYoQhDDAgNWQUAAAQAAIih5CCa0JrzzTkOmuWgqRSb08GJVJsnuamYm3POOeecbM4Z45xzzinKmcWgmdCac85JDJqloJnQmnPOeRKbB62p0ppzzhnnnA7GGWGcc85p0poHqdlYm3POWdCa5qi5FJtzzomUmye1uVSbc84555xzzjnnnHPOqV6czsE54Zxzzonam2u5CV2cc875ZJzuzQnhnHPOOeecc84555xzzglCQ1YBAEAAAARh2BjGnYIgfY4GYhQhpiGTHnSPDpOgMcgppB6NjkZKqYNQUhknpXSC0JBVAAAgAACEEFJIIYUUUkghhRRSSCGGGGKIIaeccgoqqKSSiirKKLPMMssss8wyy6zDzjrrsMMQQwwxtNJKLDXVVmONteaec645SGultdZaK6WUUkoppSA0ZBUAAAIAQCBkkEEGGYUUUkghhphyyimnoIIKCA1ZBQAAAgAIAAAA8CTPER3RER3RER3RER3RER3P8RxREiVREiXRMi1TMz1VVFVXdm1Zl3Xbt4Vd2HXf133f141fF4ZlWZZlWZZlWZZlWZZlWZZlCUJDVgEAIAAAAEIIIYQUUkghhZRijDHHnINOQgmB0JBVAAAgAIAAAAAAR3EUx5EcyZEkS7IkTdIszfI0T/M00RNFUTRNUxVd0RV10xZlUzZd0zVl01Vl1XZl2bZlW7d9WbZ93/d93/d93/d93/d939d1IDRkFQAgAQCgIzmSIimSIjmO40iSBISGrAIAZAAABACgKI7iOI4jSZIkWZImeZZniZqpmZ7pqaIKhIasAgAAAQAEAAAAAACgaIqnmIqniIrniI4oiZZpiZqquaJsyq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7rukBoyCoAQAIAQEdyJEdyJEVSJEVyJAcIDVkFAMgAAAgAwDEcQ1Ikx7IsTfM0T/M00RM90TM9VXRFFwgNWQUAAAIACAAAAAAAwJAMS7EczdEkUVIt1VI11VItVVQ9VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV1TRN0zSB0JCVAAAZAAAjQQYZhBCKcpBCbj1YCDHmJAWhOQahxBiEpxAzDDkNInSQQSc9uJI5wwzz4FIoFURMg40lN44gDcKmXEnlOAhCQ1YEAFEAAIAxyDHEGHLOScmgRM4xCZ2UyDknpZPSSSktlhgzKSWmEmPjnKPSScmklBhLip2kEmOJrQAAgAAHAIAAC6HQkBUBQBQAAGIMUgophZRSzinmkFLKMeUcUko5p5xTzjkIHYTKMQadgxAppRxTzinHHITMQeWcg9BBKAAAIMABACDAQig0ZEUAECcA4HAkz5M0SxQlSxNFzxRl1xNN15U0zTQ1UVRVyxNV1VRV2xZNVbYlTRNNTfRUVRNFVRVV05ZNVbVtzzRl2VRV3RZV1bZl2xZ+V5Z13zNNWRZV1dZNVbV115Z9X9ZtXZg0zTQ1UVRVTRRV1VRV2zZV17Y1UXRVUVVlWVRVWXZlWfdVV9Z9SxRV1VNN2RVVVbZV2fVtVZZ94XRVXVdl2fdVWRZ+W9eF4fZ94RhV1dZN19V1VZZ9YdZlYbd13yhpmmlqoqiqmiiqqqmqtm2qrq1bouiqoqrKsmeqrqzKsq+rrmzrmiiqrqiqsiyqqiyrsqz7qizrtqiquq3KsrCbrqvrtu8LwyzrunCqrq6rsuz7qizruq3rxnHrujB8pinLpqvquqm6um7runHMtm0co6rqvirLwrDKsu/rui+0dSFRVXXdlF3jV2VZ921fd55b94WybTu/rfvKceu60vg5z28cubZtHLNuG7+t+8bzKz9hOI6lZ5q2baqqrZuqq+uybivDrOtCUVV9XZVl3zddWRdu3zeOW9eNoqrquirLvrDKsjHcxm8cuzAcXds2jlvXnbKtC31jyPcJz2vbxnH7OuP2daOvDAnHjwAAgAEHAIAAE8pAoSErAoA4AQAGIecUUxAqxSB0EFLqIKRUMQYhc05KxRyUUEpqIZTUKsYgVI5JyJyTEkpoKZTSUgehpVBKa6GU1lJrsabUYu0gpBZKaS2U0lpqqcbUWowRYxAy56RkzkkJpbQWSmktc05K56CkDkJKpaQUS0otVsxJyaCj0kFIqaQSU0mptVBKa6WkFktKMbYUW24x1hxKaS2kEltJKcYUU20txpojxiBkzknJnJMSSmktlNJa5ZiUDkJKmYOSSkqtlZJSzJyT0kFIqYOOSkkptpJKTKGU1kpKsYVSWmwx1pxSbDWU0lpJKcaSSmwtxlpbTLV1EFoLpbQWSmmttVZraq3GUEprJaUYS0qxtRZrbjHmGkppraQSW0mpxRZbji3GmlNrNabWam4x5hpbbT3WmnNKrdbUUo0txppjbb3VmnvvIKQWSmktlNJiai3G1mKtoZTWSiqxlZJabDHm2lqMOZTSYkmpxZJSjC3GmltsuaaWamwx5ppSi7Xm2nNsNfbUWqwtxppTS7XWWnOPufVWAADAgAMAQIAJZaDQkJUAQBQAAEGIUs5JaRByzDkqCULMOSepckxCKSlVzEEIJbXOOSkpxdY5CCWlFksqLcVWaykptRZrLQAAoMABACDABk2JxQEKDVkJAEQBACDGIMQYhAYZpRiD0BikFGMQIqUYc05KpRRjzknJGHMOQioZY85BKCmEUEoqKYUQSkklpQIAAAocAAACbNCUWByg0JAVAUAUAABgDGIMMYYgdFQyKhGETEonqYEQWgutddZSa6XFzFpqrbTYQAithdYySyXG1FpmrcSYWisAAOzAAQDswEIoNGQlAJAHAEAYoxRjzjlnEGLMOegcNAgx5hyEDirGnIMOQggVY85BCCGEzDkIIYQQQuYchBBCCKGDEEIIpZTSQQghhFJK6SCEEEIppXQQQgihlFIKAAAqcAAACLBRZHOCkaBCQ1YCAHkAAIAxSjkHoZRGKcYglJJSoxRjEEpJqXIMQikpxVY5B6GUlFrsIJTSWmw1dhBKaS3GWkNKrcVYa64hpdZirDXX1FqMteaaa0otxlprzbkAANwFBwCwAxtFNicYCSo0ZCUAkAcAgCCkFGOMMYYUYoox55xDCCnFmHPOKaYYc84555RijDnnnHOMMeecc845xphzzjnnHHPOOeecc44555xzzjnnnHPOOeecc84555xzzgkAACpwAAAIsFFkc4KRoEJDVgIAqQAAABFWYowxxhgbCDHGGGOMMUYSYowxxhhjbDHGGGOMMcaYYowxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGFtrrbXWWmuttdZaa6211lprrQBAvwoHAP8HG1ZHOCkaCyw0ZCUAEA4AABjDmHOOOQYdhIYp6KSEDkIIoUNKOSglhFBKKSlzTkpKpaSUWkqZc1JSKiWlllLqIKTUWkottdZaByWl1lJqrbXWOgiltNRaa6212EFIKaXWWostxlBKSq212GKMNYZSUmqtxdhirDGk0lJsLcYYY6yhlNZaazHGGGstKbXWYoy1xlprSam11mKLNdZaCwDgbnAAgEiwcYaVpLPC0eBCQ1YCACEBAARCjDnnnHMQQgghUoox56CDEEIIIURKMeYcdBBCCCGEjDHnoIMQQgghhJAx5hx0EEIIIYQQOucchBBCCKGEUkrnHHQQQgghlFBC6SCEEEIIoYRSSikdhBBCKKGEUkopJYQQQgmllFJKKaWEEEIIoYQSSimllBBCCKWUUkoppZQSQgghlFJKKaWUUkIIoZRQSimllFJKCCGEUkoppZRSSgkhhFBKKaWUUkopIYQSSimllFJKKaUAAIADBwCAACPoJKPKImw04cIDUGjISgCADAAAcdhq6ynWyCDFnISWS4SQchBiLhFSijlHsWVIGcUY1ZQxpRRTUmvonGKMUU+dY0oxw6yUVkookYLScqy1dswBAAAgCAAwECEzgUABFBjIAIADhAQpAKCwwNAxXAQE5BIyCgwKx4Rz0mkDABCEyAyRiFgMEhOqgaJiOgBYXGDIB4AMjY20iwvoMsAFXdx1IIQgBCGIxQEUkICDE2544g1PuMEJOkWlDgIAAAAA4AAAHgAAkg0gIiKaOY4Ojw+QEJERkhKTE5QAAAAAALABgA8AgCQFiIiIZo6jw+MDJERkhKTE5AQlAAAAAAAAAAAACAgIAAAAAAAEAAAACAhPZ2dTAAQYOwAAAAAAAOGpbkoCAAAAmc74DRgyNjM69TAzOTk74dnLubewsbagmZiNp4d0KbsExSY/I3XUTwJgkeZdn1HY4zoj33/q9DFtv3Ui1/jmx7lCUtPt18/sYf9MkgAsAGRBd3gMGP4sU+qCPYBy9VrA3YqJosW3W2/ef1iO/u3cg8ZG/57jU+pPmbGEJUgkfnaI39DbPqxddZphbMRmCc5rKlkUMkyx8iIoug5dJv1OYH9a59c+3Gevqc7Z2XFdDjL/qHztRfjWEWxJ/aiGezjohu9HsCZdQBKbiH0VtU/3m85lDG2T/+xkZcYnX+E+aqzv/xTgOoTFG+x7SNqQ4N+oAABSxuVXw77Jd5bmmTmuJakX7509HH0kGYKvARPpwfOSAPySPAc2EkneDwB2HwAAJlQDYK5586N79GJCjx4+p6aDUd27XSvRyXLJkIC5YZ1jLv5lpOhZTz0s+DmnF1diptrnM6UDgIW11Xh8cHTd0/SmbgOAdxcyWwMAAGIrZ3fNSfZbzKiYrK4+tPqtnMVLOeWOG2kVvUY+p2PJ/hkCl5aFRO4TLGYPZcIU3vYM1hohS4jHFlnyW/2T5J7kGsShXWT8N05V+3C/GPqJ1QdWisGPxEzHqXISBPIinWDUt7IeJv/f5OtzBxpTzZZQ+CYEhHXfqG4aABQli72GJhN4oJv+hXcApAJSErAW8G2raAX4NUcABnVt77CzZAB+LsHcVe+Q4h+QB1wh/ZrJTPxSBdI8mgTeAdTsQOoFUEng9BHcVPhxSRRYkKWZJXOFYP6V4AEripJoEjXgA2wJRZHSExmJDm8F0A6gEXsg5a4ZsALItrMB7+fh7UKLvYWSdtsDwFf1mzYzS1F82N1h2Oyt2e76B1QdS0SAsQigLPMOgJS9JRC7hFXA6kUsLFNKD5cA5cTRvgSqPc3Fl99xW3QTi/MHR8DEm6WnvaVQATwRqRKjywQ9BrrhugR2AKTsPQeQckrAOgDOhbTESyrXQ50CkNpXdtWjW7W2/3UjeX3U95gIdalfRAoAmqUEiwp53hCdcCwlg47fcbfzlmQMAgaBkh7c+fcDgF+ifwDXfzegLPcLYJsAAJQArTXjnh/uXGy3v1Hk3pV6/3t5ruW81f6prfbM2Q3WNVy98BwUtbCwhFhAWuPev6Oe/4ZaFQUcgKrVs4defzh1TADA1DEh5b3VlDaECw5b+bPfkKos3tIAue3vJZOih3ga3l6O3PSfIkrLv0PAS86PPdL7g8oc2KteNFKKzKRehOv2gJoFLBPXmaXvPBQILgJon0bbWBszrYZYYwE7jl2j+vTdU7Vpk21LiU0QajPkywAAHqbUC0/YsYOdb4e6BOp7E0cCi04Ao/TgD8ZVAMid6h/A8IeBNkp6/xsAACZELEYIk+yvI6Qz1NN6lIftB/6IMWjWJNOqPTMedAmyaj6Es0QBklJpiSWWHnQ2CoYbGWAmt+0gLQBFKCBnp2QUUQZ/1thtZDBJUpFWY82z34ocorB62oX7qB5y0oPAv/foxH25wVmgIHf2xFOr8leZcBq1Kx3ZvCq9Bga639AxuHuPNL/71YCF4EywJpqHFAX6XF0sjVbuANnvvdLcrufYwOM/iDa6iA468AYAAB6mNBMXcgTD8HSRqJ4vw8CjAlCEPACASlX/APwPOJKl9xQAAAPmnev2eWp33Xgyw3Dvfz6myGk3oyP8YTKsCOvzAgALQi0o1c6Nzs2O2Pg2h4ACIJAgAGP0aNn5x0BDgVfH7u2TtyfDcRIuYAyQhBF/lvSRAttgA6TPbWZA9gaUrZWAUEAA+Dx47Q3/r87HxUUqZmB0BmUuMlojFjHt1gDunnvuX8MImsjSq5WkzSzGS62OEIlOufWWezxWpv6FBgDgJVltfXFYtNAAnqU0xQoD0YLiXo5cF5QV4CnY1tBLAkZCOABAhbk/AM+/AwSCCdlWAAAMcFjS7owb8GVDzveDiZvznbt2tF4bL5odN1YKl88TAEABCZvufq9YCTBtMwVAQUEAwGtNltzSaHvADYC3TxLVjqiRA+OZAMhzcqEgRcAOwoCgvdTxsTHLQEF6+oOb2+PAI8ciPQcXg7pOY+LjxQSv2fjmFuj34gGwz310/bGK6z3xgT887eomWULEaDd04wHetYxdjcgV2SxvSwn0VoZXJRqkRC5ASQ/muVoAUsX7AgAQMBNaVwAAlABRxT/1PmfqLqSRNDbhXb07berpB3b94jpuWEZjBCD2OcdXFpCKEgCDfcFPMw8AAADUwT4lnUm50lmwrpMMhPQIKj6u0E8fr2vGBngMNdIlrZsigjahljud6AFVg+tzXwUnXL3TJLpajaWKA4VAAAAMiFfqJgKAZ08XrtS3dxtQNYcpPvYEG8ClvrQRJgBephwnNWJjtGqmp6VEPSvBe7EBiU3qgJbQAwD4Le8LAMDMhHbNAAAlgK+tFs5O+YyJc9yCnJa3rxLPulGnxwsXV9Fsk2k4PisCAHC8FkwbGE9gJQAAoMnyksj0CdFMZLLgoz8M+FxziwYBgIx+zHiCBAKAlBKNpF1sO9JpVcyEi9ar15YlHgrut5fPJnkdJ6vEwZPyAHQBIEDUrlMcBAAd2KAS0Qq+JwRsE4AJZtMnAD6GnOYwYlOIZvtzUNdjreB7fiMkWI0CmBB6AIAKc38A9osEFlTSGECB+cbeRDC0aRpLHqNPplcK/76Lxn2rpmqyXsYJWRi/FQAAAKBQk9MCAOibrQBQADCDsqpooPutd+05Ce9g6iEdiYXgVmQAI4+4wskEBEiBloNQ6Ki0/KTQ0QjWfjxzi+AeuXKoMjEVfQOZzr0y941qLgM2AExvbZOqcxZ6J6krlrj4y2j9AdgKDx6GnJsVLhbc42uq584+ouSdNBpoCiCVHrz+WzUA/DDtD8ATgA3h0lMCAAzcFv+S+fSSNkeYWlTpb34mf2RfmqqJeMeklhHAfu7VoAEACgAApKRktL+KkQDWMwYCUAAAAHCKsp80xhp91UjqQBw3x45cetqkjQEyu3G9B6N+R650Uq8OVig7wOm6Wun0ea4lKDPoabJs6aLqgbhPzpv4KR4iODilw88ZpY7q1IOMcbASAOAVtmcCnobcrkG4KGS7/ZnskVWRNF9J0RUHKOnByy9WA8Dv6L4AAARMCQUA4GritfVM2lcZfH3Q3T/vZ47J2YHhcmBazjfdyuV25gLAzrc0cwAAAAAYCh6PdwAAAGyWjFW4yScjaWa2mGcofHxWxewKALglWBpLUvwwk+UOh5eNGyUOs1/EF+pZr+ud5OzoGwYdAABg2p52LiSgAY/ZVlOmilEgHn6G3OcwYjzI7vOj1t6xsx4S3lBY96EUQBF6AIBAmPYH4PoGYCoJAADWe+OZJZi7/x76/yH7Lzf9M5XzRKnFPmveMsilQHwVAAAAAKB3LQD8PCIAAADga0QujBLywzeJ4a6Z/ERVBAUlAEDqvoM7BQBAuAguzFqILtmjH3Kd4wfKobnOhA3z85qWoRPm9hwoOHoDAAlCbwDAA56FHAuXflHo3fe2ttG9XUDeA9YmYCBQ0oPr/1QC8IvuCwAAApbUAQCK22MmE3O78VAbHQT9PIPNoT9zNc3l2Oe7TAVLANBufT8MAQAAAGzT4PS8AQAAoELGHb2uaCwwEv1EWhFriUkbAaAZ27/fVZnTZXbWz3BwWpjUaMZKRj7dZ0J//gUeTdpVEwAAZOFsNxKAjQSgA+ABPoY8Jj5y2wje81jsXc/1TOQWTDYZBmAkNDiqVwuA2NJ9AQAAEBKAt9Vrsfs/2N19MO91S9rd8EHTZHnzC5MYmfQEACy/FBcAAADA5c4gi4z8RANs/m6FNXVo9DV46JG1BBDukqlw/Va5G7QbuGVSI+2aZaoLXJrdVj2zlC9Z5QEAEFz/5QzgVZwAAAAA/oXcxyC6WfTu+09Ve/c766J4VTAGUFmA51+VANKi/QPoPwYgYAkA715OH4S0s5KDHvj99MMq8TPFc3roKZnGOoT1bmIhVgc7XAMBAAAAAMAW1VbQw3gapzOpJd+Kd2fc4iSO62fJv9+movui1wUNPAj059N3OVxzk4gV73PmE8FIA2F5mRq37Evc76vLXfF4rD5UJJAw46hW6LZCb5sNLdx+kzMCAAB+hfy95+965ZCLP7B3/VlTHCvDEKtQhTm4KiCgAEAbrfbWTPssAAAAXpee1tVrozYYn41wD1aeYtkKfswN5/SXPO0JDnhO/4laUortv/s412fybe/nONdncoCHnBVliu0CQGBWlPY/5Kwom2L/kruPM6Q7oz4tvDQy+bZ3HzOi+gNHA4DZEgA='; From fd51d0acf380f0a2008248e76d7d1705da09dd6c Mon Sep 17 00:00:00 2001 From: npezza93 Date: Wed, 9 Aug 2017 23:17:32 -0400 Subject: [PATCH 052/378] Preload bell audio Allow the bellSound to be more easily configurable. You can pass false to the bellSound and no sound will be played or you can pass it any source you'd like. --- src/InputHandler.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 4b633e7d..6ee081d2 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -108,8 +108,8 @@ export class InputHandler implements IInputHandler { */ public bell(): void { this._terminal.emit('bell'); - if (this._terminal.playDefaultBell) { - (new Audio(Bell)).play(); + if (this._terminal.bellSound) { + this._terminal.bellAudioElement.play(); } if (!this._terminal.visualBell) { return; From 6b46e0837fc35e9f0f2367bd1475e5274a3be1f1 Mon Sep 17 00:00:00 2001 From: npezza93 Date: Thu, 10 Aug 2017 18:27:05 -0400 Subject: [PATCH 053/378] Move preloading bell audio to open() and append it the the terminal element --- src/Terminal.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Terminal.ts b/src/Terminal.ts index 8edb406d..6cda1a6b 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -689,6 +689,14 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.viewportScrollArea.classList.add('xterm-scroll-area'); this.viewportElement.appendChild(this.viewportScrollArea); + // preload audio + if (this.options.bellSound) { + this.bellAudioElement = document.createElement('audio'); + this.bellAudioElement.setAttribute('preload', 'auto'); + this.bellAudioElement.setAttribute('src', this.options.bellSound); + this.element.appendChild(this.bellAudioElement); + } + // Create the selection container. this.selectionContainer = document.createElement('div'); this.selectionContainer.classList.add('xterm-selection'); From 8a1f365a7ecdc8b46891f95ce2bb4c1f38fbf8ef Mon Sep 17 00:00:00 2001 From: npezza93 Date: Sat, 12 Aug 2017 11:28:29 -0400 Subject: [PATCH 054/378] Add ability to have a visual bell --- demo/main.js | 3 ++- src/Interfaces.ts | 6 +++--- src/Terminal.ts | 23 +++++++++++++++-------- src/Types.ts | 8 +++++--- src/utils/BellStyles.ts | 5 +++++ src/xterm.css | 6 +++--- 6 files changed, 33 insertions(+), 18 deletions(-) create mode 100644 src/utils/BellStyles.ts diff --git a/demo/main.js b/demo/main.js index 9098c2f0..1464bcc6 100644 --- a/demo/main.js +++ b/demo/main.js @@ -70,7 +70,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), + bellStyles: [1, 2] }); term.on('resize', function (size) { if (!pid) { diff --git a/src/Interfaces.ts b/src/Interfaces.ts index e9db3f9c..741ae09b 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -3,7 +3,7 @@ */ import { ILinkMatcherOptions } from './Interfaces'; -import { LinkMatcherHandler, LinkMatcherValidationCallback, Charset, LineData } from './Types'; +import { LinkMatcherHandler, LinkMatcherValidationCallback, Charset, LineData, BellStylesEnum } from './Types'; export interface IBrowser { isNode: boolean; @@ -113,6 +113,8 @@ export interface IInputHandlingTerminal extends IEventEmitter { } export interface ITerminalOptions { + bellSound?: string; + bellStyles?: BellStylesEnum[]; cancelEvents?: boolean; colors?: string[]; cols?: number; @@ -123,14 +125,12 @@ export interface ITerminalOptions { disableStdin?: boolean; geometry?: [number, number]; handler?: (data: string) => void; - popOnBell?: boolean; rows?: number; screenKeys?: boolean; scrollback?: number; tabStopWidth?: number; termName?: string; useFlowControl?: boolean; - visualBell?: boolean; } export interface IBuffer { diff --git a/src/Terminal.ts b/src/Terminal.ts index 6cda1a6b..5c9b51a2 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -39,6 +39,8 @@ import { CHARSETS } from './Charsets'; import { getRawByteCoords } from './utils/Mouse'; import { CustomKeyEventHandler, Charset, LinkMatcherHandler, LinkMatcherValidationCallback, CharData, LineData, Option, StringOption, BooleanOption, StringArrayOption, NumberOption, GeometryOption, HandlerOption } from './Types'; import { ITerminal, IBrowser, ITerminalOptions, IInputHandlingTerminal, ILinkMatcherOptions, IViewport, ICompositionHelper } from './Interfaces'; +import { BellSound } from './utils/Sounds'; +import { BellStyles } from './utils/BellStyles'; // Declare for RequireJS in loadAddon declare var define: any; @@ -147,8 +149,8 @@ const DEFAULT_OPTIONS: ITerminalOptions = { geometry: [80, 24], cursorBlink: false, cursorStyle: 'block', - visualBell: false, - popOnBell: false, + bellSound: BellSound, + bellStyles: [BellStyles.Sound], scrollback: 1000, screenKeys: false, debug: false, @@ -1882,12 +1884,17 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * Note: We could do sweet things with webaudio here */ public bell(): void { - if (!this.options.visualBell) return; - this.element.style.borderColor = 'white'; - setTimeout(() => { - this.element.style.borderColor = ''; - }, 10); - if (this.options.popOnBell) this.focus(); + this.emit('bell'); + if (this.options.bellStyles.indexOf(BellStyles.Sound) > -1) { + this.bellAudioElement.play(); + } + if (this.options.bellStyles.indexOf(BellStyles.Visual) > -1) { + var cursor = this.element.querySelector('.terminal-cursor') as HTMLElement; + cursor.style.backgroundColor = "#fff"; + setTimeout(function() { + cursor.style.backgroundColor = "#e6e6e6"; + }, 200); + } } /** diff --git a/src/Types.ts b/src/Types.ts index 0de78d52..e32f6295 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -2,6 +2,8 @@ * @license MIT */ +import { BellStyles } from './utils/BellStyles'; + export type LinkMatcher = { id: number, regex: RegExp, @@ -26,10 +28,8 @@ export type BooleanOption = 'cursorBlink' | 'debug' | 'disableStdin' | - 'popOnBell' | 'screenKeys' | - 'useFlowControl' | - 'visualBell'; + 'useFlowControl'; export type StringOption = 'cursorStyle' | 'termName'; @@ -41,3 +41,5 @@ export type NumberOption = 'scrollback'; export type GeometryOption = 'geometry'; export type HandlerOption = 'handler'; + +export type BellStylesEnum = BellStyles; diff --git a/src/utils/BellStyles.ts b/src/utils/BellStyles.ts new file mode 100644 index 00000000..5534ea54 --- /dev/null +++ b/src/utils/BellStyles.ts @@ -0,0 +1,5 @@ +export enum BellStyles { + None, + Sound, + Visual +} diff --git a/src/xterm.css b/src/xterm.css index 89daf9e3..299b1e37 100644 --- a/src/xterm.css +++ b/src/xterm.css @@ -94,12 +94,12 @@ } .terminal:not(.focus) .terminal-cursor { - outline: 1px solid #fff; + outline: 1px solid #e6e6e6; outline-offset: -1px; } .terminal.xterm-cursor-style-block.focus:not(.xterm-cursor-blink-on) .terminal-cursor { - background-color: #fff; + background-color: #e6e6e6; color: #000; } @@ -107,7 +107,7 @@ .terminal.focus.xterm-cursor-style-underline:not(.xterm-cursor-blink-on) .terminal-cursor::before { content: ''; position: absolute; - background-color: #fff; + background-color: ##e6e6e6; } .terminal.focus.xterm-cursor-style-bar:not(.xterm-cursor-blink-on) .terminal-cursor::before { From 44ed0b9cebaeeced6afcc6ebe193797ea876d8dd Mon Sep 17 00:00:00 2001 From: npezza93 Date: Sat, 12 Aug 2017 11:40:50 -0400 Subject: [PATCH 055/378] Dont hardcode as many color codes in xterm.ts --- src/Terminal.ts | 3 ++- src/xterm.css | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 5c9b51a2..62449654 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1890,9 +1890,10 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT } if (this.options.bellStyles.indexOf(BellStyles.Visual) > -1) { var cursor = this.element.querySelector('.terminal-cursor') as HTMLElement; + var originalBackground = cursor.style.backgroundColor; cursor.style.backgroundColor = "#fff"; setTimeout(function() { - cursor.style.backgroundColor = "#e6e6e6"; + cursor.style.backgroundColor = originalBackground; }, 200); } } diff --git a/src/xterm.css b/src/xterm.css index 299b1e37..7ccad12f 100644 --- a/src/xterm.css +++ b/src/xterm.css @@ -94,12 +94,12 @@ } .terminal:not(.focus) .terminal-cursor { - outline: 1px solid #e6e6e6; + outline: 1px solid #eeeeec; outline-offset: -1px; } .terminal.xterm-cursor-style-block.focus:not(.xterm-cursor-blink-on) .terminal-cursor { - background-color: #e6e6e6; + background-color: #eeeeec; color: #000; } @@ -107,7 +107,7 @@ .terminal.focus.xterm-cursor-style-underline:not(.xterm-cursor-blink-on) .terminal-cursor::before { content: ''; position: absolute; - background-color: ##e6e6e6; + background-color: #eeeeec; } .terminal.focus.xterm-cursor-style-bar:not(.xterm-cursor-blink-on) .terminal-cursor::before { From 8c94ef8aa09eb478098b99f0698ac8ff1fe7373b Mon Sep 17 00:00:00 2001 From: npezza93 Date: Sun, 13 Aug 2017 16:05:37 -0400 Subject: [PATCH 056/378] Clear out any existing vidualBellTimers. Set colors back to all white. Use visual class instead of setting colors codes in js. --- src/Terminal.ts | 15 ++++++++------- src/xterm.css | 10 +++++++--- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 62449654..e9b499b1 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -150,7 +150,7 @@ const DEFAULT_OPTIONS: ITerminalOptions = { cursorBlink: false, cursorStyle: 'block', bellSound: BellSound, - bellStyles: [BellStyles.Sound], + bellStyles: [BellStyles.Sound, BellStyles.Visual], scrollback: 1000, screenKeys: false, debug: false, @@ -180,6 +180,8 @@ 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; @@ -692,7 +694,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.viewportElement.appendChild(this.viewportScrollArea); // preload audio - if (this.options.bellSound) { + if (this.options.bellStyles.indexOf(BellStyles.Sound) > -1) { this.bellAudioElement = document.createElement('audio'); this.bellAudioElement.setAttribute('preload', 'auto'); this.bellAudioElement.setAttribute('src', this.options.bellSound); @@ -1889,11 +1891,10 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.bellAudioElement.play(); } if (this.options.bellStyles.indexOf(BellStyles.Visual) > -1) { - var cursor = this.element.querySelector('.terminal-cursor') as HTMLElement; - var originalBackground = cursor.style.backgroundColor; - cursor.style.backgroundColor = "#fff"; - setTimeout(function() { - cursor.style.backgroundColor = originalBackground; + this.element.classList.add("visual-bell-active"); + clearTimeout(this.visualBellTimer); + this.visualBellTimer = window.setTimeout(() => { + this.element.classList.remove("visual-bell-active"); }, 200); } } diff --git a/src/xterm.css b/src/xterm.css index 7ccad12f..0996742b 100644 --- a/src/xterm.css +++ b/src/xterm.css @@ -94,12 +94,12 @@ } .terminal:not(.focus) .terminal-cursor { - outline: 1px solid #eeeeec; + outline: 1px solid #fff; outline-offset: -1px; } .terminal.xterm-cursor-style-block.focus:not(.xterm-cursor-blink-on) .terminal-cursor { - background-color: #eeeeec; + background-color: #fff; color: #000; } @@ -107,7 +107,7 @@ .terminal.focus.xterm-cursor-style-underline:not(.xterm-cursor-blink-on) .terminal-cursor::before { content: ''; position: absolute; - background-color: #eeeeec; + background-color: #fff; } .terminal.focus.xterm-cursor-style-bar:not(.xterm-cursor-blink-on) .terminal-cursor::before { @@ -124,6 +124,10 @@ height: 1px; } +.visual-bell-active .terminal-cursor { + background-color: #fff !important; +} + .terminal .composition-view { background: #000; color: #FFF; From 3027b1d3d491d14250aa9280087c5f690a0621c6 Mon Sep 17 00:00:00 2001 From: npezza93 Date: Sun, 13 Aug 2017 16:07:27 -0400 Subject: [PATCH 057/378] Set the visual bell color to a light gray --- src/xterm.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xterm.css b/src/xterm.css index 0996742b..32f2a7c8 100644 --- a/src/xterm.css +++ b/src/xterm.css @@ -125,7 +125,7 @@ } .visual-bell-active .terminal-cursor { - background-color: #fff !important; + background-color: #ccc !important; } .terminal .composition-view { From 96c9d50ee292bf65bfcbbe43930c76a44beaec3f Mon Sep 17 00:00:00 2001 From: npezza93 Date: Sun, 13 Aug 2017 16:09:06 -0400 Subject: [PATCH 058/378] Fix some quote lints --- src/Terminal.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index e9b499b1..69d5c908 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1891,10 +1891,10 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.bellAudioElement.play(); } if (this.options.bellStyles.indexOf(BellStyles.Visual) > -1) { - this.element.classList.add("visual-bell-active"); + this.element.classList.add('visual-bell-active'); clearTimeout(this.visualBellTimer); this.visualBellTimer = window.setTimeout(() => { - this.element.classList.remove("visual-bell-active"); + this.element.classList.remove('visual-bell-active'); }, 200); } } From d8717d64bac4f522a60ca599663f2be894c97ca2 Mon Sep 17 00:00:00 2001 From: npezza93 Date: Mon, 14 Aug 2017 18:11:22 -0400 Subject: [PATCH 059/378] Add visual bell styling for underline and bar cursor types --- src/xterm.css | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/xterm.css b/src/xterm.css index 32f2a7c8..0da9e041 100644 --- a/src/xterm.css +++ b/src/xterm.css @@ -91,6 +91,7 @@ .terminal .terminal-cursor { position: relative; + transition: background-color 150ms ease; } .terminal:not(.focus) .terminal-cursor { @@ -108,6 +109,7 @@ content: ''; position: absolute; background-color: #fff; + transition: background-color 150ms ease; } .terminal.focus.xterm-cursor-style-bar:not(.xterm-cursor-blink-on) .terminal-cursor::before { @@ -124,9 +126,13 @@ height: 1px; } -.visual-bell-active .terminal-cursor { +.terminal.focus.xterm-cursor-style-block.visual-bell-active .terminal-cursor { background-color: #ccc !important; } +.terminal.focus.xterm-cursor-style-bar.visual-bell-active .terminal-cursor::before, +.terminal.focus.xterm-cursor-style-underline.visual-bell-active .terminal-cursor::before { + background-color: transparent; +} .terminal .composition-view { background: #000; From 2f1380a31d4872d20f934f59083de07c7a6360f2 Mon Sep 17 00:00:00 2001 From: npezza93 Date: Wed, 16 Aug 2017 19:13:29 -0400 Subject: [PATCH 060/378] Make bellStyle singular. Preload sound on option setting. Use strings as options --- demo/main.js | 3 +-- src/Interfaces.ts | 4 ++-- src/Terminal.ts | 36 ++++++++++++++++++++++++++---------- src/Types.ts | 5 +---- src/utils/BellStyles.ts | 5 ----- 5 files changed, 30 insertions(+), 23 deletions(-) delete mode 100644 src/utils/BellStyles.ts diff --git a/demo/main.js b/demo/main.js index 1464bcc6..9098c2f0 100644 --- a/demo/main.js +++ b/demo/main.js @@ -70,8 +70,7 @@ function createTerminal() { term = new Terminal({ cursorBlink: optionElements.cursorBlink.checked, scrollback: parseInt(optionElements.scrollback.value, 10), - tabStopWidth: parseInt(optionElements.tabstopwidth.value, 10), - bellStyles: [1, 2] + tabStopWidth: parseInt(optionElements.tabstopwidth.value, 10) }); term.on('resize', function (size) { if (!pid) { diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 741ae09b..9ab16c3c 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -3,7 +3,7 @@ */ import { ILinkMatcherOptions } from './Interfaces'; -import { LinkMatcherHandler, LinkMatcherValidationCallback, Charset, LineData, BellStylesEnum } from './Types'; +import { LinkMatcherHandler, LinkMatcherValidationCallback, Charset, LineData } from './Types'; export interface IBrowser { isNode: boolean; @@ -114,7 +114,7 @@ export interface IInputHandlingTerminal extends IEventEmitter { export interface ITerminalOptions { bellSound?: string; - bellStyles?: BellStylesEnum[]; + bellStyle?: string[]; cancelEvents?: boolean; colors?: string[]; cols?: number; diff --git a/src/Terminal.ts b/src/Terminal.ts index 69d5c908..fae8e380 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -40,7 +40,6 @@ import { getRawByteCoords } from './utils/Mouse'; import { CustomKeyEventHandler, Charset, LinkMatcherHandler, LinkMatcherValidationCallback, CharData, LineData, Option, StringOption, BooleanOption, StringArrayOption, NumberOption, GeometryOption, HandlerOption } from './Types'; import { ITerminal, IBrowser, ITerminalOptions, IInputHandlingTerminal, ILinkMatcherOptions, IViewport, ICompositionHelper } from './Interfaces'; import { BellSound } from './utils/Sounds'; -import { BellStyles } from './utils/BellStyles'; // Declare for RequireJS in loadAddon declare var define: any; @@ -150,7 +149,7 @@ const DEFAULT_OPTIONS: ITerminalOptions = { cursorBlink: false, cursorStyle: 'block', bellSound: BellSound, - bellStyles: [BellStyles.Sound, BellStyles.Visual], + bellStyle: null, scrollback: 1000, screenKeys: false, debug: false, @@ -486,6 +485,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.viewport.syncScrollArea(); break; case 'tabStopWidth': this.setupStops(); break; + case 'bellStyle': this.preloadBellSound(); break; } } @@ -694,12 +694,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.viewportElement.appendChild(this.viewportScrollArea); // preload audio - if (this.options.bellStyles.indexOf(BellStyles.Sound) > -1) { - this.bellAudioElement = document.createElement('audio'); - this.bellAudioElement.setAttribute('preload', 'auto'); - this.bellAudioElement.setAttribute('src', this.options.bellSound); - this.element.appendChild(this.bellAudioElement); - } + this.preloadBellSound() // Create the selection container. this.selectionContainer = document.createElement('div'); @@ -1887,10 +1882,10 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT */ public bell(): void { this.emit('bell'); - if (this.options.bellStyles.indexOf(BellStyles.Sound) > -1) { + if (this.soundBell()) { this.bellAudioElement.play(); } - if (this.options.bellStyles.indexOf(BellStyles.Visual) > -1) { + if (this.visualBell()) { this.element.classList.add('visual-bell-active'); clearTimeout(this.visualBellTimer); this.visualBellTimer = window.setTimeout(() => { @@ -2288,6 +2283,27 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT return matchColorCache[hash] = li; } + + private visualBell() { + var styles = [].concat.apply([], [this.options.bellStyle]); + + return styles.indexOf('visual') > -1 || styles.indexOf('both') > -1 + } + + private soundBell() { + var styles = [].concat.apply([], [this.options.bellStyle]); + + return styles.indexOf('sound') > -1 || styles.indexOf('both') > -1 + } + + private preloadBellSound() { + if (this.soundBell()) { + this.bellAudioElement = document.createElement('audio'); + this.bellAudioElement.setAttribute('preload', 'auto'); + this.bellAudioElement.setAttribute('src', this.options.bellSound); + this.element.appendChild(this.bellAudioElement); + } + } } /** diff --git a/src/Types.ts b/src/Types.ts index e32f6295..c287030d 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -2,8 +2,6 @@ * @license MIT */ -import { BellStyles } from './utils/BellStyles'; - export type LinkMatcher = { id: number, regex: RegExp, @@ -32,6 +30,7 @@ export type BooleanOption = 'useFlowControl'; export type StringOption = 'cursorStyle' | + 'bellStyle' | 'termName'; export type StringArrayOption = 'colors'; export type NumberOption = @@ -41,5 +40,3 @@ export type NumberOption = 'scrollback'; export type GeometryOption = 'geometry'; export type HandlerOption = 'handler'; - -export type BellStylesEnum = BellStyles; diff --git a/src/utils/BellStyles.ts b/src/utils/BellStyles.ts deleted file mode 100644 index 5534ea54..00000000 --- a/src/utils/BellStyles.ts +++ /dev/null @@ -1,5 +0,0 @@ -export enum BellStyles { - None, - Sound, - Visual -} From c41bd81526be41650af1c72e0d065e50ff61b020 Mon Sep 17 00:00:00 2001 From: npezza93 Date: Wed, 16 Aug 2017 19:29:35 -0400 Subject: [PATCH 061/378] Swap out bell sound for a free one --- src/utils/Sounds.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/Sounds.ts b/src/utils/Sounds.ts index 2169aec5..65c20c3c 100644 --- a/src/utils/Sounds.ts +++ b/src/utils/Sounds.ts @@ -1 +1 @@ -export const BellSound = 'data:audio/ogg;base64,T2dnUwACAAAAAAAAAADhqW5KAAAAAMFvEjYBHgF2b3JiaXMAAAAAAYC7AAAAAAAAAHcBAAAAAAC4AU9nZ1MAAAAAAAAAAAAA4aluSgEAAAAAesI3EC3//////////////////8kDdm9yYmlzHQAAAFhpcGguT3JnIGxpYlZvcmJpcyBJIDIwMDkwNzA5AAAAAAEFdm9yYmlzKUJDVgEACAAAADFMIMWA0JBVAAAQAABgJCkOk2ZJKaWUoSh5mJRISSmllMUwiZiUicUYY4wxxhhjjDHGGGOMIDRkFQAABACAKAmOo+ZJas45ZxgnjnKgOWlOOKcgB4pR4DkJwvUmY26mtKZrbs4pJQgNWQUAAAIAQEghhRRSSCGFFGKIIYYYYoghhxxyyCGnnHIKKqigggoyyCCDTDLppJNOOumoo4466ii00EILLbTSSkwx1VZjrr0GXXxzzjnnnHPOOeecc84JQkNWAQAgAAAEQgYZZBBCCCGFFFKIKaaYcgoyyIDQkFUAACAAgAAAAABHkRRJsRTLsRzN0SRP8ixREzXRM0VTVE1VVVVVdV1XdmXXdnXXdn1ZmIVbuH1ZuIVb2IVd94VhGIZhGIZhGIZh+H3f933f930gNGQVACABAKAjOZbjKaIiGqLiOaIDhIasAgBkAAAEACAJkiIpkqNJpmZqrmmbtmirtm3LsizLsgyEhqwCAAABAAQAAAAAAKBpmqZpmqZpmqZpmqZpmqZpmqZpmmZZlmVZlmVZlmVZlmVZlmVZlmVZlmVZlmVZlmVZlmVZlmVZlmVZQGjIKgBAAgBAx3Ecx3EkRVIkx3IsBwgNWQUAyAAACABAUizFcjRHczTHczzHczxHdETJlEzN9EwPCA1ZBQAAAgAIAAAAAABAMRzFcRzJ0SRPUi3TcjVXcz3Xc03XdV1XVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVYHQkFUAAAQAACGdZpZqgAgzkGEgNGQVAIAAAAAYoQhDDAgNWQUAAAQAAIih5CCa0JrzzTkOmuWgqRSb08GJVJsnuamYm3POOeecbM4Z45xzzinKmcWgmdCac85JDJqloJnQmnPOeRKbB62p0ppzzhnnnA7GGWGcc85p0poHqdlYm3POWdCa5qi5FJtzzomUmye1uVSbc84555xzzjnnnHPOqV6czsE54Zxzzonam2u5CV2cc875ZJzuzQnhnHPOOeecc84555xzzglCQ1YBAEAAAARh2BjGnYIgfY4GYhQhpiGTHnSPDpOgMcgppB6NjkZKqYNQUhknpXSC0JBVAAAgAACEEFJIIYUUUkghhRRSSCGGGGKIIaeccgoqqKSSiirKKLPMMssss8wyy6zDzjrrsMMQQwwxtNJKLDXVVmONteaec645SGultdZaK6WUUkoppSA0ZBUAAAIAQCBkkEEGGYUUUkghhphyyimnoIIKCA1ZBQAAAgAIAAAA8CTPER3RER3RER3RER3RER3P8RxREiVREiXRMi1TMz1VVFVXdm1Zl3Xbt4Vd2HXf133f141fF4ZlWZZlWZZlWZZlWZZlWZZlCUJDVgEAIAAAAEIIIYQUUkghhZRijDHHnINOQgmB0JBVAAAgAIAAAAAAR3EUx5EcyZEkS7IkTdIszfI0T/M00RNFUTRNUxVd0RV10xZlUzZd0zVl01Vl1XZl2bZlW7d9WbZ93/d93/d93/d93/d939d1IDRkFQAgAQCgIzmSIimSIjmO40iSBISGrAIAZAAABACgKI7iOI4jSZIkWZImeZZniZqpmZ7pqaIKhIasAgAAAQAEAAAAAACgaIqnmIqniIrniI4oiZZpiZqquaJsyq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7rukBoyCoAQAIAQEdyJEdyJEVSJEVyJAcIDVkFAMgAAAgAwDEcQ1Ikx7IsTfM0T/M00RM90TM9VXRFFwgNWQUAAAIACAAAAAAAwJAMS7EczdEkUVIt1VI11VItVVQ9VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV1TRN0zSB0JCVAAAZAAAjQQYZhBCKcpBCbj1YCDHmJAWhOQahxBiEpxAzDDkNInSQQSc9uJI5wwzz4FIoFURMg40lN44gDcKmXEnlOAhCQ1YEAFEAAIAxyDHEGHLOScmgRM4xCZ2UyDknpZPSSSktlhgzKSWmEmPjnKPSScmklBhLip2kEmOJrQAAgAAHAIAAC6HQkBUBQBQAAGIMUgophZRSzinmkFLKMeUcUko5p5xTzjkIHYTKMQadgxAppRxTzinHHITMQeWcg9BBKAAAIMABACDAQig0ZEUAECcA4HAkz5M0SxQlSxNFzxRl1xNN15U0zTQ1UVRVyxNV1VRV2xZNVbYlTRNNTfRUVRNFVRVV05ZNVbVtzzRl2VRV3RZV1bZl2xZ+V5Z13zNNWRZV1dZNVbV115Z9X9ZtXZg0zTQ1UVRVTRRV1VRV2zZV17Y1UXRVUVVlWVRVWXZlWfdVV9Z9SxRV1VNN2RVVVbZV2fVtVZZ94XRVXVdl2fdVWRZ+W9eF4fZ94RhV1dZN19V1VZZ9YdZlYbd13yhpmmlqoqiqmiiqqqmqtm2qrq1bouiqoqrKsmeqrqzKsq+rrmzrmiiqrqiqsiyqqiyrsqz7qizrtqiquq3KsrCbrqvrtu8LwyzrunCqrq6rsuz7qizruq3rxnHrujB8pinLpqvquqm6um7runHMtm0co6rqvirLwrDKsu/rui+0dSFRVXXdlF3jV2VZ921fd55b94WybTu/rfvKceu60vg5z28cubZtHLNuG7+t+8bzKz9hOI6lZ5q2baqqrZuqq+uybivDrOtCUVV9XZVl3zddWRdu3zeOW9eNoqrquirLvrDKsjHcxm8cuzAcXds2jlvXnbKtC31jyPcJz2vbxnH7OuP2daOvDAnHjwAAgAEHAIAAE8pAoSErAoA4AQAGIecUUxAqxSB0EFLqIKRUMQYhc05KxRyUUEpqIZTUKsYgVI5JyJyTEkpoKZTSUgehpVBKa6GU1lJrsabUYu0gpBZKaS2U0lpqqcbUWowRYxAy56RkzkkJpbQWSmktc05K56CkDkJKpaQUS0otVsxJyaCj0kFIqaQSU0mptVBKa6WkFktKMbYUW24x1hxKaS2kEltJKcYUU20txpojxiBkzknJnJMSSmktlNJa5ZiUDkJKmYOSSkqtlZJSzJyT0kFIqYOOSkkptpJKTKGU1kpKsYVSWmwx1pxSbDWU0lpJKcaSSmwtxlpbTLV1EFoLpbQWSmmttVZraq3GUEprJaUYS0qxtRZrbjHmGkppraQSW0mpxRZbji3GmlNrNabWam4x5hpbbT3WmnNKrdbUUo0txppjbb3VmnvvIKQWSmktlNJiai3G1mKtoZTWSiqxlZJabDHm2lqMOZTSYkmpxZJSjC3GmltsuaaWamwx5ppSi7Xm2nNsNfbUWqwtxppTS7XWWnOPufVWAADAgAMAQIAJZaDQkJUAQBQAAEGIUs5JaRByzDkqCULMOSepckxCKSlVzEEIJbXOOSkpxdY5CCWlFksqLcVWaykptRZrLQAAoMABACDABk2JxQEKDVkJAEQBACDGIMQYhAYZpRiD0BikFGMQIqUYc05KpRRjzknJGHMOQioZY85BKCmEUEoqKYUQSkklpQIAAAocAAACbNCUWByg0JAVAUAUAABgDGIMMYYgdFQyKhGETEonqYEQWgutddZSa6XFzFpqrbTYQAithdYySyXG1FpmrcSYWisAAOzAAQDswEIoNGQlAJAHAEAYoxRjzjlnEGLMOegcNAgx5hyEDirGnIMOQggVY85BCCGEzDkIIYQQQuYchBBCCKGDEEIIpZTSQQghhFJK6SCEEEIppXQQQgihlFIKAAAqcAAACLBRZHOCkaBCQ1YCAHkAAIAxSjkHoZRGKcYglJJSoxRjEEpJqXIMQikpxVY5B6GUlFrsIJTSWmw1dhBKaS3GWkNKrcVYa64hpdZirDXX1FqMteaaa0otxlprzbkAANwFBwCwAxtFNicYCSo0ZCUAkAcAgCCkFGOMMYYUYoox55xDCCnFmHPOKaYYc84555RijDnnnHOMMeecc845xphzzjnnHHPOOeecc44555xzzjnnnHPOOeecc84555xzzgkAACpwAAAIsFFkc4KRoEJDVgIAqQAAABFWYowxxhgbCDHGGGOMMUYSYowxxhhjbDHGGGOMMcaYYowxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGFtrrbXWWmuttdZaa6211lprrQBAvwoHAP8HG1ZHOCkaCyw0ZCUAEA4AABjDmHOOOQYdhIYp6KSEDkIIoUNKOSglhFBKKSlzTkpKpaSUWkqZc1JSKiWlllLqIKTUWkottdZaByWl1lJqrbXWOgiltNRaa6212EFIKaXWWostxlBKSq212GKMNYZSUmqtxdhirDGk0lJsLcYYY6yhlNZaazHGGGstKbXWYoy1xlprSam11mKLNdZaCwDgbnAAgEiwcYaVpLPC0eBCQ1YCACEBAARCjDnnnHMQQgghUoox56CDEEIIIURKMeYcdBBCCCGEjDHnoIMQQgghhJAx5hx0EEIIIYQQOucchBBCCKGEUkrnHHQQQgghlFBC6SCEEEIIoYRSSikdhBBCKKGEUkopJYQQQgmllFJKKaWEEEIIoYQSSimllBBCCKWUUkoppZQSQgghlFJKKaWUUkIIoZRQSimllFJKCCGEUkoppZRSSgkhhFBKKaWUUkopIYQSSimllFJKKaUAAIADBwCAACPoJKPKImw04cIDUGjISgCADAAAcdhq6ynWyCDFnISWS4SQchBiLhFSijlHsWVIGcUY1ZQxpRRTUmvonGKMUU+dY0oxw6yUVkookYLScqy1dswBAAAgCAAwECEzgUABFBjIAIADhAQpAKCwwNAxXAQE5BIyCgwKx4Rz0mkDABCEyAyRiFgMEhOqgaJiOgBYXGDIB4AMjY20iwvoMsAFXdx1IIQgBCGIxQEUkICDE2544g1PuMEJOkWlDgIAAAAA4AAAHgAAkg0gIiKaOY4Ojw+QEJERkhKTE5QAAAAAALABgA8AgCQFiIiIZo6jw+MDJERkhKTE5AQlAAAAAAAAAAAACAgIAAAAAAAEAAAACAhPZ2dTAAQYOwAAAAAAAOGpbkoCAAAAmc74DRgyNjM69TAzOTk74dnLubewsbagmZiNp4d0KbsExSY/I3XUTwJgkeZdn1HY4zoj33/q9DFtv3Ui1/jmx7lCUtPt18/sYf9MkgAsAGRBd3gMGP4sU+qCPYBy9VrA3YqJosW3W2/ef1iO/u3cg8ZG/57jU+pPmbGEJUgkfnaI39DbPqxddZphbMRmCc5rKlkUMkyx8iIoug5dJv1OYH9a59c+3Gevqc7Z2XFdDjL/qHztRfjWEWxJ/aiGezjohu9HsCZdQBKbiH0VtU/3m85lDG2T/+xkZcYnX+E+aqzv/xTgOoTFG+x7SNqQ4N+oAABSxuVXw77Jd5bmmTmuJakX7509HH0kGYKvARPpwfOSAPySPAc2EkneDwB2HwAAJlQDYK5586N79GJCjx4+p6aDUd27XSvRyXLJkIC5YZ1jLv5lpOhZTz0s+DmnF1diptrnM6UDgIW11Xh8cHTd0/SmbgOAdxcyWwMAAGIrZ3fNSfZbzKiYrK4+tPqtnMVLOeWOG2kVvUY+p2PJ/hkCl5aFRO4TLGYPZcIU3vYM1hohS4jHFlnyW/2T5J7kGsShXWT8N05V+3C/GPqJ1QdWisGPxEzHqXISBPIinWDUt7IeJv/f5OtzBxpTzZZQ+CYEhHXfqG4aABQli72GJhN4oJv+hXcApAJSErAW8G2raAX4NUcABnVt77CzZAB+LsHcVe+Q4h+QB1wh/ZrJTPxSBdI8mgTeAdTsQOoFUEng9BHcVPhxSRRYkKWZJXOFYP6V4AEripJoEjXgA2wJRZHSExmJDm8F0A6gEXsg5a4ZsALItrMB7+fh7UKLvYWSdtsDwFf1mzYzS1F82N1h2Oyt2e76B1QdS0SAsQigLPMOgJS9JRC7hFXA6kUsLFNKD5cA5cTRvgSqPc3Fl99xW3QTi/MHR8DEm6WnvaVQATwRqRKjywQ9BrrhugR2AKTsPQeQckrAOgDOhbTESyrXQ50CkNpXdtWjW7W2/3UjeX3U95gIdalfRAoAmqUEiwp53hCdcCwlg47fcbfzlmQMAgaBkh7c+fcDgF+ifwDXfzegLPcLYJsAAJQArTXjnh/uXGy3v1Hk3pV6/3t5ruW81f6prfbM2Q3WNVy98BwUtbCwhFhAWuPev6Oe/4ZaFQUcgKrVs4defzh1TADA1DEh5b3VlDaECw5b+bPfkKos3tIAue3vJZOih3ga3l6O3PSfIkrLv0PAS86PPdL7g8oc2KteNFKKzKRehOv2gJoFLBPXmaXvPBQILgJon0bbWBszrYZYYwE7jl2j+vTdU7Vpk21LiU0QajPkywAAHqbUC0/YsYOdb4e6BOp7E0cCi04Ao/TgD8ZVAMid6h/A8IeBNkp6/xsAACZELEYIk+yvI6Qz1NN6lIftB/6IMWjWJNOqPTMedAmyaj6Es0QBklJpiSWWHnQ2CoYbGWAmt+0gLQBFKCBnp2QUUQZ/1thtZDBJUpFWY82z34ocorB62oX7qB5y0oPAv/foxH25wVmgIHf2xFOr8leZcBq1Kx3ZvCq9Bga639AxuHuPNL/71YCF4EywJpqHFAX6XF0sjVbuANnvvdLcrufYwOM/iDa6iA468AYAAB6mNBMXcgTD8HSRqJ4vw8CjAlCEPACASlX/APwPOJKl9xQAAAPmnev2eWp33Xgyw3Dvfz6myGk3oyP8YTKsCOvzAgALQi0o1c6Nzs2O2Pg2h4ACIJAgAGP0aNn5x0BDgVfH7u2TtyfDcRIuYAyQhBF/lvSRAttgA6TPbWZA9gaUrZWAUEAA+Dx47Q3/r87HxUUqZmB0BmUuMlojFjHt1gDunnvuX8MImsjSq5WkzSzGS62OEIlOufWWezxWpv6FBgDgJVltfXFYtNAAnqU0xQoD0YLiXo5cF5QV4CnY1tBLAkZCOABAhbk/AM+/AwSCCdlWAAAMcFjS7owb8GVDzveDiZvznbt2tF4bL5odN1YKl88TAEABCZvufq9YCTBtMwVAQUEAwGtNltzSaHvADYC3TxLVjqiRA+OZAMhzcqEgRcAOwoCgvdTxsTHLQEF6+oOb2+PAI8ciPQcXg7pOY+LjxQSv2fjmFuj34gGwz310/bGK6z3xgT887eomWULEaDd04wHetYxdjcgV2SxvSwn0VoZXJRqkRC5ASQ/muVoAUsX7AgAQMBNaVwAAlABRxT/1PmfqLqSRNDbhXb07berpB3b94jpuWEZjBCD2OcdXFpCKEgCDfcFPMw8AAADUwT4lnUm50lmwrpMMhPQIKj6u0E8fr2vGBngMNdIlrZsigjahljud6AFVg+tzXwUnXL3TJLpajaWKA4VAAAAMiFfqJgKAZ08XrtS3dxtQNYcpPvYEG8ClvrQRJgBephwnNWJjtGqmp6VEPSvBe7EBiU3qgJbQAwD4Le8LAMDMhHbNAAAlgK+tFs5O+YyJc9yCnJa3rxLPulGnxwsXV9Fsk2k4PisCAHC8FkwbGE9gJQAAoMnyksj0CdFMZLLgoz8M+FxziwYBgIx+zHiCBAKAlBKNpF1sO9JpVcyEi9ar15YlHgrut5fPJnkdJ6vEwZPyAHQBIEDUrlMcBAAd2KAS0Qq+JwRsE4AJZtMnAD6GnOYwYlOIZvtzUNdjreB7fiMkWI0CmBB6AIAKc38A9osEFlTSGECB+cbeRDC0aRpLHqNPplcK/76Lxn2rpmqyXsYJWRi/FQAAAKBQk9MCAOibrQBQADCDsqpooPutd+05Ce9g6iEdiYXgVmQAI4+4wskEBEiBloNQ6Ki0/KTQ0QjWfjxzi+AeuXKoMjEVfQOZzr0y941qLgM2AExvbZOqcxZ6J6krlrj4y2j9AdgKDx6GnJsVLhbc42uq584+ouSdNBpoCiCVHrz+WzUA/DDtD8ATgA3h0lMCAAzcFv+S+fSSNkeYWlTpb34mf2RfmqqJeMeklhHAfu7VoAEACgAApKRktL+KkQDWMwYCUAAAAHCKsp80xhp91UjqQBw3x45cetqkjQEyu3G9B6N+R650Uq8OVig7wOm6Wun0ea4lKDPoabJs6aLqgbhPzpv4KR4iODilw88ZpY7q1IOMcbASAOAVtmcCnobcrkG4KGS7/ZnskVWRNF9J0RUHKOnByy9WA8Dv6L4AAARMCQUA4GritfVM2lcZfH3Q3T/vZ47J2YHhcmBazjfdyuV25gLAzrc0cwAAAAAYCh6PdwAAAGyWjFW4yScjaWa2mGcofHxWxewKALglWBpLUvwwk+UOh5eNGyUOs1/EF+pZr+ud5OzoGwYdAABg2p52LiSgAY/ZVlOmilEgHn6G3OcwYjzI7vOj1t6xsx4S3lBY96EUQBF6AIBAmPYH4PoGYCoJAADWe+OZJZi7/x76/yH7Lzf9M5XzRKnFPmveMsilQHwVAAAAAKB3LQD8PCIAAADga0QujBLywzeJ4a6Z/ERVBAUlAEDqvoM7BQBAuAguzFqILtmjH3Kd4wfKobnOhA3z85qWoRPm9hwoOHoDAAlCbwDAA56FHAuXflHo3fe2ttG9XUDeA9YmYCBQ0oPr/1QC8IvuCwAAApbUAQCK22MmE3O78VAbHQT9PIPNoT9zNc3l2Oe7TAVLANBufT8MAQAAAGzT4PS8AQAAoELGHb2uaCwwEv1EWhFriUkbAaAZ27/fVZnTZXbWz3BwWpjUaMZKRj7dZ0J//gUeTdpVEwAAZOFsNxKAjQSgA+ABPoY8Jj5y2wje81jsXc/1TOQWTDYZBmAkNDiqVwuA2NJ9AQAAEBKAt9Vrsfs/2N19MO91S9rd8EHTZHnzC5MYmfQEACy/FBcAAADA5c4gi4z8RANs/m6FNXVo9DV46JG1BBDukqlw/Va5G7QbuGVSI+2aZaoLXJrdVj2zlC9Z5QEAEFz/5QzgVZwAAAAA/oXcxyC6WfTu+09Ve/c766J4VTAGUFmA51+VANKi/QPoPwYgYAkA715OH4S0s5KDHvj99MMq8TPFc3roKZnGOoT1bmIhVgc7XAMBAAAAAMAW1VbQw3gapzOpJd+Kd2fc4iSO62fJv9+movui1wUNPAj059N3OVxzk4gV73PmE8FIA2F5mRq37Evc76vLXfF4rD5UJJAw46hW6LZCb5sNLdx+kzMCAAB+hfy95+965ZCLP7B3/VlTHCvDEKtQhTm4KiCgAEAbrfbWTPssAAAAXpee1tVrozYYn41wD1aeYtkKfswN5/SXPO0JDnhO/4laUortv/s412fybe/nONdncoCHnBVliu0CQGBWlPY/5Kwom2L/kruPM6Q7oz4tvDQy+bZ3HzOi+gNHA4DZEgA='; +export const BellSound = 'data:audio/flac;base64,ZkxhQwAAACIQABAAAAt+ACPpCsRDcAAAFSY0hTdxoSGnIFMiBuLRx4+DhAAAeyAAAAByZWZlcmVuY2UgbGliRkxBQyAxLjMuMCAyMDEzMDUyNgQAAAAhAAAAU29mdHdhcmU9TE1NUyAobGlic25kZmlsZS0xLjAuMjUpDgAAAEFSVElTVD1jb3B5YzR0CQAAAERBVEU9MjAxNAsAAABUSVRMRT1EaW5nIf/4yawA2Vb2SQD7SAAASAACrQADPQABGQAJ9wD2aAD0Ov8Ncf8H2AAIBADmIdfSpfYUQnidPDwnzzwu8Q4T0hkj0fq1InaCJGndpb+0VZhQIXWNoqrFNKL1wYQXuk7xqJOipoTgITh+3SKIu1wVNOqsr+l9zFKBdwKUikHGste88i/UjulKRtzgE3Vz7+fmI8U0UzqFuxH9VhOkY8KjSkR2/N3Exg+9PKnvowcv5aL3xCCLPxjmXv6MFP+paLX9KVW+seJ249ZcWRh3sUESSic1z1pBkF3BsHwRKJkuUQJ7yhpWCjd47owqrwfHVpphY20P1BPs6qNg6lG9f785hOd81FiBIA2xRQ1smE6tprk3OoX7AheTmfIXMY+mHb71FEEl6WsvthDBeCBWDlNWUkkQV/frRSulMUwZoyEoDzr98XMILfbWQz1xmorBSsyUm2IJ/l0WVRHFyPiaOvsWskVf0s/FVVWG+gdAfXPLAk1wSVRCbRHXW2F+Ov4VdlX5s8me3+VesT/EjGkovyURrsCnbpwORajTNECSZOdJ9IvjhmIx1i38P3PgJ+sHGeXjM6YHwKtykAyYdlSfcWTgIJmaLHwGiM79NJam0EIYlf2Rjgi75EpuQJmV9CFj7hMLBo2lR+jQmzEol4uYN1f2WtfzoKgUyH917pX0bQqSX/kD50s34tvCRu4sUc29OL5tLmAS+W+vmHlJ5SkUNhy1XE9WBpK1km1bStlI21Up10mmo5DjC0eUssWUhAU0klzYJGJYsKKb3ySWqICnnvNM0FzTmAIrclPYhvMBSgJbE0Tk8zO8eThqOaCPd5KQfvT3bW4aC4xiaFRNt0fMs5ksLr7QJCaLH0L62apiAiCjNo2HlZIy+2qGinJ00hho1o+1jKjpsDPGBqw/dNASOooPVanrb4hhVyakiOlnmXZYL1H6rAL3ducfj5ks5pFMTaXOFfdg/S2vm43j/HKZe8bJvIl+kRVN/FoEghENYJQ3FIFURuXV0rUQQFrE5DIm7DNHYCcboENRYK42lC9ogVtNxVND2LeNVTAT799pZyG7JAWZQrlHOY9FiQIhxRAt+iFOc8qEVwAjF/guiNdkeSO2KBR2W2BgjWckmMl44y6MCnrGzCg2thqZrTIV7QqqcNAPqQ1lU7aS4jAha6QGFydsRArGDwrv5FKTaBEslstfnlF10OdVCrMSxYYHUia7emYHP1udbF1Ztn87oordptubHiYhYZEE42O8CNYYGp8+8fh4PLaJMa3hJT5YScZ6LtfFMcm5jHpLhICx5xDYw9pO15pepHr4EC4cmphcY86Y9VozR4+U4MDYSz4j9pFBpCxpUDomyoG2IdVYrlZF647P47+Y0z4hUYRSoq5o+zp0NpMtJK1IuzG+WC3Oc+SxAYYSgwtuMEv/dYMJWBimjCRTFNOMalTIzPaS0I6Az1ORx8gKBSfyuhIC66Hm0coinUaARsEJ8D+w3NrCL+oU1RHdCtiMjIx1nrQfLG5lPGrfs5SR2lhGdif5ICH8Hlysoi7gR/CrWQ7S90XGC38jZ2qoRIz5Gd+3lFFfewZVjkwivVehOUeg1GWA0rBmBOYgxTH6lWTpAtx9pduwHox4NtMmb92r9yGEPIyyHpoQ3zYaG8oORoKhwFg4NYI89tRJAnsXElJzXhE7iX469XJU+cdhWFFNPLJ9NBerLlFU5qtWzhvaDm2/vOyYLeA14+o+12Htf2iX3xn8EG0kJLAXnSTEJ1CUYb9ubK4SRLlvIM1S2WHfDzICczcFcfG9iUIPmyEa4ZuDuwFy8VlRRCKqrPuUnPbyFAGVM5bA1t1MdTQPIbDsuZJueufiTEwauCyd7QIcdajcbv2ilICEqNRmekrDlT0Frc2IDoeKQ8mA4wB+xBekGkZqQMX2U8JWG01lnczODN7I2z+vxUS+pFzYtTkLctuHcrHvWUyWgg5WSFlChnGD2E3mOaM52SKhS/OHbcLFSoy0OXbOj6OXDKSKf4Cj4ULqpSTJzOJkR+4q0QgkwpixmIiO1NjWDbcdh9vNNHYzjlzV5UUQfH74w7L7MF7n/Z3Qu4FNS6zWzmWDax2Vl3hx2VE0hQdUUqIBUuZkLjvbCKHBNDg+Vw4tRF9qrea8RijQGT8TC843EqR5ib+E8bXiY+GN58cWN1eWzRS6tGQaW0ZJB2BcHFmgNRpo1LjhpCS8WGaXpJtji1XMY1T48cMYPpNSME23KAIUSeAoePCCKuftiwQdQzFgvKkHaIn8KI0KVMKDMfW50ZkXD0k1d6dudgD56YOlhdWsSWKWMRryygNnYiyEG4s3LpxULR3MEikEtNJa3CF4HhcFT8LMAyItwbIFXIphSmwWb657ctJWU1GYcHyxvIMnmvB1A6oGVqRYDrgEdkl1H9IlbooZUvrIE4VRJwhGOmYC6G9fdB1c8XKVISURS2b5mfP1oolbqgg23jup+BNUr73r7MqkWW6kEZEphvFn0WzwtsBA0N+Am5rURaSk5qFGSKRMI3k9Sy83WHptwuAUppzMNVlLBRIQPUfqy+FXlUWtmUlXOIzl19n2W9IylL/56CJIGJBgzDjysEPgTHD6OhE+Y/UTcqacvonf1Mns0MKcLZuAmSS2JTGVUN/L/PEOtg+Gj12NDZ3mRRfIK1aTElJgV4Nndy+p0iao4eTmlRRJrbTKF0t82tyHwH+ibQ/TZWB5NGlMqJtM+VRoajmPO8Vhh0T7YgN0YcywilDWyb7zg+QsG+RPm0kHu3jqoxeK/VrrhbL8cWqIoOyBbc+tyqmuM0z1634rpiLR7yyrrOVEQO4uCSQv5A5vrtms+PTK44v4sSzRGwGq6zB8h98farogtiYbKJ8IKsNbML3UQJZRboJOql85mCEWvj9gJUpeVVa2YHPn7T8lNv3VBVejNgsx9autUdZPo+9vQrtH/Ilz9bRWnSDD0xJCEL50MlUJFsbzIT2pLGvdVT0zzXX0tDmTFs9bkCX+dThI4HKEINoo2la9IhV5OZK15jk5S9DXKeuZU9spyi5KEVI9gtUhPfXJuTfJX4ufiXDE6BsoFsedwUjbCi54tZn03KPwvJBbhGYE/5mk8hStDctqSLxG/+CQzCYBNVEz09CuxRxz8jGIu0+YK3jS26uUj/WXrTb8XljMGlgPyVEvmRGRe9KThgWIjgcIOp8BMxRStcMX1WVoJuxjApF8CTOJnuvr11vnXyd6JnmswFQRpnmSQxKmjEUXQmxjCWJKStqVq5ETlXOchZacrptnS26OG3FHosbcak1PHG993h+FfMs8OvjieImSUY+oCVCq3D2ejVa3CfSMSIg8hApRBnvNM3R8yRd9SGoZWteRE7GVGDmT4ZD1kZmfcK+9H6CaIp54kuvHVJNJVaLLZaaJ7CVBze7u1qJk92WJviXRgrJgwHCRQESc5zlJ8mYlqxw4K7ihckbOJKZ4rH962rdELOdeYxW3GrE4y7ZhRCmMPT4ZNgvGBYyHOIrdJETqvvOPxnDO64EBJrkYZal9hZdnG85inMIxWxUoBfrElETPyBySA7DRJmcz83fjdt5yS4QfPgk1hGfAl1BXWCawEK4SOC32XMh74CHeUTiaYsC1Cm6QRO9leCXmJFOvxNKBrt4guilEfvXsRd2MxkiV+uNe76/q2RjXm1VvUd72eH2XIzlm4j83Z1wuZCmEETMf1j/ckkrCNkqrZ0RoIiQxF5uOWpe41i0iHH8e9RfLfbh06G9A1aD+EKDQ8hCPKc7hxtLVDGu3dSynT1///D2JJwtkhKQK9By0LUSwWhUDKVNb04PRNjmMv3J4o0pFVdVxHymkwu87yONoYR8RxJCDaCCsDZKMJpy0YhLIhNQcYs5aZ3zf2rZaQLilWTBCdY3RoLiDddq6wvlG6CLc+5+othEKsX1bymDOwAYCFKIxEzo2NtQVuh8JF2F+veToj+xg7krCLtU8PSTXN3Nb+puVYFwC+nSvQUvnGpoRRpA9BG5HdUOmwvYvPiDpotEzCrCXfSmmZUn54YuQiThgXdlUctIHslVH1r+VrsvXtjJl6aTUbQLLacaQ4qhQQOJ+skrqkVW6gIDYjqC5UJTBdvTvkb+/vzY9Nwk3VTAD6M6I6ftXdfa6t0TIV9o8p2FglJN0hIYJtxvFIgTIncmpOfid1M9Pup/7GNEX7RzPDV8P2IT7RZJfhGgZjvob3U13prFeT9UIbx7Hf+flYpEzLVS0qhrnXSlSNcIQIgW/wi6E0pZ3YU6jNukpD5C5MeZ6NE3IdgBRgF94ddmqN1RKXYnUn+IKboTejG2ojlS22aze3P8dog/S+1Jy+x7XBDkOGpUP7hrML5b6wi4cC7pgiaLxqyY3B5TWmxWDLwnP80UeJiltd+NhdIzPALHxTRcg0M/ovqt8ufyObIfFzt1ib28MSQ66CI4Ma5taMw4wuBRiHy4KsAZyRbwHKoadGBloCkYKOfcILsaAbQdUl+tMv7ohptsEH5BJ3hXugvxWfXoH0dETqQNH+NQP0cyFdTj6Rj4qNgRRGNMQXiB8nCyYNrAjmvHsqGxGVitZPuleks6e856e2ItGjpSZXvJ3S4K93vKQx0DWwCMoOJI8OpmDylXiLgRO4dkziC/6pbG3/yRMByUMJKIwakUWQOakLQxIYP+J8KWiDX73yeELCBV2vp/izojQjhhCwA/8gYIAJJwKWASsTk+/AIu1ADpXNtCt/GsIWcSNiaViDujv4s+7VrSPYpoaKBRafPK5SJfTF7B7cvgcwWzHfXTNIEUcjsKF/QJPgnsmPU6boBvgnaFpaUlRW9Pc0VVX+b7d3Jf8TppFcpsnfH36nsIZnArBAt8Ah5BqfH5hqccK6TfwsVd70PJ/poVLyBgILZwrENq6dwz4MLu8XmFl0oiAsKaZNfvZjumoOLkslf033th9LhKDDjKDhKBwEDS6H9AapHmR1HLIpHqEalKELKDeUbn/UtkVFN7xHfhzV2PmoWqA4eB3XBjaEaU9MLLCXpoacFVCi2iaX6J/Y0VghsginBNzGVQhXjNOemD/6csi0Ed0kMHlq7VJZ0l3GG1uHBWJcMwl1Kz9UFWUJS4gyjjMI6gsImO1G+ISiFyz+FdEumimSMCLmW0Cp9pgeRCjYAE64ZbAnCCFgE7gIIKpyyCXUaL+cX3I6dy6tRH8UpoUXjxenOBwwZG+wUMR0gZoTliNdB4vHK0iCp4FIBlJmK5sbv74nafJKywWOksryC7WbViHircuxU2tXY7/jrT9BJxMeovhCRlNvgiHPEJfoRXghljd8HrQ0I0ga4xx4I4B7Pkemf8IQXIaC4ck4z7RthtEVfxz7Peoki2BLIdoFecbnOE7ZOWIjiYU0I4IqZJIWFvwuG2/1sfSHeGmgEooLxoYARNdI0zx4kISSXXUb2PPFdUv26MKjK4cFNEDJnVtzCyOWIKfwLlQImQPXweZwX+jts5yJ9spKxB2lBL8b7w/TBbba5190muPT3Qwmif4FuIpA3Udh+5BJFf8nZbpneiTSq5T17XlcR5xqAAibgXAAKVoP5APMghFo4GRbTyPP/yj86PsPxDtQtU0oiHvNQQfh7qu2JjVYJBQbtBmWEkdjvMmtvzoZEr3XN62nB7h/7AxLjn+CBQHgEQT3lcyjrIf3i0VNL6qCqyWICs1WNWTbpdcmQ2RhflWiThYgAirAYpAEmgH+QOBwedhrVKaRrzI0Ch3zIlwk2NigSZYVxRAb1tPR3UxKD5pCJOBY5WFFPhYPkAGf34+zQTDIYiotlUKvKjFwEUuxawDBmEmARoQjUDJkSNiaaS/SucYninWrRPtElPRXAm8KXH/xnjNcDz9IEUwMTwTkAqbCKoi3OlFtgsZWNsm9lBcpbHhgmtl0Y4FjMAQlTWaZlTzOLHY8ohMqK8qCMcM4RrId4oeWYU14Vno+K09Sng8qoh/DF0Lexr7XmDfHLSqY5Da+LEB8zK5DVyof+1pK0pVFCSq0vEpSgxHaDD+P0QtWLET6/ICsWH4ymQ6dwlgx9khiIl7/nyp1/h76IU+qxDadvQCWIxqSFboiDngZANXg5K3jBfCv3BcRgmRQjTWszqWzvXF5gIQX88Zv2ymCML040zrsM1KiFMr9k1lZ18TKsNr5emgkeBOtBKIBb1B3rBwHDxsIiRlvJRsebooiRmCjWHMFNUQkIe/Q2hq3O3AzIQKK/jtQQUmU0bhQvG+81OkpkiWyafKEF/JkUDrx55yBQWMgz+hUeCTOKFZUjVGLnTQZ6H9y/dnfF5JqF04tFlpQLmg6i4Xg3dS44BHFBdkAlRgxAB8iHmtoyd5KIrTRpd8mUOQ67UpUttBTCPbe6T/bT5xIqMPB7oxLmjKeSgGOaiZ979ugmkKbe5xqt4mfuO/g2bgJNAO4YJCIbWQpijKGJEam889ohORj5h5WyarVA5fbi8RCXFJS01R0PSqileovDGXQTjSCQyENcUVq5kBinXG26vy5L3Byg4MKNQobSNFLUpwgll74v4EMiJa3ebRLsv+Ua34eiILyMKXkr620qgJ1p4vsh8aCuoGgoJfp+1fTUFpO2PKz8WY3COKq7Ik9L+rIlTm+ObdXJN+9MRJCZ0OiBqgSYVy7LO1kU/okgxECjBvM2veDS+aa2V9RdrGV4u7v4b8uKq4hFj9qHZ4cAhWnK2z6Xll/N8XNYiUxEkt8luuqXQxzjRkNKhVvcB3ttptyBfkjZd0waGxpftSpF6WHKUZQB73/ZV8camCmjkTODHw3rvFCMqNrE0WQ4NQVOoNwsLqcV3Ny2285RbtVyAMVmT674E48vZmGU2XBi2CqsO+4mmPCzGrdGqsJRnODEmiG6IXKAnAhEnCeobnRAfTaZlQss1iip7+31gnJamxXzf14PhHxzkKhbUKZgsUhs+BMzByjBgBER5/SND9Jmk7a5tXtTyCtRQOIxWWMcYgTF5FYS4FED1FOP7AhRDXkFtEyzPbZIIZRUH3l2x3EIqqSDOeC2sBhTAw8AxnhKROM5BRRfGZj1CoiFXdtWvsFo+9QlSNsdkhqXyKtk+toqXBPXDEmHAQK14/fjGwUBULVEJJ4XUzt5DL6P1AdFgQVAmwC5oLcR1LE+MVtiGqanzAMKrZwWMmavggJi0pKcMgZYlOYoOVA5uh+OBNtBm9DUiG/4ST2bcoxd9XuL464q+UcmRWTG93SgDLvV6UuL1+woOWHyznE1EURWypoXvyM9tRkSll6V3KQrnsO+RgwGGoLggSIwg1l+lyVIoRuTkjkiJudpXl9sytoFgOjNjE4SJRUfoJ4xoTP1JZwQxvHUE0TE4hGYu72/NW0tuOza5ATc9wLkhF4RX1ezwi40TXVAroYh1j0Ef7xTLerEccmB1H6KLM89RmpiCzSK6HEZpQhBBKCFZg+BHgIVHxIMGRQliCtMKVpipNXhWXRODU3EQOMEURGJD0o4u+KqPvxL2kpLKDGiRKjEW249RR4TiRBLsH1tn7s4PH18o6y+QF5gODZekFPwbeB/PBNOCTsJTSiSWX4rUTSvEKSXnKFzj5ryndufWTkaX1i+QoKoEK3ZwcMbg8jmeIvFqe6As261mO1GV/++1zN+57j6MGeIJLw9WHF1assMUT6Jd8iy5Iosb1rtPJOvZzZourAlwcuvZryk7MQ0bhzzDI2Hv0EDY75FC9euUajhpdvrxvSc86Wq2ivEKxp7aevFCOw+5vxJ0tYdCJ40JDN7SqPD27SXNKt8Ytoox3bnCMepRfcBSIHdkJ8ASqRKrIQyrWIkFeElvmwE0hn4QYD0o/JQ6z/4DLGTkE9sTvjrMe9gxxB6sHwwUSiV8QraDXV4O7fyiGcYSTgpRhIsBrZHnAIyok+uOzdPS8N/3nkz6XQiubyaVEwaGeyLfEYpYyBX9uua8nFmTCXKMSAuHmv6CK+Yur+XREbPD22+BOSdlE8f6qIUTlQ/8Z5drYZrzUeNx1KABWOUg7VbMZ88tRmMj2XCtzFAa/RUNiowCmuECoRIyXog3Kvqk5gy+/GoUQ3qeHTGvGK8Wz2fHiuPSkPuE5kXoHVK+uz/o3MtR7iQiZ0pm+73vSb+ZUyo3lHEJ9wyziAqRazVYQGi4SacBUOEHgWLDTcKeB74X5HV+Wj4RmoMYkK2MLCUm5UDOjvY2j9avUKri+Wptl5KvoXsH92xEm3xTA57SOJLNtoLkCO8q8BvUltR3ioTGD0OFNEnrKxR3gn3OLvs1XzQjVRalhu3myeqBSaIxbHK1prbJkpOtCIGFV4EUkFkYOiRH4JCpCU+yZVKmBKdL7PVZpMaKrOHfA+2P9zP1ZA/0aYMaxr5AUCqwP7eu+LkTs2vEv/YUn76A6uxArDNGDz8CmgEgUJYxk3ZTiVXZZZV4jlDEUa6cesWt9yPMZFFj/lKzTX+dw9F6WU1LwuIOKznupHaCcSg4ihwn6xl0kG/YV2QmSsx+kRNj0Qu8C01pT0HFRiMcE/WSXVX5r6dRo4jWReomlST/f5fSf2xhV5CEqHNAF64GfcNUJY3QlO66uI2bvAsRQtnF6qVvGMYvyIaCZWRQ6ljT1+Kq2lpSJVqdUxqZcXX596impHSCPYFXTvIPrzYNB6ECzcPVxtwbFVP2z3eO1zO3k7bHUJ5N+qY9/qlv2VRIhpxean6Cul7Zpq8EagWhTFiyFoHFUztLcjSSYzETK6TZqvsmUkTcGVSUPZamoxfPlxeYF9C/4cKixbvrv/PDEdWIKLcCeRAgcw5YTyDUqMUSylFu43LOMJp+I/CKl+99Qvpxmm6pRv5eI1yNeOr5FnP5JONbHjojJPzJXojoiEmS4kWe0Cx/gjzbHr6NlM3RZ8GlJzGedJKAeUSPaz7IzTNpuaoPWyEf0iBKTR7qZPW0LiEO+Md9LR5li8jK0dV01LC1ObfDAMrTtRV0ssLdpQ0M8IVrZV9bbYTJXOgzXFatvUb4zDKcLwWIgUVQdAQuNhW6U0Fiu1cWI2K5EVR5tjlY/mR2XyPQd+zTknRIXWzGTUSR+K8IpXn7kSeT7EWnCZyhD/yEnB0sjS2+NBkjDMICgGEEEcKREVJya5ImE3ruslUfChf4pN6sbjFVpOYVusbOjWv9gSFAtFhduDpEHZcMoAUTmDB8oloGLgMfF568rTlRA7o+nraj+sduik+1pW8/W7SXBfRnZP8mUvVxlsbEnbSbHOItQK1/FDx5ODv2HKAEfQCnAGCAQvTdhjlDb4utv574lPB7r27jeuuoQaw1Lq/0KbVtQdmw8pjUuG+EYwRLAfU/IemZX5uZbbZbaC6WbReH8Q9yC+q09ET/YbqKdz95mNwz2qrmvXbJ5UvZUY6LFkLFc1XzJpby/uAcugS0gQJwqMBq5Eg4pUGGVbGovmCtYxu2D+/Wh01lTUtskldyMLbVP2XJr7r1ADMuef08j0sk8uemociYWAI9XSu9l1Ubx6PE4cTYT2iblT4WRx3x9Ug7QkZ44BOyTwW991wmIakW8JhxumvsNlbeCgzQjWKGT4Lso6pEDxPSscvDL62YQHY0RQnzViONzYMmKmAudNo9AHyueqxr0NQJNBV4ZAGjSND7bC1ZB5ih98hW6ilhLa1Do6ID64SySJ4iV7heSKiCuOx34KUxB4VOFadA3btbIFJ1t+vtufRH9vjt+P3wrGm8dYpU+aj7fqJagmJGaiJF0p2/TK+xKkxMoQyF5jCbJwBvrH/gYAB88F2g8zlbVNeOA8trRZBgsdzJrYift76sNw74+Vpiy2l8GoVlPTilKOjKunARbjTwf0jywoiFnDZGUI6OmfJgh6D6SHH4ET8IMIYPCumRP5p+iNdCRbFviVb5QAur9hmkk67f2Q3o/kICiZeFGeiieiuMaYixQmJcDFXfipcxl92c55ci7nKNHmMXIc9+DRGYPnQ945oqlmuoFlF57zWsboR8xvLqkg1j6kw1Z6hR8Zl17H7EGEYHTUEQcYqhQJUxqZvTxbWtFySX2pfLtUtmr18j5HXF5KaiFjE2qoV9A2GKhmboaXuk2+Olg+q/UXRYuxzzzVEszM+uDowHFIJDwlWCcgVfHP5iIfxvhmlc9sHmrlvVTu1dR4jjhLEn2IhFaoxE1XcTkxCgN043jiMCt+vt8ihRECvNt0A6HPYl4GWlv1ZFtS5Ue9I2X0s9PjbZ4SBSPD7y+tkVaq36R4PkCmQnslm20CaeghGgzYg+Fgf3QJfoVpQw3CYyQAhzpCNENlgjgCGUUJ0HvZyaS6VTw91xb2yovAscj7qNVgs3PF7jBbgJJOkfOTXZEksxWeFvgnRIXKRKlOmJnIQQiMqLZwjWDooExQSWjCUleIzD3JKvMRzIuhxEIpAueWZ9Ar6hKPG9YTJDlKXujbkqXuWr/0zuFuE5HCugdjWR9o66kelPSjbJRkQHCAXNRabF7YwDLEC/5EDWNeUY8I4eylHtq8nTLSR6SnOd4iSONnoonC+GP44RowxbA/5AQAwnoDFA9VNMtcS3rOIzxtK7J/IE1MMuQdGxS0FY4pDkmy1h6rS/Lhm95lKRaiPL3HFR1RbteHNg6oR1EaCR8FH4ExAP8g91NLj4JIkcxWzyLNeMQHpDNqOXTdxTvoT72olsLW09kPFOcQnB9zG4EIZp14fziz0SLHt7o1x1UzeukhQCBjzB61BmUC52cpiWGyQO3dA7snle6mFqmr/j/eBRipKHnNSSAZL1/MqcKJ6gxKhHkCd+MC4seFelb1pll6nnjC496t35c+2CaguhEjhjQRjcSbpvXL9LjN04u3ah7PZc+X6Xm4l7Qir1SStt765JeGFgb9w1ohuNHB4SlnB8riyRtXp7Y1R2tumuFWfiaFJHJiAIbw00LQomzd7pZjNre6aUz+ZaaV0TT/Jzubfct5ZclJSkicc81JlUvllw2ZZ0pJIJNJJDVTSUk0pTErythS8L/QvbTtMQsuahUpWfUob+oFaXGFMnql8X4WR1lIhaxMwcJSzTh5klDUNQ1PpLUJ2GtTSUiQihnyaFZKmWoWUwpT1CX6BBDxJGKFLI3J0ssIaEylmGR8M8UuET+VMwpclcsPEJU8K5KlykxkpyhGWVNAqcvhpzydxnN7czywiTEpc2zjJWnzSCoaSdzOz5cdrCpkpGW2XOM/lCyQqUbLJ4UuduMzZKeaNJGQuebyVS3nEzDKQokIJ9NpQ+RZzlPpfJBOeEQ3iXL04RJBJtlLSF5H9PEknpPIIRLIdCJZ3LLNlJ1KSzlL9CWnThJFkSVJwvLJj4zPN+cmp+hWG5yk0L9CJ+yRClspbOWCcKk2STaRCPSl1h7ZzXJMushpGZmRCOd593LczQypbTrjhSIYzKLCkSIWH8ozlJz6eR+fUl55IgWS0kZynLE1VJZkTwjDhUKfJEyyb85RJuWl4QYR7uWAQfc5l8rykWmU6UC5cp3L6U4ZlIIRmJM5oY5TSYhtOZyzwkQKZMWZxCIRDTIISk8CYhWUMiRD8nZZIzEMQ7X5nTI5GZENSJmF5OXUudyNxEkjS4R5NKlLCNmWzm5T5bG/cO3Q2ehE++vcnRLNJxJSdDN8nC3IkInSJSh4h50uSHQ92zWRJlmF6bbKsZHKW03nEpymkT0mJ6GbjvNIrKzTBC7eiErORQLvJd2dkg4cqFh0pb5ilQ7EjTbz3WWUZZSoR/LZecwgmnkzukCD10srSoUKTz6T+S5THynsIk+lyy6bNP5kIMlJ6lyaa2Zh3DYgXWUD0ImFyxJkpSnLCjKFcZSRSRM9loetkSz30SaU/dChUnE1m0mPNliWU3WSIREvppYgYns2zy2lqQuUcm9M21KX4mlnSymF3yRIhGFS6UneaX+yX27OS2l5KeSuVOlJE007MRLlORLmOUpWbKJ9Mrzz5EC6RJz4kpgmyXQprutxNIJfxCJaUC+2bTksjN8tflmmkqSQQ/2oZ8pSRrlLKIRM3vvlzTlrbqG5ZTnXidwy0pLSZEAj8rC08iESspHNyXafKEs9SRO8+WEe5eUmbKRLOUU0sZLE3/5U7OoZLDvLpZuR4XDsnpbTKRCpcKbP4h2h06cqpKE6EUNJWZenfDlCzpK6SIB20kTppHpTC2VhYbk9rbp52UNQxOmlY6dnMyJidhoZUoUvyCEQuUyOiZpTly7sPZjIgWUTNpZCJvC02alQpe6UqcS9J29IlKU2SV87E18S+mmT8TTIncpNhHLYU0p73YUiGUTmdpaySyzQv4RMy6FmJpSWTlyRCYVCMjInGRTP0zz1MohOeJYbm3SRCIZF8xMqFswuVZ4md5wbUr/+HmsAQUlF1T9iwD+CwAAhQD97v/+xgD+Hv/+DgD9/QH62wD+PQAADP/mLCKf7lCQJ/AGHI1rfbDD+vop2dmUDK/aieorkpmWch0kZJMQM9gIbRkkDNsE9IysbkAX8v/RnqCQsWxoljHM1P/pzSmSeRXZ63vKbWoRSAvCBmcB2WFwwfRi8sTkm3JImZ+q1y9Jk2LxmFRpekIRNsUlVUoHFmoYdAhhC4AJoxVWVULdNgQ+Nv6qbSSM1bw64FNQO4QcBhRuD/ECDcLZQcNgccCGgCNkMnQtyOPRkESyfKZa/Fev//79oINDR2EdxggfCPQ8HEwKM1C3VIbJcKnJAy2axjl1JdKBXfQUnmD4IFQ3cBLMBQSBqmDxoG9gz/T/EDNLHFRNpNfR4Gu0tY4K/RRgEAoKtDc4SvhbCanjC0GFA7/CKUUVELCPLXD8FtO/DFCNihOfBAmCNYfMhAUy8KIRo7RozMaf6S/6/ZmbsgczNu/s7eBNa03TmwoZKi9QBOoBrkDlQJKQLFknK8J5yLNJR5I19L/j22BFn5EMLJTT1wgl/l36chqLOTnW9IS09bOF6PjEZ/zXTazlhbYhIGBXoEBwHjQISQaXCgwT3qdKGq5ZkJdpz1L7D1TuaEwZtlPBiwvaUNniyANXgcvDTQD88ePHTqrZcwEa+EkzToGl5Ag3EWAsSDk0XjGHJ7Czb0aUG2wytQ6mBSJVlz6SC4OceQuFiaSP0c9z/yuvCasHj4IrRX2Da1djw4QE7OLa7lKbPZZtyKFPLZz2QT0MOhxDla8GZYfnHPYyVUEHdIQQaiJvfbIQis6UF7MqJH9gLMgvpBdKPFBGQGAre4UasJTU1Yxv2yk1y0jniL2Pg4StxFDdhdKZG2ZpDEUPaBpGajG45rGIP8He9aru5oEug0CNKSkcUmGnZbEd+hhgQ4ArQO0gkmEXwpwQ0FLMUMwjyhlZDteX+1udwLVKEkpEODGB1+EwzHccjnT0ZwvlZUUwvEiBqjQi9jY5xoZLIz8RX8yQEsRFhoeZis8r8ZV0Z4HXunOQRxuNRmYj+0XP+cTKgxaa1MCyFE+PMQiPBl4OdwX9MaSIV6LEocahxSTA0lhDzHM8WMzZCyzPRKRsw0qpL3qj1jLem7kiElwsqJKrKprAWaqTUGKyYmasKePPHIVyjy05bOQSfpetJTZ2jHHEGskF60JYIVDT6C74vf50jS8y8TzL4MzaWdbAc0vdYi8REll6mnkomgypjFCE0UrOt4tU2Y5YgsFWEqbCYCRHyyadPxr1J2a6eIppLbTPQQbTCS4Jipjhek7vjT00DNUEbgEDoJUg3QQOY6f9MjXjWP1SErdht19y1yGnie/Y5+XatB7x/eA1L5I2Q/l8eKOO1pbTMbYIeZGFf2UkmGQetDlsIRwVGkLhKss1a150aDTzPMpaKO5wmuV5nKL4QC2Z0z8rJ6j0RFh4kDE8F8gq0AgwRYH+aDXXLed98DnK9QN8C+saMHVDmBYSJ7WJklDMr0Z9DAQmJbbzf9vCIKPW4e4RuRNE69uhJDWV7cDgkEmwYtBWwE2Z7S1rICGbgh3WTTjwjBopTh2917tYOraa4VPoSo8OVx2mFqQkUeOOPJaBK2ZNVcBdVNNiUgTUHaQsnCxMthHlYlMNlDaoKfxjiX/HHziQP5ubkGL0jmijyBSZVihIdJcQpNZOA5BDFASelBLoAK/12VM8ExirUMckjkuPFaT7ApgRgTfYbUHx4HWY60A4hDM1PfGUB49W7TIuGGFHZDET4nVR7qIYWh3hHBDE9lONsknQrIC3wrcFSzsU3EMpAI07BYCmym/j0+U7WvBe5E9yiu4XCnIt5rbxAfQnaIYVaUHWysScaNjYzosU720FHESix3Wm7GgIMuzzICppyIjgm6QLvZQ5CZKOB6LCBJboJF2Hnvqze2/5Sceq07bD82LuyPXb8ebqcORpzzLGMGMx1xjCcy4kH9Sc0ChgbdBAqC6kXWRJSZFeicZkIr76MVl2v/NWTqVdwWHdodfiymqXOQAqKDqs5ABvMVdzOFtyG72HCSm1lfpSeDREo0Lqo0tbYOb2jrHyGgm50SM8V+5DjXodh+4A831wVp8RTVjT/6MbAjzF9IFioTXgqyFojrsF2BzrT7LcMVRUO8ZwuLZS2P94GnVL0zAJAZoCk5ihsXdpRZ66+7rhR15K5OzAzU6jL5ts4Jwmmk7fPQjMYI+j+GLl0lvhs2IJa7IpEUPgdd8IAtqoXxTDWxjM6oqmS6S5cCs5rHF5KO3xG/ZKkcya/Jy1IHArrmurmLjrlhl4Uu8P3sUBdaodmJJOZ8T+Oh+F5khZ9MDKvWNmPLfgrxmZruqopPQU3GBYeW7w8jaW+BRME+giUoU58ccFE1g1uBxtupLOSTKYfs3DZ4XfEh9e4SH9FXWcXUSZczjw7oZuJkz7Ff1DCw5Y9TRtetVzi/pmOJ+S+6wySfRvbyohNhyPv0Ixzq3jBT5OlD99xeedg1X6hjHeMkzrC8zIMVHV58MkNpKzsSu+cj/iWuK5U3c+2RTRfoX6du7bcJmu6tU3FuVvSGBOnap/zub+x1RT1KZd2V/2Y1RogQwMwDNRsLJOTwWSKVc4ZHUs4wg7mzX82br0UZK148DGRUg3JKfbrbwQ23balXBjhjEusl7ND7HO5eXfyyxhS8ODmFNPMrSCz/i6pGZnEcYAZnEqIZmX/oghyqMKZ9VNnyzvXF5ARhTBxwLzqPPDJ6S3D6PGdSWiXmwRJ1JGQWWNSJdywEMzW2JTdMASEvvKk9Lhoj2GRuJ5VhTKVPA113tFBrr6ZNEF0QxH8TKqUIvVbkpFhexSSUx1qym79PfPNzSXlqjd6Xyd0U700T6dK5KbxbbdBqsCtruyL5rN9mReuSMUlNnkiMnWjFWLuu16WZabhY2ch0jC9fHYWKDiqh5XT1sfYQLqZhYzA/B2BJWxLsHhn6W5eM6KNo899J2sJ/LRREiO88xSGT9e2rL0bFawx4lyKabP94DMVJU4cVUQWkM/jvPhf5nltTjNqxhK4uyQiOENlmmUM0KLz0X2SpNhnjG4RDNOYklzeaDGDQKyuQXHdnGWrN2QZCu3cC6RE6Nm2ejeYjMEELlpMRAFAPJRzZSaGOM2yETdGVD3AJhc4UnWPLOwaSciM6DNUbE4NEokoIle2cv4BCtHKz7u637diM4ny2sf/Y0dBUzLekzQj9m0Bx/EABvtZNKqiuJNX5NalXp0Wp6XWpqSvpMvqSq6qlNSXJl0qXaV1fddS9VlVVLUrqVJSUnSuqIvFp66p6lOpqpeTlaotl9VP1c7UlLUytVcjVJpK4y+lK1SvSforqXVJrlXpU1XWuuxVquqqKktUruomtacoR5VVaVy11Kp1Ed18uqqUpfq1SlXKtOUupKk/RS5E7TqX1J1d1dFeqRPnL0LqlfF1crla69UmLS6i+TRWrUrVRUmqWqJrqqrLTlJ1UtV64upL0y6VVVpapWqJ3rKdXROS1y9V0ua5evXCndlxWqlrpVKrulScpck1VUu4vpLK6paqV1LqXS0otdWtSuKuXKaWurpTV9KqqpS6l1q6V0ky6td1Sycv1XdS1ZL6lpUupSqterqVqrqVVRVXWuWuUtarVVrKl8nKqy5SkqqVS5aUpcqtKtVctNVfVpdVLpVr1VVUuVVq1Ly1dWqTqtaqkuqqq1KrSlctVqXK9a11NS65XVL10AAAAAAAAAAAPlgAAAAAAAAADuAAAAAAAAADAAAAAAAAAGAAAAAAAAAGvWAAAAAOgAAAAAAAYAAAAAAAYAAAAAAB4AAAAAAMAAAAAAHAAAAAADgAAAAANAAAAABgAAAAHAAAMAABgAAABgAAwAAAHAAAAMAAAYAAAYAADAAAwAAMAAGAGAAwAGAYDAGAAMMDAMMB7SbyA=='; From 3beb1f5c7c1e2c0b38c92ae590dc44132b741ac3 Mon Sep 17 00:00:00 2001 From: npezza93 Date: Wed, 16 Aug 2017 19:32:22 -0400 Subject: [PATCH 062/378] Add return types on new private functions --- src/Terminal.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index fae8e380..d344a964 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -2284,19 +2284,19 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT return matchColorCache[hash] = li; } - private visualBell() { + private visualBell(): boolean { var styles = [].concat.apply([], [this.options.bellStyle]); return styles.indexOf('visual') > -1 || styles.indexOf('both') > -1 } - private soundBell() { + private soundBell(): boolean { var styles = [].concat.apply([], [this.options.bellStyle]); return styles.indexOf('sound') > -1 || styles.indexOf('both') > -1 } - private preloadBellSound() { + private preloadBellSound(): void { if (this.soundBell()) { this.bellAudioElement = document.createElement('audio'); this.bellAudioElement.setAttribute('preload', 'auto'); From 1d6f71d6808313b8bed944e1cb5da01c3b63c555 Mon Sep 17 00:00:00 2001 From: npezza93 Date: Wed, 16 Aug 2017 20:31:29 -0400 Subject: [PATCH 063/378] Fix bad merge --- src/InputHandler.ts | 13 +------------ src/Interfaces.ts | 1 + src/Terminal.ts | 5 ++--- src/utils/TestUtils.test.ts | 4 ++++ 4 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 6ee081d2..3ec2aaf3 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -107,18 +107,7 @@ export class InputHandler implements IInputHandler { * Bell (Ctrl-G). */ public bell(): void { - this._terminal.emit('bell'); - if (this._terminal.bellSound) { - this._terminal.bellAudioElement.play(); - } - if (!this._terminal.visualBell) { - return; - } - this._terminal.element.style.borderColor = 'white'; - setTimeout(() => this._terminal.element.style.borderColor = '', 10); - if (this._terminal.options.popOnBell) { - this._terminal.focus(); - } + this._terminal.bell(); } /** diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 9ab16c3c..22531fa3 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -87,6 +87,7 @@ export interface IInputHandlingTerminal extends IEventEmitter { viewport: IViewport; selectionManager: ISelectionManager; + bell(): void; focus(): void; convertEol: boolean; updateRange(y: number): void; diff --git a/src/Terminal.ts b/src/Terminal.ts index d344a964..7e3b9a2d 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1882,9 +1882,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT */ public bell(): void { this.emit('bell'); - if (this.soundBell()) { - this.bellAudioElement.play(); - } + if (this.soundBell()) this.bellAudioElement.play(); + if (this.visualBell()) { this.element.classList.add('visual-bell-active'); clearTimeout(this.visualBellTimer); diff --git a/src/utils/TestUtils.test.ts b/src/utils/TestUtils.test.ts index 8e18c1f4..8d475560 100644 --- a/src/utils/TestUtils.test.ts +++ b/src/utils/TestUtils.test.ts @@ -97,6 +97,10 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal { throw new Error('Method not implemented.'); } convertEol: boolean; + bell(): void { + throw new Error('Method not implemented.'); + } + updateRange(y: number): void { throw new Error('Method not implemented.'); } From 0bb5ff8b091f3122104f3d3b04487a6594a23bcb Mon Sep 17 00:00:00 2001 From: npezza93 Date: Wed, 16 Aug 2017 21:00:57 -0400 Subject: [PATCH 064/378] Fix lints --- src/Terminal.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 7e3b9a2d..38235ee2 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -694,7 +694,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.viewportElement.appendChild(this.viewportScrollArea); // preload audio - this.preloadBellSound() + this.preloadBellSound(); // Create the selection container. this.selectionContainer = document.createElement('div'); @@ -2284,15 +2284,15 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT } private visualBell(): boolean { - var styles = [].concat.apply([], [this.options.bellStyle]); + let styles = [].concat.apply([], [this.options.bellStyle]); - return styles.indexOf('visual') > -1 || styles.indexOf('both') > -1 + return styles.indexOf('visual') > -1 || styles.indexOf('both') > -1; } private soundBell(): boolean { - var styles = [].concat.apply([], [this.options.bellStyle]); + let styles = [].concat.apply([], [this.options.bellStyle]); - return styles.indexOf('sound') > -1 || styles.indexOf('both') > -1 + return styles.indexOf('sound') > -1 || styles.indexOf('both') > -1; } private preloadBellSound(): void { From b5fc054fa58f52674de71dc4547d228e32e7742d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 17 Aug 2017 08:36:08 -0700 Subject: [PATCH 065/378] Prevent exception switching to alt buffer before open() Fixes #892 --- src/Parser.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Parser.ts b/src/Parser.ts index 3f05992b..983b3536 100644 --- a/src/Parser.ts +++ b/src/Parser.ts @@ -341,7 +341,9 @@ export class Parser { case '=': this._terminal.log('Serial port requested application keypad.'); this._terminal.applicationKeypad = true; - this._terminal.viewport.syncScrollArea(); + if (this._terminal.viewport) { + this._terminal.viewport.syncScrollArea(); + } this._state = ParserState.NORMAL; break; @@ -349,7 +351,9 @@ export class Parser { case '>': this._terminal.log('Switching back to normal keypad.'); this._terminal.applicationKeypad = false; - this._terminal.viewport.syncScrollArea(); + if (this._terminal.viewport) { + this._terminal.viewport.syncScrollArea(); + } this._state = ParserState.NORMAL; break; From c88f50125bffdd09a1004d804a6edc55c7439199 Mon Sep 17 00:00:00 2001 From: npezza93 Date: Thu, 17 Aug 2017 18:02:12 -0400 Subject: [PATCH 066/378] Add a bellStyle dropdown in the demo --- demo/index.html | 11 +++++++++++ demo/main.js | 6 +++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/demo/index.html b/demo/index.html index ee765c20..daa60b6f 100644 --- a/demo/index.html +++ b/demo/index.html @@ -38,6 +38,17 @@

+

+ +

diff --git a/demo/main.js b/demo/main.js index 9098c2f0..cb346de2 100644 --- a/demo/main.js +++ b/demo/main.js @@ -15,7 +15,8 @@ var terminalContainer = document.getElementById('terminal-container'), cursorBlink: document.querySelector('#option-cursor-blink'), cursorStyle: document.querySelector('#option-cursor-style'), scrollback: document.querySelector('#option-scrollback'), - tabstopwidth: document.querySelector('#option-tabstopwidth') + tabstopwidth: document.querySelector('#option-tabstopwidth'), + bellStyle: document.querySelector('#option-bell-style') }, colsElement = document.getElementById('cols'), rowsElement = document.getElementById('rows'); @@ -53,6 +54,9 @@ optionElements.cursorBlink.addEventListener('change', function () { optionElements.cursorStyle.addEventListener('change', function () { term.setOption('cursorStyle', optionElements.cursorStyle.value); }); +optionElements.bellStyle.addEventListener('change', function () { + term.setOption('bellStyle', optionElements.bellStyle.value); +}); optionElements.scrollback.addEventListener('change', function () { term.setOption('scrollback', parseInt(optionElements.scrollback.value, 10)); }); From 8a6bff42c9afd116333880e6c05e1c2fb5583d9d Mon Sep 17 00:00:00 2001 From: npezza93 Date: Thu, 17 Aug 2017 18:27:49 -0400 Subject: [PATCH 067/378] Remove bellAudioElement if it is not used. Remove extra import --- src/InputHandler.ts | 1 - src/Terminal.ts | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 3ec2aaf3..f2d97751 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -7,7 +7,6 @@ import { C0 } from './EscapeSequences'; import { DEFAULT_CHARSET } from './Charsets'; import { CharData } from './Types'; import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from './Buffer'; -import { BellSound } from './utils/Sounds'; /** * The terminal's standard implementation of IInputHandler, this handles all diff --git a/src/Terminal.ts b/src/Terminal.ts index 38235ee2..ac00f390 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -2301,6 +2301,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.bellAudioElement.setAttribute('preload', 'auto'); this.bellAudioElement.setAttribute('src', this.options.bellSound); this.element.appendChild(this.bellAudioElement); + } else if (this.bellAudioElement) { + this.bellAudioElement.remove(); } } } From 68cd79db34c0c871ebae2ed3360ccd89f8b56b35 Mon Sep 17 00:00:00 2001 From: npezza93 Date: Thu, 17 Aug 2017 19:44:37 -0400 Subject: [PATCH 068/378] Set bellStyle to a string type instead of an array of strings --- src/Interfaces.ts | 2 +- src/Terminal.ts | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 22531fa3..943003ae 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -115,7 +115,7 @@ export interface IInputHandlingTerminal extends IEventEmitter { export interface ITerminalOptions { bellSound?: string; - bellStyle?: string[]; + bellStyle?: string; cancelEvents?: boolean; colors?: string[]; cols?: number; diff --git a/src/Terminal.ts b/src/Terminal.ts index ac00f390..82bb9589 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -2284,15 +2284,11 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT } private visualBell(): boolean { - let styles = [].concat.apply([], [this.options.bellStyle]); - - return styles.indexOf('visual') > -1 || styles.indexOf('both') > -1; + return this.options.bellStyle == 'visual' || this.options.bellStyle == 'both'; } private soundBell(): boolean { - let styles = [].concat.apply([], [this.options.bellStyle]); - - return styles.indexOf('sound') > -1 || styles.indexOf('both') > -1; + return this.options.bellStyle == 'sound' || this.options.bellStyle == 'both'; } private preloadBellSound(): void { From 2f60c241ee0b46097fd8e915211dca14ea6f9d7c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 18 Aug 2017 16:51:40 -0700 Subject: [PATCH 069/378] Use manual typings over automatically generated ones These are pulled from the typings I wrote for vscode Fixes #898 --- package.json | 2 +- tsconfig.json | 3 +- typings/xterm.d.ts | 194 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 196 insertions(+), 3 deletions(-) create mode 100644 typings/xterm.d.ts diff --git a/package.json b/package.json index a5f0bd75..9b550d67 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ ".gitignore" ], "main": "lib/Terminal.js", - "types": "lib/Terminal.d.ts", + "types": "typings/xterm.d.ts", "repository": "https://github.com/sourcelair/xterm.js", "license": "MIT", "files": [ diff --git a/tsconfig.json b/tsconfig.json index a3aa790c..38bfd2fa 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,8 +5,7 @@ "rootDir": "src", "outDir": "lib", "sourceMap": true, - "removeComments": true, - "declaration": true + "removeComments": true }, "include": [ "src/**/*" diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts new file mode 100644 index 00000000..a64f132c --- /dev/null +++ b/typings/xterm.d.ts @@ -0,0 +1,194 @@ +/** + * @license MIT + */ + +declare module 'xterm' { + type LinkMatcherHandler = (event: MouseEvent, uri: string) => boolean | void; + + export class Terminal { + cols: number; + rows: number; + ydisp: number; + element: HTMLElement; + textarea: HTMLTextAreaElement; + + /** + * Creates a new `Terminal` object. + * + * @param {object} options An object containing a set of options. + */ + constructor(options?: any); + + /** + * Registers an event listener. + * @param eventName The name of the event. + * @param callback The callback. + */ + on(eventName: string, callback: (data: any) => void): void; + + /** + * Resizes the terminal. + * + * @param x The number of columns to resize to. + * @param y The number of rows to resize to. + */ + resize(columns: number, rows: number): void; + + /** + * Emits an event. + * @param eventName The name of the event. + * @param data The data attached to the event. + */ + emit(eventName: string, data: any): void; + + /** + * Writes text to the terminal, followed by a break line character (\n). + * @param data The text to write to the terminal. + */ + writeln(data: string): void; + + /** + * Opens the terminal within an element. + * @param parent The element to create the terminal within. + * @param focus Focus the terminal, after it gets instantiated in the + * DOM. + */ + open(parent: HTMLElement, focus: boolean): void; + + /** + * Attaches a custom key event handler which is run before keys are + * processed, giving consumers of xterm.js ultimate control as to what + * keys should be processed by the terminal and what keys should not. + * @param customKeyEventHandler The custom KeyboardEvent handler to + * attach. This is a function that takes a KeyboardEvent, allowing + * consumers to stop propogation and/or prevent the default action. The + * function returns whether the event should be processed by xterm.js. + */ + attachCustomKeyEventHandler(customKeyEventHandler: (...any) => boolean); + + /** + * Retrieves an option's value from the terminal. + * @param key The option key. + */ + getOption(key: string): any; + + /** + * Registers a link matcher, allowing custom link patterns to be matched and + * handled. + * @param {RegExp} regex The regular expression to search for, specifically + * this searches the textContent of the rows. You will want to use \s to match + * a space ' ' character for example. + * @param {LinkMatcherHandler} handler The callback when the link is called. + * @param {LinkMatcherOptions} [options] Options for the link matcher. + * @return {number} The ID of the new matcher, this can be used to deregister. + */ + registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler , options?: any); + + /** + * Deregisters a link matcher if it has been registered. + * @param matcherId The link matcher's ID (returned after register) + */ + deregisterLinkMatcher(matcherId: number): void; + + /** + * Gets whether the terminal has an active selection. + */ + hasSelection(): boolean; + + /** + * Gets the terminal's current selection, this is useful for implementing copy + * behavior outside of xterm.js. + */ + getSelection(): string; + + /** + * Clears the current terminal selection. + */ + clearSelection(): void; + + /** + * Selects all text within the terminal. + */ + selectAll(): void; + + /** + * Focus the terminal. Delegates focus handling to the terminal's DOM element. + */ + focus(): void; + + /** + * Find the next instance of the term, then scroll to and select it. If it + * doesn't exist, do nothing. + * @param term Tne search term. + * @return Whether a result was found. + */ + findNext(term: string): boolean; + + /** + * Find the previous instance of the term, then scroll to and select it. If it + * doesn't exist, do nothing. + * @param term Tne search term. + * @return Whether a result was found. + */ + findPrevious(term: string): boolean; + + /** + * Destroys the terminal. + */ + destroy(): void; + + /** + * Scroll the display of the terminal + * @param disp The number of lines to scroll down (negatives scroll up). + */ + scrollDisp(disp: number): void; + + /** + * Scroll the display of the terminal by a number of pages. + * @param {number} pageCount The number of pages to scroll (negative scrolls up). + */ + scrollPages(pageCount: number): void; + + /** + * Scrolls the display of the terminal to the top. + */ + scrollToTop(): void; + + /** + * Scrolls the display of the terminal to the bottom. + */ + scrollToBottom(): void; + + /** + * Clears the entire buffer, making the prompt line the new first line. + */ + clear(): void; + + /** + * Writes text to the terminal. + * @param data The text to write to the terminal. + */ + write(data: string): 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. + * @param start The row to start from (between 0 and this.rows - 1). + * @param end The row to end at (between start and this.rows - 1). + */ + refresh(start: number, end: number): void; + + /** + * Loads an addon, attaching it to the Terminal prototype. + * @param addon The addon to load. + */ + static loadAddon(addon: string): void; + } +} From 3ba3638ab7ae16c8eb8e6bf2dc1984f7cb155308 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 18 Aug 2017 17:25:40 -0700 Subject: [PATCH 070/378] Include all APIs from website --- src/Terminal.ts | 24 +-- typings/xterm.d.ts | 454 +++++++++++++++++++++++++++------------------ 2 files changed, 284 insertions(+), 194 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 8edb406d..96aced25 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -524,9 +524,10 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT }; /** - * Blur the terminal. Delegates blur handling to the terminal's DOM element. + * Blur the terminal, calling the blur function on the terminal's underlying + * textarea. */ - private blur(): void { + public blur(): void { return this.textarea.blur(); } @@ -1225,9 +1226,9 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT /** * Scroll the display of the terminal - * @param {number} disp The number of lines to scroll down (negatives scroll up). + * @param {number} disp The number of lines to scroll down (negative scroll up). * @param {boolean} suppressScrollEvent Don't emit the scroll event as scrollDisp. This is used - * to avoid unwanted events being handled by the veiwport when the event was triggered from the + * to avoid unwanted events being handled by the viewport when the event was triggered from the * viewport originally. */ public scrollDisp(disp: number, suppressScrollEvent?: boolean): void { @@ -1347,12 +1348,13 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT } /** - * Attaches a custom key event handler which is run before keys are processed, giving consumers of - * xterm.js ultimate control as to what keys should be processed by the terminal and what keys - * should not. - * @param {function} customKeyEventHandler The custom KeyboardEvent handler to attach. This is a - * function that takes a KeyboardEvent, allowing consumers to stop propogation and/or prevent - * the default action. The function returns whether the event should be processed by xterm.js. + * Attaches a custom key event handler which is run before keys are processed, + * giving consumers of xterm.js ultimate control as to what keys should be + * processed by the terminal and what keys should not. + * @param customKeyEventHandler The custom KeyboardEvent handler to attach. + * This is a function that takes a KeyboardEvent, allowing consumers to stop + * propogation and/or prevent the default action. The function returns whether + * the event should be processed by xterm.js. */ public attachCustomKeyEventHandler(customKeyEventHandler: CustomKeyEventHandler): void { this.customKeyEventHandler = customKeyEventHandler; @@ -2051,7 +2053,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT } /** - * Clears the entire buffer, making the prompt line the new first line. + * Clear the entire buffer, making the prompt line the new first line. */ public clear(): void { if (this.buffer.ybase === 0 && this.buffer.y === 0) { diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index a64f132c..5e942ad5 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -1,194 +1,282 @@ /** * @license MIT + * + * This contains the type declarations for the xterm.js library. Note that + * some interfaces differ between this file and the actual implementation in + * src/, that's because this file declares the *public* API which is intended + * to be stable and consumed by external programs. */ -declare module 'xterm' { - type LinkMatcherHandler = (event: MouseEvent, uri: string) => boolean | void; +/** + * An object containing start up options for the terminal. + */ +interface ITerminalOptions { + /** + * The number of columns in the terminal. + */ + cols?: number; - export class Terminal { - cols: number; - rows: number; - ydisp: number; - element: HTMLElement; - textarea: HTMLTextAreaElement; + /** + * Whether the cursor blinks. + */ + cursorBlink?: boolean; - /** - * Creates a new `Terminal` object. - * - * @param {object} options An object containing a set of options. - */ - constructor(options?: any); + /** + * The style of the cursor. + */ + cursorStyle?: 'block' | 'underline' | 'bar'; - /** - * Registers an event listener. - * @param eventName The name of the event. - * @param callback The callback. - */ - on(eventName: string, callback: (data: any) => void): void; + /** + * Whether input should be disabled. + */ + disableStdin?: boolean; - /** - * Resizes the terminal. - * - * @param x The number of columns to resize to. - * @param y The number of rows to resize to. - */ - resize(columns: number, rows: number): void; + /** + * The number of rows in the terminal. + */ + rows?: number; - /** - * Emits an event. - * @param eventName The name of the event. - * @param data The data attached to the event. - */ - emit(eventName: string, data: any): void; + /** + * The amount of scrollback in the terminal. Scrollback is the amount of rows + * that are retained when lines are scrolled beyond the initial viewport. + */ + scrollback?: number; - /** - * Writes text to the terminal, followed by a break line character (\n). - * @param data The text to write to the terminal. - */ - writeln(data: string): void; - - /** - * Opens the terminal within an element. - * @param parent The element to create the terminal within. - * @param focus Focus the terminal, after it gets instantiated in the - * DOM. - */ - open(parent: HTMLElement, focus: boolean): void; - - /** - * Attaches a custom key event handler which is run before keys are - * processed, giving consumers of xterm.js ultimate control as to what - * keys should be processed by the terminal and what keys should not. - * @param customKeyEventHandler The custom KeyboardEvent handler to - * attach. This is a function that takes a KeyboardEvent, allowing - * consumers to stop propogation and/or prevent the default action. The - * function returns whether the event should be processed by xterm.js. - */ - attachCustomKeyEventHandler(customKeyEventHandler: (...any) => boolean); - - /** - * Retrieves an option's value from the terminal. - * @param key The option key. - */ - getOption(key: string): any; - - /** - * Registers a link matcher, allowing custom link patterns to be matched and - * handled. - * @param {RegExp} regex The regular expression to search for, specifically - * this searches the textContent of the rows. You will want to use \s to match - * a space ' ' character for example. - * @param {LinkMatcherHandler} handler The callback when the link is called. - * @param {LinkMatcherOptions} [options] Options for the link matcher. - * @return {number} The ID of the new matcher, this can be used to deregister. - */ - registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler , options?: any); - - /** - * Deregisters a link matcher if it has been registered. - * @param matcherId The link matcher's ID (returned after register) - */ - deregisterLinkMatcher(matcherId: number): void; - - /** - * Gets whether the terminal has an active selection. - */ - hasSelection(): boolean; - - /** - * Gets the terminal's current selection, this is useful for implementing copy - * behavior outside of xterm.js. - */ - getSelection(): string; - - /** - * Clears the current terminal selection. - */ - clearSelection(): void; - - /** - * Selects all text within the terminal. - */ - selectAll(): void; - - /** - * Focus the terminal. Delegates focus handling to the terminal's DOM element. - */ - focus(): void; - - /** - * Find the next instance of the term, then scroll to and select it. If it - * doesn't exist, do nothing. - * @param term Tne search term. - * @return Whether a result was found. - */ - findNext(term: string): boolean; - - /** - * Find the previous instance of the term, then scroll to and select it. If it - * doesn't exist, do nothing. - * @param term Tne search term. - * @return Whether a result was found. - */ - findPrevious(term: string): boolean; - - /** - * Destroys the terminal. - */ - destroy(): void; - - /** - * Scroll the display of the terminal - * @param disp The number of lines to scroll down (negatives scroll up). - */ - scrollDisp(disp: number): void; - - /** - * Scroll the display of the terminal by a number of pages. - * @param {number} pageCount The number of pages to scroll (negative scrolls up). - */ - scrollPages(pageCount: number): void; - - /** - * Scrolls the display of the terminal to the top. - */ - scrollToTop(): void; - - /** - * Scrolls the display of the terminal to the bottom. - */ - scrollToBottom(): void; - - /** - * Clears the entire buffer, making the prompt line the new first line. - */ - clear(): void; - - /** - * Writes text to the terminal. - * @param data The text to write to the terminal. - */ - write(data: string): 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. - * @param start The row to start from (between 0 and this.rows - 1). - * @param end The row to end at (between start and this.rows - 1). - */ - refresh(start: number, end: number): void; - - /** - * Loads an addon, attaching it to the Terminal prototype. - * @param addon The addon to load. - */ - static loadAddon(addon: string): void; - } + /** + * The size of tab stops in the terminal. + */ + tabStopWidth?: number; +} + + +type Option = BooleanOption | StringOption | StringArrayOption | NumberOption | GeometryOption | HandlerOption; +type BooleanOption = + 'cancelEvents' | + 'convertEol' | + 'cursorBlink' | + 'debug' | + 'disableStdin' | + 'popOnBell' | + 'screenKeys' | + 'useFlowControl' | + 'visualBell'; +type StringOption = + 'cursorStyle' | + 'termName'; +type StringArrayOption = 'colors'; +type NumberOption = + 'cols' | + 'rows' | + 'tabStopWidth' | + 'scrollback'; +type GeometryOption = 'geometry'; +type HandlerOption = 'handler'; + +declare module 'xterm' { + /** + * The class that represents an xterm.js terminal. + */ + export class Terminal { + element: HTMLElement; + textarea: HTMLTextAreaElement; + + /** + * Creates a new `Terminal` object. + * + * @param options An object containing a set of options. + */ + constructor(options?: ITerminalOptions); + + /** + * Unfocus the terminal. + */ + blur(): void; + + /** + * Focus the terminal. + */ + focus(): void; + + /** + * Registers an event listener. + * @param type The type of the event. + * @param listener The listener. + */ + on(type: string, listener: (data: any) => void): void; + + /** + * Deregisters an event listener. + * @param type The type of the event. + * @param listener The listener. + */ + on(type: string, listener: (data: any) => void): void; + + /** + * Resizes the terminal. + * @param x The number of columns to resize to. + * @param y The number of rows to resize to. + */ + resize(columns: number, rows: number): void; + + /** + * Writes text to the terminal, followed by a break line character (\n). + * @param data The text to write to the terminal. + */ + writeln(data: string): void; + + /** + * Opens the terminal within an element. + * @param parent The element to create the terminal within. + */ + open(parent: HTMLElement): void; + + /** + * Attaches a custom key event handler which is run before keys are + * processed, giving consumers of xterm.js ultimate control as to what keys + * should be processed by the terminal and what keys should not. + * @param customKeyEventHandler The custom KeyboardEvent handler to attach. + * This is a function that takes a KeyboardEvent, allowing consumers to stop + * propogation and/or prevent the default action. The function returns + * whether the event should be processed by xterm.js. + */ + attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean); + + /** + * Retrieves an option's value from the terminal. + * @param key The option key. + */ + getOption(key: StringOption): string; + getOption(key: BooleanOption): boolean; + getOption(key: StringArrayOption): number[]; + getOption(key: NumberOption): number; + getOption(key: GeometryOption): [number, number]; + getOption(key: HandlerOption): (data: string) => void; + getOption(key: Option): any; + + // /** + // * Registers a link matcher, allowing custom link patterns to be matched and + // * handled. + // * @param {RegExp} regex The regular expression to search for, specifically + // * this searches the textContent of the rows. You will want to use \s to match + // * a space ' ' character for example. + // * @param {LinkMatcherHandler} handler The callback when the link is called. + // * @param {LinkMatcherOptions} [options] Options for the link matcher. + // * @return {number} The ID of the new matcher, this can be used to deregister. + // */ + // registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler , options?: any); + + // /** + // * Deregisters a link matcher if it has been registered. + // * @param matcherId The link matcher's ID (returned after register) + // */ + // deregisterLinkMatcher(matcherId: number): void; + + /** + * Gets whether the terminal has an active selection. + */ + hasSelection(): boolean; + + /** + * Gets the terminal's current selection, this is useful for implementing + * copy behavior outside of xterm.js. + */ + getSelection(): string; + + /** + * Clears the current terminal selection. + */ + clearSelection(): void; + + /** + * Selects all text within the terminal. + */ + selectAll(): void; + + // /** + // * Find the next instance of the term, then scroll to and select it. If it + // * doesn't exist, do nothing. + // * @param term Tne search term. + // * @return Whether a result was found. + // */ + // findNext(term: string): boolean; + + // /** + // * Find the previous instance of the term, then scroll to and select it. If it + // * doesn't exist, do nothing. + // * @param term Tne search term. + // * @return Whether a result was found. + // */ + // findPrevious(term: string): boolean; + + /** + * Destroys the terminal and detaches it from the DOM. + */ + destroy(): void; + + /** + * Scroll the display of the terminal + * @param amount The number of lines to scroll down (negative scroll up). + */ + scrollDisp(amount: number): void; + + /** + * Scroll the display of the terminal by a number of pages. + * @param pageCount The number of pages to scroll (negative scrolls up). + */ + scrollPages(pageCount: number): void; + + /** + * Scrolls the display of the terminal to the top. + */ + scrollToTop(): void; + + /** + * Scrolls the display of the terminal to the bottom. + */ + scrollToBottom(): void; + + /** + * Clear the entire buffer, making the prompt line the new first line. + */ + clear(): void; + + /** + * Writes text to the terminal. + * @param data The text to write to the terminal. + */ + write(data: string): void; + + /** + * Sets an option on the terminal. + * @param key The option key. + * @param value The option value. + */ + setOption(key: StringOption, value: string): void; + setOption(key: BooleanOption, value: boolean): void; + setOption(key: StringArrayOption, value: number[]): void; + setOption(key: NumberOption, value: number): void; + setOption(key: GeometryOption, value: [number, number]): void; + setOption(key: HandlerOption, value: (data: string) => void): void; + setOption(key: Option, value: any): void; + + /** + * Tells the renderer to refresh terminal content between two rows + * (inclusive) at the next opportunity. + * @param start The row to start from (between 0 and this.rows - 1). + * @param end The row to end at (between start and this.rows - 1). + */ + refresh(start: number, end: number): void; + + /** + * Perform a full reset (RIS, aka '\x1bc'). + */ + reset(): void + + /** + * Loads an addon, attaching it to the Terminal prototype and making it + * available to all newly created Terminals. + * @param addon The addon to load. + */ + static loadAddon(addon: string): void; + } } From 92ac427d00a87d563d03993482ebcad1ab6128af Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 18 Aug 2017 17:34:10 -0700 Subject: [PATCH 071/378] Type addon strings in loadAddon --- typings/xterm.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 5e942ad5..98312875 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -277,6 +277,6 @@ declare module 'xterm' { * available to all newly created Terminals. * @param addon The addon to load. */ - static loadAddon(addon: string): void; + static loadAddon(addon: 'attach' | 'fit' | 'fullscreen' | 'search' | 'terminado'): void; } } From e4e2fbf88562f1ffd309002f59ef348fbf2792c2 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 18 Aug 2017 17:46:49 -0700 Subject: [PATCH 072/378] Add overloads for Terminal.on --- typings/xterm.d.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 98312875..268d95d1 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -109,6 +109,14 @@ declare module 'xterm' { * @param type The type of the event. * @param listener The listener. */ + on(type: 'blur' | 'focus' | 'lineFeed' | 'open', listener: () => void): void; + on(type: 'data', listener: (data: string) => void): void; + on(type: 'key', listener: (key: string, event: KeyboardEvent) => void): void; + on(type: 'keypress' | 'keydown', listener: (event: KeyboardEvent) => void): void; + on(type: 'refresh', listener: (data: {element: HTMLElement, start: number, end: number}) => void): void; + on(type: 'resize', listener: (data: {terminal: Terminal, cols: number, rows: number}) => void): void; + on(type: 'scroll', listener: (ydisp: number) => void): void; + on(type: 'title', listener: (title: string) => void): void; on(type: string, listener: (data: any) => void): void; /** From b0164e1f964d46cdd18f4267336965299f0cafd0 Mon Sep 17 00:00:00 2001 From: npezza93 Date: Sat, 19 Aug 2017 08:38:19 -0400 Subject: [PATCH 073/378] Update bell sound --- src/utils/Sounds.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/Sounds.ts b/src/utils/Sounds.ts index 65c20c3c..413a7a29 100644 --- a/src/utils/Sounds.ts +++ b/src/utils/Sounds.ts @@ -1 +1 @@ -export const BellSound = 'data:audio/flac;base64,ZkxhQwAAACIQABAAAAt+ACPpCsRDcAAAFSY0hTdxoSGnIFMiBuLRx4+DhAAAeyAAAAByZWZlcmVuY2UgbGliRkxBQyAxLjMuMCAyMDEzMDUyNgQAAAAhAAAAU29mdHdhcmU9TE1NUyAobGlic25kZmlsZS0xLjAuMjUpDgAAAEFSVElTVD1jb3B5YzR0CQAAAERBVEU9MjAxNAsAAABUSVRMRT1EaW5nIf/4yawA2Vb2SQD7SAAASAACrQADPQABGQAJ9wD2aAD0Ov8Ncf8H2AAIBADmIdfSpfYUQnidPDwnzzwu8Q4T0hkj0fq1InaCJGndpb+0VZhQIXWNoqrFNKL1wYQXuk7xqJOipoTgITh+3SKIu1wVNOqsr+l9zFKBdwKUikHGste88i/UjulKRtzgE3Vz7+fmI8U0UzqFuxH9VhOkY8KjSkR2/N3Exg+9PKnvowcv5aL3xCCLPxjmXv6MFP+paLX9KVW+seJ249ZcWRh3sUESSic1z1pBkF3BsHwRKJkuUQJ7yhpWCjd47owqrwfHVpphY20P1BPs6qNg6lG9f785hOd81FiBIA2xRQ1smE6tprk3OoX7AheTmfIXMY+mHb71FEEl6WsvthDBeCBWDlNWUkkQV/frRSulMUwZoyEoDzr98XMILfbWQz1xmorBSsyUm2IJ/l0WVRHFyPiaOvsWskVf0s/FVVWG+gdAfXPLAk1wSVRCbRHXW2F+Ov4VdlX5s8me3+VesT/EjGkovyURrsCnbpwORajTNECSZOdJ9IvjhmIx1i38P3PgJ+sHGeXjM6YHwKtykAyYdlSfcWTgIJmaLHwGiM79NJam0EIYlf2Rjgi75EpuQJmV9CFj7hMLBo2lR+jQmzEol4uYN1f2WtfzoKgUyH917pX0bQqSX/kD50s34tvCRu4sUc29OL5tLmAS+W+vmHlJ5SkUNhy1XE9WBpK1km1bStlI21Up10mmo5DjC0eUssWUhAU0klzYJGJYsKKb3ySWqICnnvNM0FzTmAIrclPYhvMBSgJbE0Tk8zO8eThqOaCPd5KQfvT3bW4aC4xiaFRNt0fMs5ksLr7QJCaLH0L62apiAiCjNo2HlZIy+2qGinJ00hho1o+1jKjpsDPGBqw/dNASOooPVanrb4hhVyakiOlnmXZYL1H6rAL3ducfj5ks5pFMTaXOFfdg/S2vm43j/HKZe8bJvIl+kRVN/FoEghENYJQ3FIFURuXV0rUQQFrE5DIm7DNHYCcboENRYK42lC9ogVtNxVND2LeNVTAT799pZyG7JAWZQrlHOY9FiQIhxRAt+iFOc8qEVwAjF/guiNdkeSO2KBR2W2BgjWckmMl44y6MCnrGzCg2thqZrTIV7QqqcNAPqQ1lU7aS4jAha6QGFydsRArGDwrv5FKTaBEslstfnlF10OdVCrMSxYYHUia7emYHP1udbF1Ztn87oordptubHiYhYZEE42O8CNYYGp8+8fh4PLaJMa3hJT5YScZ6LtfFMcm5jHpLhICx5xDYw9pO15pepHr4EC4cmphcY86Y9VozR4+U4MDYSz4j9pFBpCxpUDomyoG2IdVYrlZF647P47+Y0z4hUYRSoq5o+zp0NpMtJK1IuzG+WC3Oc+SxAYYSgwtuMEv/dYMJWBimjCRTFNOMalTIzPaS0I6Az1ORx8gKBSfyuhIC66Hm0coinUaARsEJ8D+w3NrCL+oU1RHdCtiMjIx1nrQfLG5lPGrfs5SR2lhGdif5ICH8Hlysoi7gR/CrWQ7S90XGC38jZ2qoRIz5Gd+3lFFfewZVjkwivVehOUeg1GWA0rBmBOYgxTH6lWTpAtx9pduwHox4NtMmb92r9yGEPIyyHpoQ3zYaG8oORoKhwFg4NYI89tRJAnsXElJzXhE7iX469XJU+cdhWFFNPLJ9NBerLlFU5qtWzhvaDm2/vOyYLeA14+o+12Htf2iX3xn8EG0kJLAXnSTEJ1CUYb9ubK4SRLlvIM1S2WHfDzICczcFcfG9iUIPmyEa4ZuDuwFy8VlRRCKqrPuUnPbyFAGVM5bA1t1MdTQPIbDsuZJueufiTEwauCyd7QIcdajcbv2ilICEqNRmekrDlT0Frc2IDoeKQ8mA4wB+xBekGkZqQMX2U8JWG01lnczODN7I2z+vxUS+pFzYtTkLctuHcrHvWUyWgg5WSFlChnGD2E3mOaM52SKhS/OHbcLFSoy0OXbOj6OXDKSKf4Cj4ULqpSTJzOJkR+4q0QgkwpixmIiO1NjWDbcdh9vNNHYzjlzV5UUQfH74w7L7MF7n/Z3Qu4FNS6zWzmWDax2Vl3hx2VE0hQdUUqIBUuZkLjvbCKHBNDg+Vw4tRF9qrea8RijQGT8TC843EqR5ib+E8bXiY+GN58cWN1eWzRS6tGQaW0ZJB2BcHFmgNRpo1LjhpCS8WGaXpJtji1XMY1T48cMYPpNSME23KAIUSeAoePCCKuftiwQdQzFgvKkHaIn8KI0KVMKDMfW50ZkXD0k1d6dudgD56YOlhdWsSWKWMRryygNnYiyEG4s3LpxULR3MEikEtNJa3CF4HhcFT8LMAyItwbIFXIphSmwWb657ctJWU1GYcHyxvIMnmvB1A6oGVqRYDrgEdkl1H9IlbooZUvrIE4VRJwhGOmYC6G9fdB1c8XKVISURS2b5mfP1oolbqgg23jup+BNUr73r7MqkWW6kEZEphvFn0WzwtsBA0N+Am5rURaSk5qFGSKRMI3k9Sy83WHptwuAUppzMNVlLBRIQPUfqy+FXlUWtmUlXOIzl19n2W9IylL/56CJIGJBgzDjysEPgTHD6OhE+Y/UTcqacvonf1Mns0MKcLZuAmSS2JTGVUN/L/PEOtg+Gj12NDZ3mRRfIK1aTElJgV4Nndy+p0iao4eTmlRRJrbTKF0t82tyHwH+ibQ/TZWB5NGlMqJtM+VRoajmPO8Vhh0T7YgN0YcywilDWyb7zg+QsG+RPm0kHu3jqoxeK/VrrhbL8cWqIoOyBbc+tyqmuM0z1634rpiLR7yyrrOVEQO4uCSQv5A5vrtms+PTK44v4sSzRGwGq6zB8h98farogtiYbKJ8IKsNbML3UQJZRboJOql85mCEWvj9gJUpeVVa2YHPn7T8lNv3VBVejNgsx9autUdZPo+9vQrtH/Ilz9bRWnSDD0xJCEL50MlUJFsbzIT2pLGvdVT0zzXX0tDmTFs9bkCX+dThI4HKEINoo2la9IhV5OZK15jk5S9DXKeuZU9spyi5KEVI9gtUhPfXJuTfJX4ufiXDE6BsoFsedwUjbCi54tZn03KPwvJBbhGYE/5mk8hStDctqSLxG/+CQzCYBNVEz09CuxRxz8jGIu0+YK3jS26uUj/WXrTb8XljMGlgPyVEvmRGRe9KThgWIjgcIOp8BMxRStcMX1WVoJuxjApF8CTOJnuvr11vnXyd6JnmswFQRpnmSQxKmjEUXQmxjCWJKStqVq5ETlXOchZacrptnS26OG3FHosbcak1PHG993h+FfMs8OvjieImSUY+oCVCq3D2ejVa3CfSMSIg8hApRBnvNM3R8yRd9SGoZWteRE7GVGDmT4ZD1kZmfcK+9H6CaIp54kuvHVJNJVaLLZaaJ7CVBze7u1qJk92WJviXRgrJgwHCRQESc5zlJ8mYlqxw4K7ihckbOJKZ4rH962rdELOdeYxW3GrE4y7ZhRCmMPT4ZNgvGBYyHOIrdJETqvvOPxnDO64EBJrkYZal9hZdnG85inMIxWxUoBfrElETPyBySA7DRJmcz83fjdt5yS4QfPgk1hGfAl1BXWCawEK4SOC32XMh74CHeUTiaYsC1Cm6QRO9leCXmJFOvxNKBrt4guilEfvXsRd2MxkiV+uNe76/q2RjXm1VvUd72eH2XIzlm4j83Z1wuZCmEETMf1j/ckkrCNkqrZ0RoIiQxF5uOWpe41i0iHH8e9RfLfbh06G9A1aD+EKDQ8hCPKc7hxtLVDGu3dSynT1///D2JJwtkhKQK9By0LUSwWhUDKVNb04PRNjmMv3J4o0pFVdVxHymkwu87yONoYR8RxJCDaCCsDZKMJpy0YhLIhNQcYs5aZ3zf2rZaQLilWTBCdY3RoLiDddq6wvlG6CLc+5+othEKsX1bymDOwAYCFKIxEzo2NtQVuh8JF2F+veToj+xg7krCLtU8PSTXN3Nb+puVYFwC+nSvQUvnGpoRRpA9BG5HdUOmwvYvPiDpotEzCrCXfSmmZUn54YuQiThgXdlUctIHslVH1r+VrsvXtjJl6aTUbQLLacaQ4qhQQOJ+skrqkVW6gIDYjqC5UJTBdvTvkb+/vzY9Nwk3VTAD6M6I6ftXdfa6t0TIV9o8p2FglJN0hIYJtxvFIgTIncmpOfid1M9Pup/7GNEX7RzPDV8P2IT7RZJfhGgZjvob3U13prFeT9UIbx7Hf+flYpEzLVS0qhrnXSlSNcIQIgW/wi6E0pZ3YU6jNukpD5C5MeZ6NE3IdgBRgF94ddmqN1RKXYnUn+IKboTejG2ojlS22aze3P8dog/S+1Jy+x7XBDkOGpUP7hrML5b6wi4cC7pgiaLxqyY3B5TWmxWDLwnP80UeJiltd+NhdIzPALHxTRcg0M/ovqt8ufyObIfFzt1ib28MSQ66CI4Ma5taMw4wuBRiHy4KsAZyRbwHKoadGBloCkYKOfcILsaAbQdUl+tMv7ohptsEH5BJ3hXugvxWfXoH0dETqQNH+NQP0cyFdTj6Rj4qNgRRGNMQXiB8nCyYNrAjmvHsqGxGVitZPuleks6e856e2ItGjpSZXvJ3S4K93vKQx0DWwCMoOJI8OpmDylXiLgRO4dkziC/6pbG3/yRMByUMJKIwakUWQOakLQxIYP+J8KWiDX73yeELCBV2vp/izojQjhhCwA/8gYIAJJwKWASsTk+/AIu1ADpXNtCt/GsIWcSNiaViDujv4s+7VrSPYpoaKBRafPK5SJfTF7B7cvgcwWzHfXTNIEUcjsKF/QJPgnsmPU6boBvgnaFpaUlRW9Pc0VVX+b7d3Jf8TppFcpsnfH36nsIZnArBAt8Ah5BqfH5hqccK6TfwsVd70PJ/poVLyBgILZwrENq6dwz4MLu8XmFl0oiAsKaZNfvZjumoOLkslf033th9LhKDDjKDhKBwEDS6H9AapHmR1HLIpHqEalKELKDeUbn/UtkVFN7xHfhzV2PmoWqA4eB3XBjaEaU9MLLCXpoacFVCi2iaX6J/Y0VghsginBNzGVQhXjNOemD/6csi0Ed0kMHlq7VJZ0l3GG1uHBWJcMwl1Kz9UFWUJS4gyjjMI6gsImO1G+ISiFyz+FdEumimSMCLmW0Cp9pgeRCjYAE64ZbAnCCFgE7gIIKpyyCXUaL+cX3I6dy6tRH8UpoUXjxenOBwwZG+wUMR0gZoTliNdB4vHK0iCp4FIBlJmK5sbv74nafJKywWOksryC7WbViHircuxU2tXY7/jrT9BJxMeovhCRlNvgiHPEJfoRXghljd8HrQ0I0ga4xx4I4B7Pkemf8IQXIaC4ck4z7RthtEVfxz7Peoki2BLIdoFecbnOE7ZOWIjiYU0I4IqZJIWFvwuG2/1sfSHeGmgEooLxoYARNdI0zx4kISSXXUb2PPFdUv26MKjK4cFNEDJnVtzCyOWIKfwLlQImQPXweZwX+jts5yJ9spKxB2lBL8b7w/TBbba5190muPT3Qwmif4FuIpA3Udh+5BJFf8nZbpneiTSq5T17XlcR5xqAAibgXAAKVoP5APMghFo4GRbTyPP/yj86PsPxDtQtU0oiHvNQQfh7qu2JjVYJBQbtBmWEkdjvMmtvzoZEr3XN62nB7h/7AxLjn+CBQHgEQT3lcyjrIf3i0VNL6qCqyWICs1WNWTbpdcmQ2RhflWiThYgAirAYpAEmgH+QOBwedhrVKaRrzI0Ch3zIlwk2NigSZYVxRAb1tPR3UxKD5pCJOBY5WFFPhYPkAGf34+zQTDIYiotlUKvKjFwEUuxawDBmEmARoQjUDJkSNiaaS/SucYninWrRPtElPRXAm8KXH/xnjNcDz9IEUwMTwTkAqbCKoi3OlFtgsZWNsm9lBcpbHhgmtl0Y4FjMAQlTWaZlTzOLHY8ohMqK8qCMcM4RrId4oeWYU14Vno+K09Sng8qoh/DF0Lexr7XmDfHLSqY5Da+LEB8zK5DVyof+1pK0pVFCSq0vEpSgxHaDD+P0QtWLET6/ICsWH4ymQ6dwlgx9khiIl7/nyp1/h76IU+qxDadvQCWIxqSFboiDngZANXg5K3jBfCv3BcRgmRQjTWszqWzvXF5gIQX88Zv2ymCML040zrsM1KiFMr9k1lZ18TKsNr5emgkeBOtBKIBb1B3rBwHDxsIiRlvJRsebooiRmCjWHMFNUQkIe/Q2hq3O3AzIQKK/jtQQUmU0bhQvG+81OkpkiWyafKEF/JkUDrx55yBQWMgz+hUeCTOKFZUjVGLnTQZ6H9y/dnfF5JqF04tFlpQLmg6i4Xg3dS44BHFBdkAlRgxAB8iHmtoyd5KIrTRpd8mUOQ67UpUttBTCPbe6T/bT5xIqMPB7oxLmjKeSgGOaiZ979ugmkKbe5xqt4mfuO/g2bgJNAO4YJCIbWQpijKGJEam889ohORj5h5WyarVA5fbi8RCXFJS01R0PSqileovDGXQTjSCQyENcUVq5kBinXG26vy5L3Byg4MKNQobSNFLUpwgll74v4EMiJa3ebRLsv+Ua34eiILyMKXkr620qgJ1p4vsh8aCuoGgoJfp+1fTUFpO2PKz8WY3COKq7Ik9L+rIlTm+ObdXJN+9MRJCZ0OiBqgSYVy7LO1kU/okgxECjBvM2veDS+aa2V9RdrGV4u7v4b8uKq4hFj9qHZ4cAhWnK2z6Xll/N8XNYiUxEkt8luuqXQxzjRkNKhVvcB3ttptyBfkjZd0waGxpftSpF6WHKUZQB73/ZV8camCmjkTODHw3rvFCMqNrE0WQ4NQVOoNwsLqcV3Ny2285RbtVyAMVmT674E48vZmGU2XBi2CqsO+4mmPCzGrdGqsJRnODEmiG6IXKAnAhEnCeobnRAfTaZlQss1iip7+31gnJamxXzf14PhHxzkKhbUKZgsUhs+BMzByjBgBER5/SND9Jmk7a5tXtTyCtRQOIxWWMcYgTF5FYS4FED1FOP7AhRDXkFtEyzPbZIIZRUH3l2x3EIqqSDOeC2sBhTAw8AxnhKROM5BRRfGZj1CoiFXdtWvsFo+9QlSNsdkhqXyKtk+toqXBPXDEmHAQK14/fjGwUBULVEJJ4XUzt5DL6P1AdFgQVAmwC5oLcR1LE+MVtiGqanzAMKrZwWMmavggJi0pKcMgZYlOYoOVA5uh+OBNtBm9DUiG/4ST2bcoxd9XuL464q+UcmRWTG93SgDLvV6UuL1+woOWHyznE1EURWypoXvyM9tRkSll6V3KQrnsO+RgwGGoLggSIwg1l+lyVIoRuTkjkiJudpXl9sytoFgOjNjE4SJRUfoJ4xoTP1JZwQxvHUE0TE4hGYu72/NW0tuOza5ATc9wLkhF4RX1ezwi40TXVAroYh1j0Ef7xTLerEccmB1H6KLM89RmpiCzSK6HEZpQhBBKCFZg+BHgIVHxIMGRQliCtMKVpipNXhWXRODU3EQOMEURGJD0o4u+KqPvxL2kpLKDGiRKjEW249RR4TiRBLsH1tn7s4PH18o6y+QF5gODZekFPwbeB/PBNOCTsJTSiSWX4rUTSvEKSXnKFzj5ryndufWTkaX1i+QoKoEK3ZwcMbg8jmeIvFqe6As261mO1GV/++1zN+57j6MGeIJLw9WHF1assMUT6Jd8iy5Iosb1rtPJOvZzZourAlwcuvZryk7MQ0bhzzDI2Hv0EDY75FC9euUajhpdvrxvSc86Wq2ivEKxp7aevFCOw+5vxJ0tYdCJ40JDN7SqPD27SXNKt8Ytoox3bnCMepRfcBSIHdkJ8ASqRKrIQyrWIkFeElvmwE0hn4QYD0o/JQ6z/4DLGTkE9sTvjrMe9gxxB6sHwwUSiV8QraDXV4O7fyiGcYSTgpRhIsBrZHnAIyok+uOzdPS8N/3nkz6XQiubyaVEwaGeyLfEYpYyBX9uua8nFmTCXKMSAuHmv6CK+Yur+XREbPD22+BOSdlE8f6qIUTlQ/8Z5drYZrzUeNx1KABWOUg7VbMZ88tRmMj2XCtzFAa/RUNiowCmuECoRIyXog3Kvqk5gy+/GoUQ3qeHTGvGK8Wz2fHiuPSkPuE5kXoHVK+uz/o3MtR7iQiZ0pm+73vSb+ZUyo3lHEJ9wyziAqRazVYQGi4SacBUOEHgWLDTcKeB74X5HV+Wj4RmoMYkK2MLCUm5UDOjvY2j9avUKri+Wptl5KvoXsH92xEm3xTA57SOJLNtoLkCO8q8BvUltR3ioTGD0OFNEnrKxR3gn3OLvs1XzQjVRalhu3myeqBSaIxbHK1prbJkpOtCIGFV4EUkFkYOiRH4JCpCU+yZVKmBKdL7PVZpMaKrOHfA+2P9zP1ZA/0aYMaxr5AUCqwP7eu+LkTs2vEv/YUn76A6uxArDNGDz8CmgEgUJYxk3ZTiVXZZZV4jlDEUa6cesWt9yPMZFFj/lKzTX+dw9F6WU1LwuIOKznupHaCcSg4ihwn6xl0kG/YV2QmSsx+kRNj0Qu8C01pT0HFRiMcE/WSXVX5r6dRo4jWReomlST/f5fSf2xhV5CEqHNAF64GfcNUJY3QlO66uI2bvAsRQtnF6qVvGMYvyIaCZWRQ6ljT1+Kq2lpSJVqdUxqZcXX596impHSCPYFXTvIPrzYNB6ECzcPVxtwbFVP2z3eO1zO3k7bHUJ5N+qY9/qlv2VRIhpxean6Cul7Zpq8EagWhTFiyFoHFUztLcjSSYzETK6TZqvsmUkTcGVSUPZamoxfPlxeYF9C/4cKixbvrv/PDEdWIKLcCeRAgcw5YTyDUqMUSylFu43LOMJp+I/CKl+99Qvpxmm6pRv5eI1yNeOr5FnP5JONbHjojJPzJXojoiEmS4kWe0Cx/gjzbHr6NlM3RZ8GlJzGedJKAeUSPaz7IzTNpuaoPWyEf0iBKTR7qZPW0LiEO+Md9LR5li8jK0dV01LC1ObfDAMrTtRV0ssLdpQ0M8IVrZV9bbYTJXOgzXFatvUb4zDKcLwWIgUVQdAQuNhW6U0Fiu1cWI2K5EVR5tjlY/mR2XyPQd+zTknRIXWzGTUSR+K8IpXn7kSeT7EWnCZyhD/yEnB0sjS2+NBkjDMICgGEEEcKREVJya5ImE3ruslUfChf4pN6sbjFVpOYVusbOjWv9gSFAtFhduDpEHZcMoAUTmDB8oloGLgMfF568rTlRA7o+nraj+sduik+1pW8/W7SXBfRnZP8mUvVxlsbEnbSbHOItQK1/FDx5ODv2HKAEfQCnAGCAQvTdhjlDb4utv574lPB7r27jeuuoQaw1Lq/0KbVtQdmw8pjUuG+EYwRLAfU/IemZX5uZbbZbaC6WbReH8Q9yC+q09ET/YbqKdz95mNwz2qrmvXbJ5UvZUY6LFkLFc1XzJpby/uAcugS0gQJwqMBq5Eg4pUGGVbGovmCtYxu2D+/Wh01lTUtskldyMLbVP2XJr7r1ADMuef08j0sk8uemociYWAI9XSu9l1Ubx6PE4cTYT2iblT4WRx3x9Ug7QkZ44BOyTwW991wmIakW8JhxumvsNlbeCgzQjWKGT4Lso6pEDxPSscvDL62YQHY0RQnzViONzYMmKmAudNo9AHyueqxr0NQJNBV4ZAGjSND7bC1ZB5ih98hW6ilhLa1Do6ID64SySJ4iV7heSKiCuOx34KUxB4VOFadA3btbIFJ1t+vtufRH9vjt+P3wrGm8dYpU+aj7fqJagmJGaiJF0p2/TK+xKkxMoQyF5jCbJwBvrH/gYAB88F2g8zlbVNeOA8trRZBgsdzJrYift76sNw74+Vpiy2l8GoVlPTilKOjKunARbjTwf0jywoiFnDZGUI6OmfJgh6D6SHH4ET8IMIYPCumRP5p+iNdCRbFviVb5QAur9hmkk67f2Q3o/kICiZeFGeiieiuMaYixQmJcDFXfipcxl92c55ci7nKNHmMXIc9+DRGYPnQ945oqlmuoFlF57zWsboR8xvLqkg1j6kw1Z6hR8Zl17H7EGEYHTUEQcYqhQJUxqZvTxbWtFySX2pfLtUtmr18j5HXF5KaiFjE2qoV9A2GKhmboaXuk2+Olg+q/UXRYuxzzzVEszM+uDowHFIJDwlWCcgVfHP5iIfxvhmlc9sHmrlvVTu1dR4jjhLEn2IhFaoxE1XcTkxCgN043jiMCt+vt8ihRECvNt0A6HPYl4GWlv1ZFtS5Ue9I2X0s9PjbZ4SBSPD7y+tkVaq36R4PkCmQnslm20CaeghGgzYg+Fgf3QJfoVpQw3CYyQAhzpCNENlgjgCGUUJ0HvZyaS6VTw91xb2yovAscj7qNVgs3PF7jBbgJJOkfOTXZEksxWeFvgnRIXKRKlOmJnIQQiMqLZwjWDooExQSWjCUleIzD3JKvMRzIuhxEIpAueWZ9Ar6hKPG9YTJDlKXujbkqXuWr/0zuFuE5HCugdjWR9o66kelPSjbJRkQHCAXNRabF7YwDLEC/5EDWNeUY8I4eylHtq8nTLSR6SnOd4iSONnoonC+GP44RowxbA/5AQAwnoDFA9VNMtcS3rOIzxtK7J/IE1MMuQdGxS0FY4pDkmy1h6rS/Lhm95lKRaiPL3HFR1RbteHNg6oR1EaCR8FH4ExAP8g91NLj4JIkcxWzyLNeMQHpDNqOXTdxTvoT72olsLW09kPFOcQnB9zG4EIZp14fziz0SLHt7o1x1UzeukhQCBjzB61BmUC52cpiWGyQO3dA7snle6mFqmr/j/eBRipKHnNSSAZL1/MqcKJ6gxKhHkCd+MC4seFelb1pll6nnjC496t35c+2CaguhEjhjQRjcSbpvXL9LjN04u3ah7PZc+X6Xm4l7Qir1SStt765JeGFgb9w1ohuNHB4SlnB8riyRtXp7Y1R2tumuFWfiaFJHJiAIbw00LQomzd7pZjNre6aUz+ZaaV0TT/Jzubfct5ZclJSkicc81JlUvllw2ZZ0pJIJNJJDVTSUk0pTErythS8L/QvbTtMQsuahUpWfUob+oFaXGFMnql8X4WR1lIhaxMwcJSzTh5klDUNQ1PpLUJ2GtTSUiQihnyaFZKmWoWUwpT1CX6BBDxJGKFLI3J0ssIaEylmGR8M8UuET+VMwpclcsPEJU8K5KlykxkpyhGWVNAqcvhpzydxnN7czywiTEpc2zjJWnzSCoaSdzOz5cdrCpkpGW2XOM/lCyQqUbLJ4UuduMzZKeaNJGQuebyVS3nEzDKQokIJ9NpQ+RZzlPpfJBOeEQ3iXL04RJBJtlLSF5H9PEknpPIIRLIdCJZ3LLNlJ1KSzlL9CWnThJFkSVJwvLJj4zPN+cmp+hWG5yk0L9CJ+yRClspbOWCcKk2STaRCPSl1h7ZzXJMushpGZmRCOd593LczQypbTrjhSIYzKLCkSIWH8ozlJz6eR+fUl55IgWS0kZynLE1VJZkTwjDhUKfJEyyb85RJuWl4QYR7uWAQfc5l8rykWmU6UC5cp3L6U4ZlIIRmJM5oY5TSYhtOZyzwkQKZMWZxCIRDTIISk8CYhWUMiRD8nZZIzEMQ7X5nTI5GZENSJmF5OXUudyNxEkjS4R5NKlLCNmWzm5T5bG/cO3Q2ehE++vcnRLNJxJSdDN8nC3IkInSJSh4h50uSHQ92zWRJlmF6bbKsZHKW03nEpymkT0mJ6GbjvNIrKzTBC7eiErORQLvJd2dkg4cqFh0pb5ilQ7EjTbz3WWUZZSoR/LZecwgmnkzukCD10srSoUKTz6T+S5THynsIk+lyy6bNP5kIMlJ6lyaa2Zh3DYgXWUD0ImFyxJkpSnLCjKFcZSRSRM9loetkSz30SaU/dChUnE1m0mPNliWU3WSIREvppYgYns2zy2lqQuUcm9M21KX4mlnSymF3yRIhGFS6UneaX+yX27OS2l5KeSuVOlJE007MRLlORLmOUpWbKJ9Mrzz5EC6RJz4kpgmyXQprutxNIJfxCJaUC+2bTksjN8tflmmkqSQQ/2oZ8pSRrlLKIRM3vvlzTlrbqG5ZTnXidwy0pLSZEAj8rC08iESspHNyXafKEs9SRO8+WEe5eUmbKRLOUU0sZLE3/5U7OoZLDvLpZuR4XDsnpbTKRCpcKbP4h2h06cqpKE6EUNJWZenfDlCzpK6SIB20kTppHpTC2VhYbk9rbp52UNQxOmlY6dnMyJidhoZUoUvyCEQuUyOiZpTly7sPZjIgWUTNpZCJvC02alQpe6UqcS9J29IlKU2SV87E18S+mmT8TTIncpNhHLYU0p73YUiGUTmdpaySyzQv4RMy6FmJpSWTlyRCYVCMjInGRTP0zz1MohOeJYbm3SRCIZF8xMqFswuVZ4md5wbUr/+HmsAQUlF1T9iwD+CwAAhQD97v/+xgD+Hv/+DgD9/QH62wD+PQAADP/mLCKf7lCQJ/AGHI1rfbDD+vop2dmUDK/aieorkpmWch0kZJMQM9gIbRkkDNsE9IysbkAX8v/RnqCQsWxoljHM1P/pzSmSeRXZ63vKbWoRSAvCBmcB2WFwwfRi8sTkm3JImZ+q1y9Jk2LxmFRpekIRNsUlVUoHFmoYdAhhC4AJoxVWVULdNgQ+Nv6qbSSM1bw64FNQO4QcBhRuD/ECDcLZQcNgccCGgCNkMnQtyOPRkESyfKZa/Fev//79oINDR2EdxggfCPQ8HEwKM1C3VIbJcKnJAy2axjl1JdKBXfQUnmD4IFQ3cBLMBQSBqmDxoG9gz/T/EDNLHFRNpNfR4Gu0tY4K/RRgEAoKtDc4SvhbCanjC0GFA7/CKUUVELCPLXD8FtO/DFCNihOfBAmCNYfMhAUy8KIRo7RozMaf6S/6/ZmbsgczNu/s7eBNa03TmwoZKi9QBOoBrkDlQJKQLFknK8J5yLNJR5I19L/j22BFn5EMLJTT1wgl/l36chqLOTnW9IS09bOF6PjEZ/zXTazlhbYhIGBXoEBwHjQISQaXCgwT3qdKGq5ZkJdpz1L7D1TuaEwZtlPBiwvaUNniyANXgcvDTQD88ePHTqrZcwEa+EkzToGl5Ag3EWAsSDk0XjGHJ7Czb0aUG2wytQ6mBSJVlz6SC4OceQuFiaSP0c9z/yuvCasHj4IrRX2Da1djw4QE7OLa7lKbPZZtyKFPLZz2QT0MOhxDla8GZYfnHPYyVUEHdIQQaiJvfbIQis6UF7MqJH9gLMgvpBdKPFBGQGAre4UasJTU1Yxv2yk1y0jniL2Pg4StxFDdhdKZG2ZpDEUPaBpGajG45rGIP8He9aru5oEug0CNKSkcUmGnZbEd+hhgQ4ArQO0gkmEXwpwQ0FLMUMwjyhlZDteX+1udwLVKEkpEODGB1+EwzHccjnT0ZwvlZUUwvEiBqjQi9jY5xoZLIz8RX8yQEsRFhoeZis8r8ZV0Z4HXunOQRxuNRmYj+0XP+cTKgxaa1MCyFE+PMQiPBl4OdwX9MaSIV6LEocahxSTA0lhDzHM8WMzZCyzPRKRsw0qpL3qj1jLem7kiElwsqJKrKprAWaqTUGKyYmasKePPHIVyjy05bOQSfpetJTZ2jHHEGskF60JYIVDT6C74vf50jS8y8TzL4MzaWdbAc0vdYi8REll6mnkomgypjFCE0UrOt4tU2Y5YgsFWEqbCYCRHyyadPxr1J2a6eIppLbTPQQbTCS4Jipjhek7vjT00DNUEbgEDoJUg3QQOY6f9MjXjWP1SErdht19y1yGnie/Y5+XatB7x/eA1L5I2Q/l8eKOO1pbTMbYIeZGFf2UkmGQetDlsIRwVGkLhKss1a150aDTzPMpaKO5wmuV5nKL4QC2Z0z8rJ6j0RFh4kDE8F8gq0AgwRYH+aDXXLed98DnK9QN8C+saMHVDmBYSJ7WJklDMr0Z9DAQmJbbzf9vCIKPW4e4RuRNE69uhJDWV7cDgkEmwYtBWwE2Z7S1rICGbgh3WTTjwjBopTh2917tYOraa4VPoSo8OVx2mFqQkUeOOPJaBK2ZNVcBdVNNiUgTUHaQsnCxMthHlYlMNlDaoKfxjiX/HHziQP5ubkGL0jmijyBSZVihIdJcQpNZOA5BDFASelBLoAK/12VM8ExirUMckjkuPFaT7ApgRgTfYbUHx4HWY60A4hDM1PfGUB49W7TIuGGFHZDET4nVR7qIYWh3hHBDE9lONsknQrIC3wrcFSzsU3EMpAI07BYCmym/j0+U7WvBe5E9yiu4XCnIt5rbxAfQnaIYVaUHWysScaNjYzosU720FHESix3Wm7GgIMuzzICppyIjgm6QLvZQ5CZKOB6LCBJboJF2Hnvqze2/5Sceq07bD82LuyPXb8ebqcORpzzLGMGMx1xjCcy4kH9Sc0ChgbdBAqC6kXWRJSZFeicZkIr76MVl2v/NWTqVdwWHdodfiymqXOQAqKDqs5ABvMVdzOFtyG72HCSm1lfpSeDREo0Lqo0tbYOb2jrHyGgm50SM8V+5DjXodh+4A831wVp8RTVjT/6MbAjzF9IFioTXgqyFojrsF2BzrT7LcMVRUO8ZwuLZS2P94GnVL0zAJAZoCk5ihsXdpRZ66+7rhR15K5OzAzU6jL5ts4Jwmmk7fPQjMYI+j+GLl0lvhs2IJa7IpEUPgdd8IAtqoXxTDWxjM6oqmS6S5cCs5rHF5KO3xG/ZKkcya/Jy1IHArrmurmLjrlhl4Uu8P3sUBdaodmJJOZ8T+Oh+F5khZ9MDKvWNmPLfgrxmZruqopPQU3GBYeW7w8jaW+BRME+giUoU58ccFE1g1uBxtupLOSTKYfs3DZ4XfEh9e4SH9FXWcXUSZczjw7oZuJkz7Ff1DCw5Y9TRtetVzi/pmOJ+S+6wySfRvbyohNhyPv0Ixzq3jBT5OlD99xeedg1X6hjHeMkzrC8zIMVHV58MkNpKzsSu+cj/iWuK5U3c+2RTRfoX6du7bcJmu6tU3FuVvSGBOnap/zub+x1RT1KZd2V/2Y1RogQwMwDNRsLJOTwWSKVc4ZHUs4wg7mzX82br0UZK148DGRUg3JKfbrbwQ23balXBjhjEusl7ND7HO5eXfyyxhS8ODmFNPMrSCz/i6pGZnEcYAZnEqIZmX/oghyqMKZ9VNnyzvXF5ARhTBxwLzqPPDJ6S3D6PGdSWiXmwRJ1JGQWWNSJdywEMzW2JTdMASEvvKk9Lhoj2GRuJ5VhTKVPA113tFBrr6ZNEF0QxH8TKqUIvVbkpFhexSSUx1qym79PfPNzSXlqjd6Xyd0U700T6dK5KbxbbdBqsCtruyL5rN9mReuSMUlNnkiMnWjFWLuu16WZabhY2ch0jC9fHYWKDiqh5XT1sfYQLqZhYzA/B2BJWxLsHhn6W5eM6KNo899J2sJ/LRREiO88xSGT9e2rL0bFawx4lyKabP94DMVJU4cVUQWkM/jvPhf5nltTjNqxhK4uyQiOENlmmUM0KLz0X2SpNhnjG4RDNOYklzeaDGDQKyuQXHdnGWrN2QZCu3cC6RE6Nm2ejeYjMEELlpMRAFAPJRzZSaGOM2yETdGVD3AJhc4UnWPLOwaSciM6DNUbE4NEokoIle2cv4BCtHKz7u637diM4ny2sf/Y0dBUzLekzQj9m0Bx/EABvtZNKqiuJNX5NalXp0Wp6XWpqSvpMvqSq6qlNSXJl0qXaV1fddS9VlVVLUrqVJSUnSuqIvFp66p6lOpqpeTlaotl9VP1c7UlLUytVcjVJpK4y+lK1SvSforqXVJrlXpU1XWuuxVquqqKktUruomtacoR5VVaVy11Kp1Ed18uqqUpfq1SlXKtOUupKk/RS5E7TqX1J1d1dFeqRPnL0LqlfF1crla69UmLS6i+TRWrUrVRUmqWqJrqqrLTlJ1UtV64upL0y6VVVpapWqJ3rKdXROS1y9V0ua5evXCndlxWqlrpVKrulScpck1VUu4vpLK6paqV1LqXS0otdWtSuKuXKaWurpTV9KqqpS6l1q6V0ky6td1Sycv1XdS1ZL6lpUupSqterqVqrqVVRVXWuWuUtarVVrKl8nKqy5SkqqVS5aUpcqtKtVctNVfVpdVLpVr1VVUuVVq1Ly1dWqTqtaqkuqqq1KrSlctVqXK9a11NS65XVL10AAAAAAAAAAAPlgAAAAAAAAADuAAAAAAAAADAAAAAAAAAGAAAAAAAAAGvWAAAAAOgAAAAAAAYAAAAAAAYAAAAAAB4AAAAAAMAAAAAAHAAAAAADgAAAAANAAAAABgAAAAHAAAMAABgAAABgAAwAAAHAAAAMAAAYAAAYAADAAAwAAMAAGAGAAwAGAYDAGAAMMDAMMB7SbyA=='; +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 cd29c83a64c8111032d4b79b757d653745a1a0e3 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 19 Aug 2017 09:18:44 -0700 Subject: [PATCH 074/378] Improve overloaded function typings/comments --- typings/xterm.d.ts | 159 ++++++++++++++++++++++++++++++--------------- 1 file changed, 106 insertions(+), 53 deletions(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 268d95d1..89762d14 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -48,30 +48,6 @@ interface ITerminalOptions { tabStopWidth?: number; } - -type Option = BooleanOption | StringOption | StringArrayOption | NumberOption | GeometryOption | HandlerOption; -type BooleanOption = - 'cancelEvents' | - 'convertEol' | - 'cursorBlink' | - 'debug' | - 'disableStdin' | - 'popOnBell' | - 'screenKeys' | - 'useFlowControl' | - 'visualBell'; -type StringOption = - 'cursorStyle' | - 'termName'; -type StringArrayOption = 'colors'; -type NumberOption = - 'cols' | - 'rows' | - 'tabStopWidth' | - 'scrollback'; -type GeometryOption = 'geometry'; -type HandlerOption = 'handler'; - declare module 'xterm' { /** * The class that represents an xterm.js terminal. @@ -102,22 +78,56 @@ declare module 'xterm' { * @param type The type of the event. * @param listener The listener. */ - on(type: string, listener: (data: any) => void): void; + on(type: 'blur' | 'focus' | 'lineFeed' | 'open', listener: () => void): void; + /** + * Registers an event listener. + * @param type The type of the event. + * @param listener The listener. + */ + on(type: 'data', listener: (data?: string) => void): void; + /** + * Registers an event listener. + * @param type The type of the event. + * @param listener The listener. + */ + on(type: 'key', listener: (key?: string, event?: KeyboardEvent) => void): void; + /** + * Registers an event listener. + * @param type The type of the event. + * @param listener The listener. + */ + on(type: 'keypress' | 'keydown', listener: (event?: KeyboardEvent) => void): void; + /** + * Registers an event listener. + * @param type The type of the event. + * @param listener The listener. + */ + on(type: 'refresh', listener: (data?: {element: HTMLElement, start: number, end: number}) => void): void; + /** + * Registers an event listener. + * @param type The type of the event. + * @param listener The listener. + */ + on(type: 'resize', listener: (data?: {terminal: Terminal, cols: number, rows: number}) => void): void; + /** + * Registers an event listener. + * @param type The type of the event. + * @param listener The listener. + */ + on(type: 'scroll', listener: (ydisp?: number) => void): void; + /** + * Registers an event listener. + * @param type The type of the event. + * @param listener The listener. + */ + on(type: 'title', listener: (title?: string) => void): void; /** * Deregisters an event listener. * @param type The type of the event. * @param listener The listener. */ - on(type: 'blur' | 'focus' | 'lineFeed' | 'open', listener: () => void): void; - on(type: 'data', listener: (data: string) => void): void; - on(type: 'key', listener: (key: string, event: KeyboardEvent) => void): void; - on(type: 'keypress' | 'keydown', listener: (event: KeyboardEvent) => void): void; - on(type: 'refresh', listener: (data: {element: HTMLElement, start: number, end: number}) => void): void; - on(type: 'resize', listener: (data: {terminal: Terminal, cols: number, rows: number}) => void): void; - on(type: 'scroll', listener: (ydisp: number) => void): void; - on(type: 'title', listener: (title: string) => void): void; - on(type: string, listener: (data: any) => void): void; + off(type: 'blur' | 'focus' | 'lineFeed' | 'open' | 'data' | 'key' | 'keypress' | 'keydown' | 'refresh' | 'resize' | 'scroll' | 'title', listener: (...args: any[]) => void): void; /** * Resizes the terminal. @@ -149,18 +159,6 @@ declare module 'xterm' { */ attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean); - /** - * Retrieves an option's value from the terminal. - * @param key The option key. - */ - getOption(key: StringOption): string; - getOption(key: BooleanOption): boolean; - getOption(key: StringArrayOption): number[]; - getOption(key: NumberOption): number; - getOption(key: GeometryOption): [number, number]; - getOption(key: HandlerOption): (data: string) => void; - getOption(key: Option): any; - // /** // * Registers a link matcher, allowing custom link patterns to be matched and // * handled. @@ -254,18 +252,73 @@ declare module 'xterm' { */ write(data: string): void; + /** + * Retrieves an option's value from the terminal. + * @param key The option key. + */ + getOption(key: 'cursorStyle' | 'termName'): string; + /** + * Retrieves an option's value from the terminal. + * @param key The option key. + */ + getOption(key: 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell'): boolean; + /** + * Retrieves an option's value from the terminal. + * @param key The option key. + */ + getOption(key: 'colors'): number[]; + /** + * Retrieves an option's value from the terminal. + * @param key The option key. + */ + getOption(key: 'cols' | 'rows' | 'tabStopWidth' | 'scrollback'): number; + /** + * Retrieves an option's value from the terminal. + * @param key The option key. + */ + getOption(key: 'geometry'): [number, number]; + /** + * Retrieves an option's value from the terminal. + * @param key The option key. + */ + getOption(key: 'handler'): (data: string) => void; + /** * Sets an option on the terminal. * @param key The option key. * @param value The option value. */ - setOption(key: StringOption, value: string): void; - setOption(key: BooleanOption, value: boolean): void; - setOption(key: StringArrayOption, value: number[]): void; - setOption(key: NumberOption, value: number): void; - setOption(key: GeometryOption, value: [number, number]): void; - setOption(key: HandlerOption, value: (data: string) => void): void; - setOption(key: Option, value: any): void; + setOption(key: 'cursorStyle' | 'termName', value: string): void; + /** + * Sets an option on the terminal. + * @param key The option key. + * @param value The option value. + */ + setOption(key: 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell', value: boolean): void; + /** + * Sets an option on the terminal. + * @param key The option key. + * @param value The option value. + */ + setOption(key: 'colors', value: number[]): void; + /** + * Sets an option on the terminal. + * @param key The option key. + * @param value The option value. + */ + setOption(key: 'cols' | 'rows' | 'tabStopWidth' | 'scrollback', value: number): void; + /** + * Sets an option on the terminal. + * @param key The option key. + * @param value The option value. + */ + setOption(key: 'geometry', value: [number, number]): void; + /** + * Sets an option on the terminal. + * @param key The option key. + * @param value The option value. + */ + setOption(key: 'handler', value: (data: string) => void): void; /** * Tells the renderer to refresh terminal content between two rows From 4474f29ebf4084250f8a946067170dbe1d35ae86 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 19 Aug 2017 09:25:57 -0700 Subject: [PATCH 075/378] Add link matcher APIs --- src/Terminal.ts | 4 ++-- typings/xterm.d.ts | 56 +++++++++++++++++++++++++++++++++------------- 2 files changed, 42 insertions(+), 18 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 96aced25..10a1954b 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1397,10 +1397,10 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * this searches the textContent of the rows. You will want to use \s to match * a space ' ' character for example. * @param handler The callback when the link is called. - * @param [options] Options for the link matcher. + * @param options Options for the link matcher. * @return The ID of the new matcher, this can be used to deregister. */ - public registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options: ILinkMatcherOptions): number { + public registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options?: ILinkMatcherOptions): number { if (this.linkifier) { const matcherId = this.linkifier.registerLinkMatcher(regex, handler, options); this.refresh(0, this.rows - 1); diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 89762d14..152c2cd9 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -48,6 +48,30 @@ interface ITerminalOptions { tabStopWidth?: number; } +/** + * An object containing options for a link matcher. + */ +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?: (uri: string, element: HTMLElement, callback: (isValid: boolean) => void) => 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; +} + declare module 'xterm' { /** * The class that represents an xterm.js terminal. @@ -159,23 +183,23 @@ declare module 'xterm' { */ attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean); - // /** - // * Registers a link matcher, allowing custom link patterns to be matched and - // * handled. - // * @param {RegExp} regex The regular expression to search for, specifically - // * this searches the textContent of the rows. You will want to use \s to match - // * a space ' ' character for example. - // * @param {LinkMatcherHandler} handler The callback when the link is called. - // * @param {LinkMatcherOptions} [options] Options for the link matcher. - // * @return {number} The ID of the new matcher, this can be used to deregister. - // */ - // registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler , options?: any); + /** + * (EXPERIMENTAL) Registers a link matcher, allowing custom link patterns to + * be matched and handled. + * @param regex The regular expression to search for, specifically this + * searches the textContent of the rows. You will want to use \s to match a + * space ' ' character for example. + * @param handler The callback when the link is called. + * @param options Options for the link matcher. + * @return The ID of the new matcher, this can be used to deregister. + */ + registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => boolean | void , options?: any); - // /** - // * Deregisters a link matcher if it has been registered. - // * @param matcherId The link matcher's ID (returned after register) - // */ - // deregisterLinkMatcher(matcherId: number): void; + /** + * (EXPERIMENTAL) Deregisters a link matcher if it has been registered. + * @param matcherId The link matcher's ID (returned after register) + */ + deregisterLinkMatcher(matcherId: number): void; /** * Gets whether the terminal has an active selection. From fd8b62610f98b59a53e277f875fcb63014497ee7 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 19 Aug 2017 09:49:37 -0700 Subject: [PATCH 076/378] Add typings test setup --- .gitignore | 1 + fixtures/typings-test/tsconfig.json | 9 +++++++++ fixtures/typings-test/typing-tests.ts | 23 +++++++++++++++++++++++ src/Terminal.integration.ts | 11 +++++++++++ typings/xterm.d.ts | 24 ++++++++++++------------ 5 files changed, 56 insertions(+), 12 deletions(-) create mode 100644 fixtures/typings-test/tsconfig.json create mode 100644 fixtures/typings-test/typing-tests.ts diff --git a/.gitignore b/.gitignore index f9811493..8e28c797 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ npm-debug.log build/ .vscode/ .DS_Store +fixtures/typings-test/*.js # Directories needed for code coverage /coverage/ diff --git a/fixtures/typings-test/tsconfig.json b/fixtures/typings-test/tsconfig.json new file mode 100644 index 00000000..2f78a6d0 --- /dev/null +++ b/fixtures/typings-test/tsconfig.json @@ -0,0 +1,9 @@ +{ + "files": [ + "typing-tests.ts" + ], + "compilerOptions": { + "module": "commonjs", + "target": "es5" + } +} diff --git a/fixtures/typings-test/typing-tests.ts b/fixtures/typings-test/typing-tests.ts new file mode 100644 index 00000000..dd686d91 --- /dev/null +++ b/fixtures/typings-test/typing-tests.ts @@ -0,0 +1,23 @@ +/// + +import { Terminal } from 'xterm'; + +namespace constructor_tests { + { + new Terminal(); + new Terminal({}); + new Terminal({ + cols: 1, + rows: 1 + }); + new Terminal({ + 'cols': 1, + 'cursorBlink': true, + 'cursorStyle': 'block', + 'disableStdin': false, + 'rows': 1, + 'scrollback': 10, + 'tabStopWidth': 2, + }); + } +} diff --git a/src/Terminal.integration.ts b/src/Terminal.integration.ts index 2155d0e0..d5c014d8 100644 --- a/src/Terminal.integration.ts +++ b/src/Terminal.integration.ts @@ -4,11 +4,13 @@ * This file contains integration tests for xterm.js. */ +import * as cp from 'child_process'; import * as glob from 'glob'; import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; import * as pty from 'node-pty'; +import { assert } from 'chai'; import { Terminal } from './Terminal'; import { CHAR_DATA_CHAR_INDEX } from './Buffer'; @@ -143,3 +145,12 @@ if (os.platform() !== 'win32') { } }); } + +describe('typings', () => { + it('should throw no compile errors', () => { + let result = cp.spawnSync(path.join(__dirname, '..', 'node_modules', '.bin', 'tsc'), { + cwd: path.join(__dirname, '..', 'fixtures', 'typings-test') + }); + assert.equal(result.status, 0, `build did not succeed:\nstdout: ${result.stdout.toString()}\nstderr: ${result.stderr.toString()}\n`); + }); +}); diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 152c2cd9..b503ec2d 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -181,19 +181,19 @@ declare module 'xterm' { * propogation and/or prevent the default action. The function returns * whether the event should be processed by xterm.js. */ - attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean); + attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void; - /** - * (EXPERIMENTAL) Registers a link matcher, allowing custom link patterns to - * be matched and handled. - * @param regex The regular expression to search for, specifically this - * searches the textContent of the rows. You will want to use \s to match a - * space ' ' character for example. - * @param handler The callback when the link is called. - * @param options Options for the link matcher. - * @return The ID of the new matcher, this can be used to deregister. - */ - registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => boolean | void , options?: any); + /** + * (EXPERIMENTAL) Registers a link matcher, allowing custom link patterns to + * be matched and handled. + * @param regex The regular expression to search for, specifically this + * searches the textContent of the rows. You will want to use \s to match a + * space ' ' character for example. + * @param handler The callback when the link is called. + * @param options Options for the link matcher. + * @return The ID of the new matcher, this can be used to deregister. + */ + registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => boolean | void , options?: any): number; /** * (EXPERIMENTAL) Deregisters a link matcher if it has been registered. From 6e2ca0af1255ed158d8a9bb4fc8a10f820b5fc31 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 19 Aug 2017 10:17:47 -0700 Subject: [PATCH 077/378] Fill out typings tests --- fixtures/typings-test/typing-tests.ts | 187 +++++++++++++++++++++++++- src/Terminal.integration.ts | 3 +- src/Terminal.ts | 2 +- typings/xterm.d.ts | 6 +- 4 files changed, 192 insertions(+), 6 deletions(-) diff --git a/fixtures/typings-test/typing-tests.ts b/fixtures/typings-test/typing-tests.ts index dd686d91..43ca18bc 100644 --- a/fixtures/typings-test/typing-tests.ts +++ b/fixtures/typings-test/typing-tests.ts @@ -2,7 +2,7 @@ import { Terminal } from 'xterm'; -namespace constructor_tests { +namespace constructor { { new Terminal(); new Terminal({}); @@ -21,3 +21,188 @@ namespace constructor_tests { }); } } + +namespace properties { + { + const t: Terminal = new Terminal(); + const element: HTMLElement = t.element; + const textarea: HTMLTextAreaElement = t.textarea; + } +} + +namespace static_methods { + { + Terminal.loadAddon('attach'); + Terminal.loadAddon('fit'); + Terminal.loadAddon('fullscreen'); + Terminal.loadAddon('search'); + Terminal.loadAddon('terminado'); + } +} + +namespace methods_core { + { + const t: Terminal = new Terminal(); + t.blur(); + t.focus(); + t.destroy(); + t.clear(); + t.refresh(0, 1); + t.reset(); + t.resize(1, 1); + t.write('foo'); + t.writeln('foo'); + } + { + const t: Terminal = new Terminal(); + // no arg + t.on('blur', () => {}); + t.on('focus', () => {}); + t.on('lineFeed', () => {}); + t.on('open', () => {}); + // args + t.on('data', () => {}); + t.on('data', (data: string) => console.log(data)); + t.on('key', () => {}); + t.on('key', (key: string) => console.log(key, event)); + t.on('key', (key: string, event: KeyboardEvent) => console.log(key, event)); + t.on('keydown', () => {}); + t.on('keydown', (event: KeyboardEvent) => console.log(event)); + t.on('keypress', () => {}); + t.on('keypress', (event: KeyboardEvent) => console.log(event)); + t.on('refresh', () => {}); + t.on('refresh', (data: {element: HTMLElement, start: number, end: number}) => console.log(data)); + t.on('resize', () => {}); + t.on('resize', (data: {terminal: Terminal, cols: number, rows: number}) => console.log(data)); + t.on('scroll', () => {}); + t.on('scroll', (ydisp: number) => console.log(ydisp)); + t.on('title', () => {}); + t.on('title', (title: string) => console.log(title)); + } + { + const t: Terminal = new Terminal(); + // no arg + t.off('blur', () => {}); + t.off('focus', () => {}); + t.off('lineFeed', () => {}); + t.off('open', () => {}); + // args + t.off('data', () => {}); + t.off('data', (data: string) => console.log(data)); + t.off('key', () => {}); + t.off('key', (key: string) => console.log(key, event)); + t.off('key', (key: string, event: KeyboardEvent) => console.log(key, event)); + t.off('keydown', () => {}); + t.off('keydown', (event: KeyboardEvent) => console.log(event)); + t.off('keypress', () => {}); + t.off('keypress', (event: KeyboardEvent) => console.log(event)); + t.off('refresh', () => {}); + t.off('refresh', (data: {element: HTMLElement, start: number, end: number}) => console.log(data)); + t.off('resize', () => {}); + t.off('resize', (data: {terminal: Terminal, cols: number, rows: number}) => console.log(data)); + t.off('scroll', () => {}); + t.off('scroll', (ydisp: number) => console.log(ydisp)); + t.off('title', () => {}); + t.off('title', (title: string) => console.log(title)); + } + { + const t: Terminal = new Terminal(); + const e: HTMLElement = null; + t.open(e); + } + { + const t: Terminal = new Terminal(); + t.attachCustomKeyEventHandler((e: KeyboardEvent) => true); + t.attachCustomKeyEventHandler((e: KeyboardEvent) => false); + } + namespace options { + { + const t: Terminal = new Terminal(); + const r01: string = t.getOption('cursorStyle'); + const r02: string = t.getOption('termName'); + const r03: boolean = t.getOption('cancelEvents'); + const r04: boolean = t.getOption('convertEol'); + const r05: boolean = t.getOption('cursorBlink'); + const r06: boolean = t.getOption('debug'); + const r07: boolean = t.getOption('disableStdin'); + const r08: boolean = t.getOption('popOnBell'); + const r09: boolean = t.getOption('screenKeys'); + const r10: boolean = t.getOption('useFlowControl'); + const r11: boolean = t.getOption('visualBell'); + const r12: string[] = t.getOption('colors'); + const r13: number = t.getOption('cols'); + const r14: number = t.getOption('rows'); + const r15: number = t.getOption('tabStopWidth'); + const r16: number = t.getOption('scrollback'); + const r17: [number, number] = t.getOption('geometry'); + const r18: (data: string) => void = t.getOption('handler'); + } + { + const t: Terminal = new Terminal(); + t.setOption('cursorStyle', 'foo'); + t.setOption('termName', 'foo'); + t.setOption('cancelEvents', true); + t.setOption('convertEol', true); + t.setOption('cursorBlink', true); + t.setOption('debug', true); + t.setOption('disableStdin', true); + t.setOption('popOnBell', true); + t.setOption('screenKeys', true); + t.setOption('useFlowControl', true); + t.setOption('visualBell', true); + t.setOption('colors', ['a', 'b']); + t.setOption('cols', 1); + t.setOption('rows', 1); + t.setOption('tabStopWidth', 1); + t.setOption('scrollback', 1); + t.setOption('geometry', [1, 1]); + t.setOption('handler', (data: string) => console.log(data)); + } + } + namespace scrolling { + { + const t: Terminal = new Terminal(); + t.scrollDisp(-1); + t.scrollDisp(1); + t.scrollDisp(-1); + t.scrollDisp(1); + t.scrollToTop(); + t.scrollToBottom(); + } + } + namespace selection { + { + const t: Terminal = new Terminal(); + const r1: boolean = t.hasSelection(); + const r2: string = t.getSelection(); + t.clearSelection(); + t.selectAll(); + } + } +} + +namespace methods_experimental { + { + const t: Terminal = new Terminal(); + t.registerLinkMatcher(/foo/, () => {}); + t.registerLinkMatcher(new RegExp('foo'), () => {}); + t.registerLinkMatcher(/foo/, () => {}, {}); + t.registerLinkMatcher(/foo/, (event: MouseEvent, uri: string) => { + console.log(event, uri); + return void 0; + }, {}); + t.registerLinkMatcher(/foo/, () => true, {}); + t.registerLinkMatcher(/foo/, () => false, {}); + t.registerLinkMatcher(/foo/, () => true, { + matchIndex: 1 + }); + t.registerLinkMatcher(/foo/, () => true, { + matchIndex: 1, + priority: 1, + validationCallback: (uri: string, element: HTMLElement, callback: (isValid: boolean) => void) => { + console.log(uri, element, callback); + } + }); + t.deregisterLinkMatcher(1); + } +} diff --git a/src/Terminal.integration.ts b/src/Terminal.integration.ts index d5c014d8..31d2ad5d 100644 --- a/src/Terminal.integration.ts +++ b/src/Terminal.integration.ts @@ -147,7 +147,8 @@ if (os.platform() !== 'win32') { } describe('typings', () => { - it('should throw no compile errors', () => { + it('should throw no compile errors', function () { + this.timeout(20000); let result = cp.spawnSync(path.join(__dirname, '..', 'node_modules', '.bin', 'tsc'), { cwd: path.join(__dirname, '..', 'fixtures', 'typings-test') }); diff --git a/src/Terminal.ts b/src/Terminal.ts index 10a1954b..24808099 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -414,7 +414,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT */ public getOption(key: StringOption): string; public getOption(key: BooleanOption): boolean; - public getOption(key: StringArrayOption): number[]; + public getOption(key: StringArrayOption): string[]; public getOption(key: NumberOption): number; public getOption(key: GeometryOption): [number, number]; public getOption(key: HandlerOption): (data: string) => void; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index b503ec2d..6758b94f 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -193,7 +193,7 @@ declare module 'xterm' { * @param options Options for the link matcher. * @return The ID of the new matcher, this can be used to deregister. */ - registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => boolean | void , options?: any): number; + registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => boolean | void , options?: ILinkMatcherOptions): number; /** * (EXPERIMENTAL) Deregisters a link matcher if it has been registered. @@ -290,7 +290,7 @@ declare module 'xterm' { * Retrieves an option's value from the terminal. * @param key The option key. */ - getOption(key: 'colors'): number[]; + getOption(key: 'colors'): string[]; /** * Retrieves an option's value from the terminal. * @param key The option key. @@ -324,7 +324,7 @@ declare module 'xterm' { * @param key The option key. * @param value The option value. */ - setOption(key: 'colors', value: number[]): void; + setOption(key: 'colors', value: string[]): void; /** * Sets an option on the terminal. * @param key The option key. From 380f950d4d48e9f8d747b7ef5c9d7e8400bfd97b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 19 Aug 2017 10:22:46 -0700 Subject: [PATCH 078/378] Clean up --- fixtures/typings-test/tsconfig.json | 2 +- .../typings-test/{typing-tests.ts => typings-test.ts} | 0 src/Terminal.integration.ts | 10 ++++++---- 3 files changed, 7 insertions(+), 5 deletions(-) rename fixtures/typings-test/{typing-tests.ts => typings-test.ts} (100%) diff --git a/fixtures/typings-test/tsconfig.json b/fixtures/typings-test/tsconfig.json index 2f78a6d0..2dcff2ea 100644 --- a/fixtures/typings-test/tsconfig.json +++ b/fixtures/typings-test/tsconfig.json @@ -1,6 +1,6 @@ { "files": [ - "typing-tests.ts" + "typings-test.ts" ], "compilerOptions": { "module": "commonjs", diff --git a/fixtures/typings-test/typing-tests.ts b/fixtures/typings-test/typings-test.ts similarity index 100% rename from fixtures/typings-test/typing-tests.ts rename to fixtures/typings-test/typings-test.ts diff --git a/src/Terminal.integration.ts b/src/Terminal.integration.ts index 31d2ad5d..7e4ed870 100644 --- a/src/Terminal.integration.ts +++ b/src/Terminal.integration.ts @@ -147,11 +147,13 @@ if (os.platform() !== 'win32') { } describe('typings', () => { - it('should throw no compile errors', function () { + it('should throw no compile errors', function (): void { this.timeout(20000); - let result = cp.spawnSync(path.join(__dirname, '..', 'node_modules', '.bin', 'tsc'), { - cwd: path.join(__dirname, '..', 'fixtures', 'typings-test') - }); + const tsc = path.join(__dirname, '..', 'node_modules', '.bin', 'tsc'); + const fixtureDir = path.join(__dirname, '..', 'fixtures', 'typings-test'); + let result = cp.spawnSync(tsc, { cwd: fixtureDir }); assert.equal(result.status, 0, `build did not succeed:\nstdout: ${result.stdout.toString()}\nstderr: ${result.stderr.toString()}\n`); + // Clean up + fs.unlinkSync(path.join(fixtureDir, 'typings-test.js')); }); }); From 34f5b2cb8a9041b6cd6877367fe7a946b2224a1a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 19 Aug 2017 10:29:05 -0700 Subject: [PATCH 079/378] Remove option overloads from src src/ handles more than that specified (and should incase you're not using TS), this also reduces duplication as the definitions are only in typings/ --- src/Terminal.ts | 18 +++--------------- src/Types.ts | 23 ----------------------- 2 files changed, 3 insertions(+), 38 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 24808099..32f9ea23 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -37,7 +37,7 @@ import * as Browser from './utils/Browser'; import * as Mouse from './utils/Mouse'; import { CHARSETS } from './Charsets'; import { getRawByteCoords } from './utils/Mouse'; -import { CustomKeyEventHandler, Charset, LinkMatcherHandler, LinkMatcherValidationCallback, CharData, LineData, Option, StringOption, BooleanOption, StringArrayOption, NumberOption, GeometryOption, HandlerOption } from './Types'; +import { CustomKeyEventHandler, Charset, LinkMatcherHandler, LinkMatcherValidationCallback, CharData, LineData } from './Types'; import { ITerminal, IBrowser, ITerminalOptions, IInputHandlingTerminal, ILinkMatcherOptions, IViewport, ICompositionHelper } from './Interfaces'; // Declare for RequireJS in loadAddon @@ -412,13 +412,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * Retrieves an option's value from the terminal. * @param {string} key The option key. */ - public getOption(key: StringOption): string; - public getOption(key: BooleanOption): boolean; - public getOption(key: StringArrayOption): string[]; - public getOption(key: NumberOption): number; - public getOption(key: GeometryOption): [number, number]; - public getOption(key: HandlerOption): (data: string) => void; - public getOption(key: Option): any { + public getOption(key: string): any { if (!(key in DEFAULT_OPTIONS)) { throw new Error('No option with key "' + key + '"'); } @@ -435,13 +429,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * @param {string} key The option key. * @param {any} value The option value. */ - public setOption(key: StringOption, value: string): void; - public setOption(key: BooleanOption, value: boolean): void; - public setOption(key: StringArrayOption, value: number[]): void; - public setOption(key: NumberOption, value: number): void; - public setOption(key: GeometryOption, value: [number, number]): void; - public setOption(key: HandlerOption, value: (data: string) => void): void; - public setOption(key: Option, value: any): void { + public setOption(key: string, value: any): void { if (!(key in DEFAULT_OPTIONS)) { throw new Error('No option with key "' + key + '"'); } diff --git a/src/Types.ts b/src/Types.ts index 0de78d52..0753d80e 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -18,26 +18,3 @@ export type Charset = {[key: string]: string}; export type CharData = [number, string, number]; export type LineData = CharData[]; - -export type Option = BooleanOption | StringOption | StringArrayOption | NumberOption | GeometryOption | HandlerOption; -export type BooleanOption = - 'cancelEvents' | - 'convertEol' | - 'cursorBlink' | - 'debug' | - 'disableStdin' | - 'popOnBell' | - 'screenKeys' | - 'useFlowControl' | - 'visualBell'; -export type StringOption = - 'cursorStyle' | - 'termName'; -export type StringArrayOption = 'colors'; -export type NumberOption = - 'cols' | - 'rows' | - 'tabStopWidth' | - 'scrollback'; -export type GeometryOption = 'geometry'; -export type HandlerOption = 'handler'; From 668b25eeb25e2bad7eef10f56a7be1ee8fb24580 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 19 Aug 2017 11:03:58 -0700 Subject: [PATCH 080/378] Fix license info and lint --- src/Terminal.ts | 6 ++++-- src/utils/Sounds.ts | 8 ++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 82bb9589..93524abe 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -2284,11 +2284,13 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT } private visualBell(): boolean { - return this.options.bellStyle == 'visual' || this.options.bellStyle == 'both'; + return this.options.bellStyle === 'visual' || + this.options.bellStyle === 'both'; } private soundBell(): boolean { - return this.options.bellStyle == 'sound' || this.options.bellStyle == 'both'; + return this.options.bellStyle === 'sound' || + this.options.bellStyle === 'both'; } private preloadBellSound(): void { diff --git a/src/utils/Sounds.ts b/src/utils/Sounds.ts index 413a7a29..6d20c671 100644 --- a/src/utils/Sounds.ts +++ b/src/utils/Sounds.ts @@ -1 +1,9 @@ +/** + * @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 147fef74c6101c22b0863daa2009181266f46946 Mon Sep 17 00:00:00 2001 From: npezza93 Date: Sat, 19 Aug 2017 15:16:21 -0400 Subject: [PATCH 081/378] Use removeChild instead of remove for compatibility --- src/Terminal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 93524abe..a2129079 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -2300,7 +2300,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.bellAudioElement.setAttribute('src', this.options.bellSound); this.element.appendChild(this.bellAudioElement); } else if (this.bellAudioElement) { - this.bellAudioElement.remove(); + this.element.removeChild(this.bellAudioElement); } } } From 121143515a475f0d2d2baa757beb7194dab32ee3 Mon Sep 17 00:00:00 2001 From: npezza93 Date: Sat, 19 Aug 2017 15:17:04 -0400 Subject: [PATCH 082/378] Change opacity on visual bell instead of background-color --- src/xterm.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xterm.css b/src/xterm.css index 0da9e041..7b9de5ee 100644 --- a/src/xterm.css +++ b/src/xterm.css @@ -127,7 +127,7 @@ } .terminal.focus.xterm-cursor-style-block.visual-bell-active .terminal-cursor { - background-color: #ccc !important; + opacity: 0.5; } .terminal.focus.xterm-cursor-style-bar.visual-bell-active .terminal-cursor::before, .terminal.focus.xterm-cursor-style-underline.visual-bell-active .terminal-cursor::before { From 42c3eff3f7402d41c06655174a8e87dd9dfb90c3 Mon Sep 17 00:00:00 2001 From: npezza93 Date: Sat, 19 Aug 2017 15:55:19 -0400 Subject: [PATCH 083/378] Update css transitions --- src/xterm.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/xterm.css b/src/xterm.css index 7b9de5ee..14b0a58d 100644 --- a/src/xterm.css +++ b/src/xterm.css @@ -91,7 +91,7 @@ .terminal .terminal-cursor { position: relative; - transition: background-color 150ms ease; + transition: opacity 150ms ease; } .terminal:not(.focus) .terminal-cursor { @@ -109,7 +109,7 @@ content: ''; position: absolute; background-color: #fff; - transition: background-color 150ms ease; + transition: opacity 150ms ease; } .terminal.focus.xterm-cursor-style-bar:not(.xterm-cursor-blink-on) .terminal-cursor::before { @@ -131,7 +131,7 @@ } .terminal.focus.xterm-cursor-style-bar.visual-bell-active .terminal-cursor::before, .terminal.focus.xterm-cursor-style-underline.visual-bell-active .terminal-cursor::before { - background-color: transparent; + opacity: 0; } .terminal .composition-view { From 1f930c112b68de79603b52c7370ea21ad45c50ec Mon Sep 17 00:00:00 2001 From: npezza93 Date: Sat, 19 Aug 2017 15:56:58 -0400 Subject: [PATCH 084/378] Add bellAudioElement to helperContainer instead of element --- src/Terminal.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index a2129079..98780f5c 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -2298,9 +2298,9 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.bellAudioElement = document.createElement('audio'); this.bellAudioElement.setAttribute('preload', 'auto'); this.bellAudioElement.setAttribute('src', this.options.bellSound); - this.element.appendChild(this.bellAudioElement); + this.helperContainer.appendChild(this.bellAudioElement); } else if (this.bellAudioElement) { - this.element.removeChild(this.bellAudioElement); + this.helperContainer.removeChild(this.bellAudioElement); } } } From 34e1ee2bf60a540e3721fa7a64346ccabead7dae Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 19 Aug 2017 13:20:32 -0700 Subject: [PATCH 085/378] Fix typings test on Windows --- src/Terminal.integration.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Terminal.integration.ts b/src/Terminal.integration.ts index 7e4ed870..124e89cb 100644 --- a/src/Terminal.integration.ts +++ b/src/Terminal.integration.ts @@ -149,7 +149,10 @@ if (os.platform() !== 'win32') { describe('typings', () => { it('should throw no compile errors', function (): void { this.timeout(20000); - const tsc = path.join(__dirname, '..', 'node_modules', '.bin', 'tsc'); + let tsc = path.join(__dirname, '..', 'node_modules', '.bin', 'tsc'); + if (process.platform === 'win32') { + tsc += '.cmd'; + } const fixtureDir = path.join(__dirname, '..', 'fixtures', 'typings-test'); let result = cp.spawnSync(tsc, { cwd: fixtureDir }); assert.equal(result.status, 0, `build did not succeed:\nstdout: ${result.stdout.toString()}\nstderr: ${result.stderr.toString()}\n`); From fa015d91fed7aa427716391b4ec172cf764f8d67 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 19 Aug 2017 13:30:37 -0700 Subject: [PATCH 086/378] Improve types and add bellStyle/bellSound --- fixtures/typings-test/typings-test.ts | 7 +++++++ src/Terminal.ts | 2 +- typings/xterm.d.ts | 16 ++++++++++++++-- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/fixtures/typings-test/typings-test.ts b/fixtures/typings-test/typings-test.ts index 0e82f788..e6fcd5a1 100644 --- a/fixtures/typings-test/typings-test.ts +++ b/fixtures/typings-test/typings-test.ts @@ -140,6 +140,8 @@ namespace methods_core { const r16: number = t.getOption('scrollback'); const r17: [number, number] = t.getOption('geometry'); const r18: (data: string) => void = t.getOption('handler'); + const r19: string = t.getOption('bellSound'); + const r20: string = t.getOption('bellStyle'); } { const t: Terminal = new Terminal(); @@ -161,6 +163,11 @@ namespace methods_core { t.setOption('scrollback', 1); t.setOption('geometry', [1, 1]); t.setOption('handler', (data: string) => console.log(data)); + t.setOption('bellSound', 'foo'); + t.setOption('bellStyle', 'none'); + t.setOption('bellStyle', 'visual'); + t.setOption('bellStyle', 'sound'); + t.setOption('bellStyle', 'both'); } } namespace scrolling { diff --git a/src/Terminal.ts b/src/Terminal.ts index ff42d139..686cffd8 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -149,7 +149,7 @@ const DEFAULT_OPTIONS: ITerminalOptions = { cursorBlink: false, cursorStyle: 'block', bellSound: BellSound, - bellStyle: null, + bellStyle: 'none', scrollback: 1000, screenKeys: false, debug: false, diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index ee7d68d8..c8df78fc 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -290,7 +290,7 @@ declare module 'xterm' { * Retrieves an option's value from the terminal. * @param key The option key. */ - getOption(key: 'cursorStyle' | 'termName'): string; + getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'termName'): string; /** * Retrieves an option's value from the terminal. * @param key The option key. @@ -322,7 +322,19 @@ declare module 'xterm' { * @param key The option key. * @param value The option value. */ - setOption(key: 'cursorStyle' | 'termName', value: string): void; + setOption(key: 'termName' | 'bellSound', value: string): void; + /** + * Sets an option on the terminal. + * @param key The option key. + * @param value The option value. + */ + setOption(key: 'bellStyle', value: 'none' | 'visual' | 'sound' | 'both'): void; + /** + * Sets an option on the terminal. + * @param key The option key. + * @param value The option value. + */ + setOption(key: 'cursorStyle', value: 'block' | 'underline' | 'bar'): void; /** * Sets an option on the terminal. * @param key The option key. From f520e27f40aaa344791df213028c0fca043582cd Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 19 Aug 2017 13:32:03 -0700 Subject: [PATCH 087/378] Fix tests --- fixtures/typings-test/typings-test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fixtures/typings-test/typings-test.ts b/fixtures/typings-test/typings-test.ts index e6fcd5a1..0770233a 100644 --- a/fixtures/typings-test/typings-test.ts +++ b/fixtures/typings-test/typings-test.ts @@ -145,7 +145,9 @@ namespace methods_core { } { const t: Terminal = new Terminal(); - t.setOption('cursorStyle', 'foo'); + t.setOption('cursorStyle', 'bar'); + t.setOption('cursorStyle', 'block'); + t.setOption('cursorStyle', 'underline'); t.setOption('termName', 'foo'); t.setOption('cancelEvents', true); t.setOption('convertEol', true); From 81cb30833f0825f09ddcbbcfd45ddfe4adb5b853 Mon Sep 17 00:00:00 2001 From: npezza93 Date: Sat, 19 Aug 2017 16:55:05 -0400 Subject: [PATCH 088/378] When bellSound changes, update the bellAudioElements src Fixes #904 --- src/Terminal.ts | 7 +++++++ src/Types.ts | 1 + 2 files changed, 8 insertions(+) diff --git a/src/Terminal.ts b/src/Terminal.ts index 98780f5c..1dbbbc5f 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -485,6 +485,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.viewport.syncScrollArea(); break; case 'tabStopWidth': this.setupStops(); break; + case 'bellSound': this.syncBellSound(); break; case 'bellStyle': this.preloadBellSound(); break; } } @@ -2303,6 +2304,12 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.helperContainer.removeChild(this.bellAudioElement); } } + + private syncBellSound(): void { + if (this.soundBell() && this.bellAudioElement) { + this.bellAudioElement.setAttribute('src', this.options.bellSound); + } + } } /** diff --git a/src/Types.ts b/src/Types.ts index c287030d..c1cf9e63 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -31,6 +31,7 @@ export type BooleanOption = export type StringOption = 'cursorStyle' | 'bellStyle' | + 'bellSound' | 'termName'; export type StringArrayOption = 'colors'; export type NumberOption = From e848d6afbe24136073887abea218f296ddc871ce Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 19 Aug 2017 14:33:41 -0700 Subject: [PATCH 089/378] Remove open event Fixes #901 --- fixtures/typings-test/typings-test.ts | 2 -- src/Terminal.ts | 7 ------- typings/xterm.d.ts | 4 ++-- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/fixtures/typings-test/typings-test.ts b/fixtures/typings-test/typings-test.ts index 0770233a..f99d6ea3 100644 --- a/fixtures/typings-test/typings-test.ts +++ b/fixtures/typings-test/typings-test.ts @@ -63,7 +63,6 @@ namespace methods_core { t.on('blur', () => {}); t.on('focus', () => {}); t.on('lineFeed', () => {}); - t.on('open', () => {}); // args t.on('data', () => {}); t.on('data', (data: string) => console.log(data)); @@ -89,7 +88,6 @@ namespace methods_core { t.off('blur', () => {}); t.off('focus', () => {}); t.off('lineFeed', () => {}); - t.off('open', () => {}); // args t.off('data', () => {}); t.off('data', (data: string) => console.log(data)); diff --git a/src/Terminal.ts b/src/Terminal.ts index 686cffd8..42d3fc3f 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -765,13 +765,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT // Listen for mouse events and translate // them into terminal mouse protocols. this.bindMouse(); - - /** - * This event is emitted when terminal has completed opening. - * - * @event open - */ - this.emit('open'); } /** diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index c8df78fc..80cc14a8 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -112,7 +112,7 @@ declare module 'xterm' { * @param type The type of the event. * @param listener The listener. */ - on(type: 'blur' | 'focus' | 'lineFeed' | 'open', listener: () => void): void; + on(type: 'blur' | 'focus' | 'lineFeed', listener: () => void): void; /** * Registers an event listener. * @param type The type of the event. @@ -161,7 +161,7 @@ declare module 'xterm' { * @param type The type of the event. * @param listener The listener. */ - off(type: 'blur' | 'focus' | 'lineFeed' | 'open' | 'data' | 'key' | 'keypress' | 'keydown' | 'refresh' | 'resize' | 'scroll' | 'title', listener: (...args: any[]) => void): void; + off(type: 'blur' | 'focus' | 'lineFeed' | 'data' | 'key' | 'keypress' | 'keydown' | 'refresh' | 'resize' | 'scroll' | 'title', listener: (...args: any[]) => void): void; /** * Resizes the terminal. From 8fdedd4da1cd3ce3bcebcdf7ea44b38da778b976 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 19 Aug 2017 14:38:54 -0700 Subject: [PATCH 090/378] Slim down refresh and resize events Fixes #903 --- fixtures/typings-test/typings-test.ts | 4 ++-- src/Renderer.ts | 2 +- src/Terminal.ts | 2 +- typings/xterm.d.ts | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/fixtures/typings-test/typings-test.ts b/fixtures/typings-test/typings-test.ts index f99d6ea3..ffe6afd4 100644 --- a/fixtures/typings-test/typings-test.ts +++ b/fixtures/typings-test/typings-test.ts @@ -74,9 +74,9 @@ namespace methods_core { t.on('keypress', () => {}); t.on('keypress', (event: KeyboardEvent) => console.log(event)); t.on('refresh', () => {}); - t.on('refresh', (data: {element: HTMLElement, start: number, end: number}) => console.log(data)); + t.on('refresh', (data: {start: number, end: number}) => console.log(data)); t.on('resize', () => {}); - t.on('resize', (data: {terminal: Terminal, cols: number, rows: number}) => console.log(data)); + t.on('resize', (data: {cols: number, rows: number}) => console.log(data)); t.on('scroll', () => {}); t.on('scroll', (ydisp: number) => console.log(ydisp)); t.on('title', () => {}); diff --git a/src/Renderer.ts b/src/Renderer.ts index f2999afa..b51a9ff9 100644 --- a/src/Renderer.ts +++ b/src/Renderer.ts @@ -320,7 +320,7 @@ export class Renderer { this._terminal.element.appendChild(this._terminal.rowContainer); } - this._terminal.emit('refresh', {element: this._terminal.element, start: start, end: end}); + this._terminal.emit('refresh', {start, end}); }; /** diff --git a/src/Terminal.ts b/src/Terminal.ts index 42d3fc3f..90a7a6fa 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1944,7 +1944,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.refresh(0, this.rows - 1); this.geometry = [this.cols, this.rows]; - this.emit('resize', {terminal: this, cols: x, rows: y}); + this.emit('resize', {cols: x, rows: y}); } /** diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 80cc14a8..3172f3b3 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -136,13 +136,13 @@ declare module 'xterm' { * @param type The type of the event. * @param listener The listener. */ - on(type: 'refresh', listener: (data?: {element: HTMLElement, start: number, end: number}) => void): void; + on(type: 'refresh', listener: (data?: {start: number, end: number}) => void): void; /** * Registers an event listener. * @param type The type of the event. * @param listener The listener. */ - on(type: 'resize', listener: (data?: {terminal: Terminal, cols: number, rows: number}) => void): void; + on(type: 'resize', listener: (data?: {cols: number, rows: number}) => void): void; /** * Registers an event listener. * @param type The type of the event. From c984013e1efc86b74035bafc0d668c585ce4fa22 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 19 Aug 2017 14:52:48 -0700 Subject: [PATCH 091/378] Remove terminal from other events --- src/Terminal.ts | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 90a7a6fa..4c6ba916 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -511,7 +511,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.showCursor(); this.restartCursorBlinking.apply(this); // TODO: Why pass terminal here? - this.emit('focus', {terminal: this}); + this.emit('focus'); }); }; @@ -535,7 +535,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.element.classList.remove('focus'); this.clearCursorBlinkingInterval.apply(this); // TODO: Why pass terminal here? - this.emit('blur', {terminal: this}); + this.emit('blur'); }); } @@ -710,14 +710,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.textarea.setAttribute('autocapitalize', 'off'); this.textarea.setAttribute('spellcheck', 'false'); this.textarea.tabIndex = 0; - this.textarea.addEventListener('focus', () => { - // TODO: Do we need terminal passed here? - this.emit('focus', {terminal: this}); - }); - this.textarea.addEventListener('blur', () => { - // TODO: Do we need terminal passed here? - this.emit('blur', {terminal: this}); - }); + this.textarea.addEventListener('focus', () => this.emit('focus')); + this.textarea.addEventListener('blur', () => this.emit('blur')); this.helperContainer.appendChild(this.textarea); this.compositionView = document.createElement('div'); From f983e4eb6bc24f2a4644ecd9b1fc571510b18cc5 Mon Sep 17 00:00:00 2001 From: npezza93 Date: Sat, 19 Aug 2017 18:03:04 -0400 Subject: [PATCH 092/378] Combine preloadBellSound and syncBellSound into the same function --- src/Terminal.ts | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 1dbbbc5f..332642b1 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -485,8 +485,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.viewport.syncScrollArea(); break; case 'tabStopWidth': this.setupStops(); break; - case 'bellSound': this.syncBellSound(); break; - case 'bellStyle': this.preloadBellSound(); break; + case 'bellSound': + case 'bellStyle': this.syncBellSound(); break; } } @@ -695,7 +695,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.viewportElement.appendChild(this.viewportScrollArea); // preload audio - this.preloadBellSound(); + this.syncBellSound(); // Create the selection container. this.selectionContainer = document.createElement('div'); @@ -2294,8 +2294,10 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.options.bellStyle === 'both'; } - private preloadBellSound(): void { - if (this.soundBell()) { + private syncBellSound(): void { + 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); @@ -2304,12 +2306,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.helperContainer.removeChild(this.bellAudioElement); } } - - private syncBellSound(): void { - if (this.soundBell() && this.bellAudioElement) { - this.bellAudioElement.setAttribute('src', this.options.bellSound); - } - } } /** From eda9443b1a48e25af1f7dab011b7d363f12abec9 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 20 Aug 2017 10:29:48 -0700 Subject: [PATCH 093/378] Allow null with cursorStyle and bellStyle --- src/Terminal.ts | 10 ++++++++++ typings/xterm.d.ts | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 879bfbab..a17cf94a 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -437,6 +437,16 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT throw new Error('No option with key "' + key + '"'); } switch (key) { + case 'bellStyle': + if (!value) { + value = 'none'; + } + break; + case 'cursorStyle': + if (!value) { + value = 'block'; + } + break; case 'tabStopWidth': if (value < 1) { console.warn(`tabStopWidth cannot be less than 1, value: ${value}`); diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 3172f3b3..7ccd1943 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -328,13 +328,13 @@ declare module 'xterm' { * @param key The option key. * @param value The option value. */ - setOption(key: 'bellStyle', value: 'none' | 'visual' | 'sound' | 'both'): void; + setOption(key: 'bellStyle', value: null | 'none' | 'visual' | 'sound' | 'both'): void; /** * Sets an option on the terminal. * @param key The option key. * @param value The option value. */ - setOption(key: 'cursorStyle', value: 'block' | 'underline' | 'bar'): void; + setOption(key: 'cursorStyle', value: null | 'block' | 'underline' | 'bar'): void; /** * Sets an option on the terminal. * @param key The option key. From cea3dd60b6de75d248d9df28d24243e58fe36590 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 20 Aug 2017 11:43:40 -0700 Subject: [PATCH 094/378] Add cols and rows to d.ts --- typings/xterm.d.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 7ccd1943..ce9fd8f6 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -87,9 +87,26 @@ declare module 'xterm' { * The class that represents an xterm.js terminal. */ export class Terminal { + /** + * The element containing the terminal. + */ element: HTMLElement; + + /** + * The textarea that accepts input for the terminal. + */ textarea: HTMLTextAreaElement; + /** + * The number of rows in the terminal's viewport. + */ + rows: number; + + /** + * The number of columns in the terminal's viewport. + */ + cols: number; + /** * Creates a new `Terminal` object. * From 8bc2242d9fe1823d8b2e3be9a98d889bfd71a048 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 20 Aug 2017 11:45:42 -0700 Subject: [PATCH 095/378] Add generic overloads to overloaded functions in d.ts If we don't have this then tsc will throw errors when passing strings to the functions, seems wrong to force casting strings to use the functions, eg. <'bar'>style --- typings/xterm.d.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index ce9fd8f6..ccc83ed9 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -172,13 +172,19 @@ declare module 'xterm' { * @param listener The listener. */ on(type: 'title', listener: (title?: string) => void): void; + /** + * Registers an event listener. + * @param type The type of the event. + * @param listener The listener. + */ + on(type: string, listener: (...args: any[]) => void): void; /** * Deregisters an event listener. * @param type The type of the event. * @param listener The listener. */ - off(type: 'blur' | 'focus' | 'lineFeed' | 'data' | 'key' | 'keypress' | 'keydown' | 'refresh' | 'resize' | 'scroll' | 'title', listener: (...args: any[]) => void): void; + off(type: 'blur' | 'focus' | 'lineFeed' | 'data' | 'key' | 'keypress' | 'keydown' | 'refresh' | 'resize' | 'scroll' | 'title' | string, listener: (...args: any[]) => void): void; /** * Resizes the terminal. @@ -333,6 +339,11 @@ 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. @@ -382,6 +393,12 @@ declare module 'xterm' { * @param value The option value. */ setOption(key: 'handler', value: (data: string) => void): 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 From 7f49bd99c216216fbeb0af67cb7ca1fcdc59d7a8 Mon Sep 17 00:00:00 2001 From: dcylabs Date: Mon, 21 Aug 2017 20:27:17 +0200 Subject: [PATCH 096/378] Fixed selection issue when terminal is in nested scrolled elements --- src/utils/Mouse.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/utils/Mouse.ts b/src/utils/Mouse.ts index c61624e9..e1901f2d 100644 --- a/src/utils/Mouse.ts +++ b/src/utils/Mouse.ts @@ -12,6 +12,7 @@ export function getCoordsRelativeToElement(event: MouseEvent, element: HTMLEleme let x = event.pageX; let y = event.pageY; + const originalElement = element; // Converts the coordinates from being relative to the document to being // relative to the terminal. @@ -20,6 +21,12 @@ export function getCoordsRelativeToElement(event: MouseEvent, element: HTMLEleme y -= element.offsetTop; element = 'offsetParent' in element ? element.offsetParent : element.parentElement; } + element = originalElement; + while (element && element !== self.document.documentElement) { + x += element.scrollLeft; + y += element.scrollTop; + element = element.parentElement; + } return [x, y]; } From 725ec80983638af28b4fcf29a3b96167dbbd180f Mon Sep 17 00:00:00 2001 From: dcylabs Date: Mon, 21 Aug 2017 20:27:17 +0200 Subject: [PATCH 097/378] Fixed selection issue when terminal is in nested scrolled elements --- src/utils/Mouse.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/utils/Mouse.ts b/src/utils/Mouse.ts index c61624e9..6852452a 100644 --- a/src/utils/Mouse.ts +++ b/src/utils/Mouse.ts @@ -10,6 +10,7 @@ export function getCoordsRelativeToElement(event: MouseEvent, element: HTMLEleme return null; } + const originalElement = element; let x = event.pageX; let y = event.pageY; @@ -20,6 +21,12 @@ export function getCoordsRelativeToElement(event: MouseEvent, element: HTMLEleme y -= element.offsetTop; element = 'offsetParent' in element ? element.offsetParent : element.parentElement; } + element = originalElement; + while (element && element !== self.document.documentElement) { + x += element.scrollLeft; + y += element.scrollTop; + element = element.parentElement; + } return [x, y]; } From 877e1e5eb30cdd8a9f4a9073a97b81c81a59dac4 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 21 Aug 2017 14:30:52 -0700 Subject: [PATCH 098/378] Prevent scrollback setOption < 0 Fixes #911 --- src/Terminal.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Terminal.ts b/src/Terminal.ts index a17cf94a..03babac5 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -454,6 +454,10 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT } break; case 'scrollback': + if (value < 0) { + console.warn(`scrollback cannot be less than 0, value: ${value}`); + return; + } if (this.options[key] !== value) { const newBufferLength = this.rows + value; if (this.buffer.lines.length > newBufferLength) { From b706383687353fd90f78c18171db82b7df01e43f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 24 Aug 2017 11:15:25 -0700 Subject: [PATCH 099/378] Allow mousedown even to propagate on right click Fixes #913 --- src/SelectionManager.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index f3aa5f82..1aaec14b 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -316,7 +316,6 @@ export class SelectionManager extends EventEmitter implements ISelectionManager // If we have selection, we want the context menu on right click even if the // terminal is in mouse mode. if (event.button === 2 && this.hasSelection) { - event.stopPropagation(); return; } From 9a8b7a4d8de143ce81c2211fa32772f65c1299fb Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 25 Aug 2017 11:26:46 -0700 Subject: [PATCH 100/378] Fix scroll region scrolling with 0 scrollback --- src/Buffer.ts | 3 +- src/Terminal.test.ts | 140 ++++++++++++++++++++++++++++++++++++++++++- src/Terminal.ts | 118 ++++++++++++++++++++++++++---------- 3 files changed, 228 insertions(+), 33 deletions(-) diff --git a/src/Buffer.ts b/src/Buffer.ts index fd6f8aef..7af090ac 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -78,10 +78,9 @@ export class Buffer implements IBuffer { this.ybase = 0; this.y = 0; this.x = 0; - this.scrollBottom = 0; - this.scrollTop = 0; this.tabs = {}; this._lines = new CircularList(this._getCorrectBufferLength(this._terminal.rows)); + this.scrollTop = 0; this.scrollBottom = this._terminal.rows - 1; } diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index 771835bb..0c333741 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -3,6 +3,9 @@ import { Terminal } from './Terminal'; import { MockViewport, MockCompositionHelper } from './utils/TestUtils.test'; import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from './Buffer'; +const INIT_COLS = 80; +const INIT_ROWS = 24; + class TestTerminal extends Terminal { public evaluateKeyEscapeSequence(ev: any): {cancel: boolean, key: string, scrollDisp: number} { return this._evaluateKeyEscapeSequence(ev); } public keyDown(ev: any): boolean { return this._keyDown(ev); } @@ -13,7 +16,10 @@ describe('term.js addons', () => { let term: TestTerminal; beforeEach(() => { - term = new TestTerminal(); + term = new TestTerminal({ + cols: INIT_COLS, + rows: INIT_ROWS + }); term.refresh = () => {}; term.viewport = new MockViewport(); (term).compositionHelper = new MockCompositionHelper(); @@ -283,6 +289,138 @@ describe('term.js addons', () => { assert.equal(term.buffer.ydisp, startYDisp - 1); }); }); + + describe('scroll() function', () => { + describe('when scrollback > 0', () => { + it('should create a new line and scroll', () => { + term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX] = 'a'; + term.buffer.lines.get(INIT_ROWS - 1)[0][CHAR_DATA_CHAR_INDEX] = 'b'; + term.buffer.y = INIT_ROWS - 1; // Move cursor to last line + term.scroll(); + assert.equal(term.buffer.lines.length, INIT_ROWS + 1); + assert.equal(term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX], 'a'); + assert.equal(term.buffer.lines.get(INIT_ROWS - 1)[0][CHAR_DATA_CHAR_INDEX], 'b'); + assert.equal(term.buffer.lines.get(INIT_ROWS)[0][CHAR_DATA_CHAR_INDEX], ' '); + }); + + it('should properly scroll inside a scroll region (scrollTop set)', () => { + term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX] = 'a'; + term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX] = 'b'; + term.buffer.lines.get(2)[0][CHAR_DATA_CHAR_INDEX] = 'c'; + term.buffer.y = INIT_ROWS - 1; // Move cursor to last line + term.buffer.scrollTop = 1; + term.scroll(); + assert.equal(term.buffer.lines.length, INIT_ROWS); + assert.equal(term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX], 'a'); + assert.equal(term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX], 'c'); + }); + + it('should properly scroll inside a scroll region (scrollBottom set)', () => { + term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX] = 'a'; + term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX] = 'b'; + term.buffer.lines.get(2)[0][CHAR_DATA_CHAR_INDEX] = 'c'; + term.buffer.lines.get(3)[0][CHAR_DATA_CHAR_INDEX] = 'd'; + term.buffer.lines.get(4)[0][CHAR_DATA_CHAR_INDEX] = 'e'; + term.buffer.y = 3; + term.buffer.scrollBottom = 3; + term.scroll(); + assert.equal(term.buffer.lines.length, INIT_ROWS + 1); + assert.equal(term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX], 'a', '\'a\' should be pushed to the scrollback'); + assert.equal(term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX], 'b'); + assert.equal(term.buffer.lines.get(2)[0][CHAR_DATA_CHAR_INDEX], 'c'); + assert.equal(term.buffer.lines.get(3)[0][CHAR_DATA_CHAR_INDEX], 'd'); + assert.equal(term.buffer.lines.get(4)[0][CHAR_DATA_CHAR_INDEX], ' ', 'a blank line should be added at scrollBottom\'s index'); + assert.equal(term.buffer.lines.get(5)[0][CHAR_DATA_CHAR_INDEX], 'e'); + }); + + it('should properly scroll inside a scroll region (scrollTop and scrollBottom set)', () => { + term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX] = 'a'; + term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX] = 'b'; + term.buffer.lines.get(2)[0][CHAR_DATA_CHAR_INDEX] = 'c'; + term.buffer.lines.get(3)[0][CHAR_DATA_CHAR_INDEX] = 'd'; + term.buffer.lines.get(4)[0][CHAR_DATA_CHAR_INDEX] = 'e'; + term.buffer.y = INIT_ROWS - 1; // Move cursor to last line + term.buffer.scrollTop = 1; + term.buffer.scrollBottom = 3; + term.scroll(); + assert.equal(term.buffer.lines.length, INIT_ROWS); + assert.equal(term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX], 'a'); + assert.equal(term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX], 'c', '\'b\' should be removed from the buffer'); + assert.equal(term.buffer.lines.get(2)[0][CHAR_DATA_CHAR_INDEX], 'd'); + assert.equal(term.buffer.lines.get(3)[0][CHAR_DATA_CHAR_INDEX], ' ', 'a blank line should be added at scrollBottom\'s index'); + assert.equal(term.buffer.lines.get(4)[0][CHAR_DATA_CHAR_INDEX], 'e'); + }); + }); + + describe('when scrollback === 0', () => { + beforeEach(() => { + term.setOption('scrollback', 0); + assert.equal(term.buffer.lines.maxLength, INIT_ROWS); + }); + + it('should create a new line and shift everything up', () => { + term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX] = 'a'; + term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX] = 'b'; + term.buffer.lines.get(INIT_ROWS - 1)[0][CHAR_DATA_CHAR_INDEX] = 'c'; + term.buffer.y = INIT_ROWS - 1; // Move cursor to last line + assert.equal(term.buffer.lines.length, INIT_ROWS); + term.scroll(); + assert.equal(term.buffer.lines.length, INIT_ROWS); + // 'a' gets pushed out of buffer + assert.equal(term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX], 'b'); + assert.equal(term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX], ' '); + assert.equal(term.buffer.lines.get(INIT_ROWS - 2)[0][CHAR_DATA_CHAR_INDEX], 'c'); + assert.equal(term.buffer.lines.get(INIT_ROWS - 1)[0][CHAR_DATA_CHAR_INDEX], ' '); + }); + + it('should properly scroll inside a scroll region (scrollTop set)', () => { + term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX] = 'a'; + term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX] = 'b'; + term.buffer.lines.get(2)[0][CHAR_DATA_CHAR_INDEX] = 'c'; + term.buffer.y = INIT_ROWS - 1; // Move cursor to last line + term.buffer.scrollTop = 1; + term.scroll(); + assert.equal(term.buffer.lines.length, INIT_ROWS); + assert.equal(term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX], 'a'); + assert.equal(term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX], 'c'); + }); + + it('should properly scroll inside a scroll region (scrollBottom set)', () => { + term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX] = 'a'; + term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX] = 'b'; + term.buffer.lines.get(2)[0][CHAR_DATA_CHAR_INDEX] = 'c'; + term.buffer.lines.get(3)[0][CHAR_DATA_CHAR_INDEX] = 'd'; + term.buffer.lines.get(4)[0][CHAR_DATA_CHAR_INDEX] = 'e'; + term.buffer.y = 3; + term.buffer.scrollBottom = 3; + term.scroll(); + assert.equal(term.buffer.lines.length, INIT_ROWS); + assert.equal(term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX], 'b'); + assert.equal(term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX], 'c'); + assert.equal(term.buffer.lines.get(2)[0][CHAR_DATA_CHAR_INDEX], 'd'); + assert.equal(term.buffer.lines.get(3)[0][CHAR_DATA_CHAR_INDEX], ' ', 'a blank line should be added at scrollBottom\'s index'); + assert.equal(term.buffer.lines.get(4)[0][CHAR_DATA_CHAR_INDEX], 'e'); + }); + + it('should properly scroll inside a scroll region (scrollTop and scrollBottom set)', () => { + term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX] = 'a'; + term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX] = 'b'; + term.buffer.lines.get(2)[0][CHAR_DATA_CHAR_INDEX] = 'c'; + term.buffer.lines.get(3)[0][CHAR_DATA_CHAR_INDEX] = 'd'; + term.buffer.lines.get(4)[0][CHAR_DATA_CHAR_INDEX] = 'e'; + term.buffer.y = INIT_ROWS - 1; // Move cursor to last line + term.buffer.scrollTop = 1; + term.buffer.scrollBottom = 3; + term.scroll(); + assert.equal(term.buffer.lines.length, INIT_ROWS); + assert.equal(term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX], 'a'); + assert.equal(term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX], 'c', '\'b\' should be removed from the buffer'); + assert.equal(term.buffer.lines.get(2)[0][CHAR_DATA_CHAR_INDEX], 'd'); + assert.equal(term.buffer.lines.get(3)[0][CHAR_DATA_CHAR_INDEX], ' ', 'a blank line should be added at scrollBottom\'s index'); + assert.equal(term.buffer.lines.get(4)[0][CHAR_DATA_CHAR_INDEX], 'e'); + }); + }); + }); }); describe('evaluateKeyEscapeSequence', () => { diff --git a/src/Terminal.ts b/src/Terminal.ts index 03babac5..a083d9d4 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1167,46 +1167,104 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * line. */ public scroll(isWrapped?: boolean): void { - let row; + // The problem is caused when scrollback = 0. This section was written + // around the assumption that there would be buffer to trim when necessary. - // Make room for the new row in lines - const bufferNeedsTrimming = this.buffer.lines.length === this.buffer.lines.maxLength; - if (bufferNeedsTrimming) { - this.buffer.lines.trimStart(1); - this.buffer.ybase--; - this.buffer.ydisp = Math.max(this.buffer.ydisp - 1, 0); + + // shiftElements should be used here + + // If scrollTop is set (non-zero), we should shiftElements + // scrollBottom defines the position at which the new element is inserted + + // Note that both scrollBottom and scrollTop need to be handled individually + // because they can be set to terminal.rows or 0 respectively + + // Don't mess with ybase temporarily, asking for trouble + + // This should work with minimal effort utilizing the CiruclarList + + // TODO: Need to hold onto top given the if? + const bottomRow = this.buffer.ybase + this.buffer.scrollBottom; + console.log('bottomRow', bottomRow); + console.log('this.buffer.scrollTop: ' + this.buffer.scrollTop); + if (this.buffer.scrollTop === 0) { + const willBufferBeTrimmed = this.buffer.lines.length === this.buffer.lines.maxLength; + + if (bottomRow === this.buffer.lines.length - 1) { + // Pushing when possible is faster than splicing + this.buffer.lines.push(this.blankLine(undefined, isWrapped)); + console.log('push'); + } else { + // Insert a row *below* the bottomRow, pushing the top row into the scrollback + this.buffer.lines.splice(bottomRow + 1, 0, this.blankLine(undefined, isWrapped)); + console.log('splice'); + } + + // Adjust ydisp and ybase to accommodate the changes after the buffer was + // trimmed. + if (!willBufferBeTrimmed) { + console.log('increment ydisp/ybase'); + this.buffer.ybase++; + this.buffer.ydisp++; + } + console.log('this.buffer.ybase: ' + this.buffer.ybase); + console.log('this.buffer.ybase: ' + this.buffer.ydisp); + } else { + const topRow = this.buffer.ybase + this.buffer.scrollTop; + const scrollRegionHeight = bottomRow - topRow + 1/*as it's zero-based*/; +console.log('shiftElements'); +console.log('topRow: ' + topRow + '(' + this.buffer.lines.get(topRow)[0][1] + ')'); +console.log('scrollRegionHeight: ' + scrollRegionHeight); + this.buffer.lines.shiftElements(topRow + 1, scrollRegionHeight - 1, -1); + this.buffer.lines.set(bottomRow, this.blankLine(undefined, isWrapped)); } - this.buffer.ybase++; - - // Scroll the viewport down to the bottom if the user is not scrolling if (!this.userScrolling) { this.buffer.ydisp = this.buffer.ybase; } - // last line - row = this.buffer.ybase + this.rows - 1; - // subtract the bottom scroll region - row -= this.rows - 1 - this.buffer.scrollBottom; + // Make room for the new row in lines + // const bufferNeedsTrimming = this.buffer.lines.length === this.buffer.lines.maxLength; + // if (bufferNeedsTrimming) { + // this.buffer.lines.trimStart(1); + // this.buffer.ybase--; + // this.buffer.ydisp = Math.max(this.buffer.ydisp - 1, 0); + // } - if (row === this.buffer.lines.length) { - // Optimization: pushing is faster than splicing when they amount to the same behavior - this.buffer.lines.push(this.blankLine(undefined, isWrapped)); - } else { - // add our new line - this.buffer.lines.splice(row, 0, this.blankLine(undefined, isWrapped)); - } + // this.buffer.ybase++; - if (this.buffer.scrollTop !== 0) { - if (this.buffer.ybase !== 0) { - this.buffer.ybase--; - if (!this.userScrolling) { - this.buffer.ydisp = this.buffer.ybase; - } - } - this.buffer.lines.splice(this.buffer.ybase + this.buffer.scrollTop, 1); - } + // // Scroll the viewport down to the bottom if the user is not scrolling + // if (!this.userScrolling) { + // this.buffer.ydisp = this.buffer.ybase; + // } + + // // last line + // let row = this.buffer.ybase + this.rows - 1; + + // // subtract the bottom scroll region + // row -= this.rows - 1 - this.buffer.scrollBottom; + + // // Same as this? + // // row = this.buffer.ybase + this.buffer.scrollBottom; + + // if (row === this.buffer.lines.length) { + // // Optimization: pushing is faster than splicing when they amount to the same behavior + // this.buffer.lines.push(this.blankLine(undefined, isWrapped)); + // } else { + // // add our new line + // this.buffer.lines.splice(row, 0, this.blankLine(undefined, isWrapped)); + // } + + // if (this.buffer.scrollTop !== 0) { + // if (this.buffer.ybase !== 0) { + // this.buffer.ybase--; + // if (!this.userScrolling) { + // this.buffer.ydisp = this.buffer.ybase; + // } + // } + // this.buffer.lines.splice(this.buffer.ybase + this.buffer.scrollTop, 1); + // } // this.maxRange(); this.updateRange(this.buffer.scrollTop); From a2161532db2deb3b05da3f1238790bc929797552 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 25 Aug 2017 12:11:20 -0700 Subject: [PATCH 101/378] Fix reverseIndex to work with scroll regions and 0 scrollback --- src/Buffer.ts | 4 ++++ src/InputHandler.ts | 3 +++ src/Terminal.ts | 48 +++++++++++++++++++++++++++++++++------------ 3 files changed, 43 insertions(+), 12 deletions(-) diff --git a/src/Buffer.ts b/src/Buffer.ts index 7af090ac..53645837 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -46,6 +46,10 @@ export class Buffer implements IBuffer { return this._lines; } + public get hasScrollback(): boolean { + return this._hasScrollback && this.lines.maxLength > this._terminal.rows; + } + /** * Gets the correct buffer length based on the rows provided, the terminal's * scrollback and whether this buffer is flagged to have scrollback or not. diff --git a/src/InputHandler.ts b/src/InputHandler.ts index f2d97751..e6ab5540 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -464,10 +464,13 @@ export class InputHandler implements IInputHandler { let j: number; j = this._terminal.rows - 1 - this._terminal.buffer.scrollBottom; j = this._terminal.rows - 1 + this._terminal.buffer.ybase - j; + console.log('deleteLines', params); while (param--) { // test: echo -e '\e[44m\e[1M\e[0m' // blankLine(true) - xterm/linux behavior + console.log('splice delete row: ' + (row - 1) + '(' + this._terminal.buffer.lines.get(row - 1)[0][1] + ')'); this._terminal.buffer.lines.splice(row - 1, 1); + console.log('splice add blank row: ' + (j) + '(' + this._terminal.buffer.lines.get(j)[0][1] + ')'); this._terminal.buffer.lines.splice(j, 0, this._terminal.blankLine(true)); } diff --git a/src/Terminal.ts b/src/Terminal.ts index a083d9d4..fa3161f0 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1184,39 +1184,62 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT // This should work with minimal effort utilizing the CiruclarList // TODO: Need to hold onto top given the if? - const bottomRow = this.buffer.ybase + this.buffer.scrollBottom; + const newLine = this.blankLine(undefined, isWrapped); + const topRow = this.buffer.ybase + this.buffer.scrollTop; + let bottomRow = this.buffer.ybase + this.buffer.scrollBottom; + + + +// TODO: There's a problem with ybase going beyond scrollback? + + console.log('bottomRow', bottomRow); console.log('this.buffer.scrollTop: ' + this.buffer.scrollTop); if (this.buffer.scrollTop === 0) { const willBufferBeTrimmed = this.buffer.lines.length === this.buffer.lines.maxLength; + // Remove the line first if there is no scrollback so the list is not + // trimmed + if (!this.buffer.hasScrollback) { + this.buffer.lines.splice(topRow, 1); + // Adjust bottomRow to make up for the deleted row + // bottomRow--; + } + if (bottomRow === this.buffer.lines.length - 1) { // Pushing when possible is faster than splicing - this.buffer.lines.push(this.blankLine(undefined, isWrapped)); + this.buffer.lines.push(newLine); console.log('push'); } else { // Insert a row *below* the bottomRow, pushing the top row into the scrollback - this.buffer.lines.splice(bottomRow + 1, 0, this.blankLine(undefined, isWrapped)); + if (!this.buffer.hasScrollback) { + // A line is deleted in this case so bottomRow is pushed up + this.buffer.lines.splice(bottomRow, 0, newLine); + } else { + this.buffer.lines.splice(bottomRow + 1, 0, newLine); + } console.log('splice'); } - // Adjust ydisp and ybase to accommodate the changes after the buffer was - // trimmed. + // Only adjust ybase and ydisp when the buffer is not trimmed if (!willBufferBeTrimmed) { console.log('increment ydisp/ybase'); this.buffer.ybase++; this.buffer.ydisp++; } + console.log('this.buffer.ybase: ' + this.buffer.ybase); - console.log('this.buffer.ybase: ' + this.buffer.ydisp); + console.log('this.buffer.ydisp: ' + this.buffer.ydisp); + } else { - const topRow = this.buffer.ybase + this.buffer.scrollTop; const scrollRegionHeight = bottomRow - topRow + 1/*as it's zero-based*/; -console.log('shiftElements'); -console.log('topRow: ' + topRow + '(' + this.buffer.lines.get(topRow)[0][1] + ')'); -console.log('scrollRegionHeight: ' + scrollRegionHeight); + + console.log('shiftElements'); + console.log('topRow: ' + topRow + '(' + this.buffer.lines.get(topRow)[0][1] + ')'); + console.log('scrollRegionHeight: ' + scrollRegionHeight); + this.buffer.lines.shiftElements(topRow + 1, scrollRegionHeight - 1, -1); - this.buffer.lines.set(bottomRow, this.blankLine(undefined, isWrapped)); + this.buffer.lines.set(bottomRow, newLine); } if (!this.userScrolling) { @@ -2247,7 +2270,8 @@ console.log('scrollRegionHeight: ' + scrollRegionHeight); // possibly move the code below to term.reverseScroll(); // test: echo -ne '\e[1;1H\e[44m\eM\e[0m' // blankLine(true) is xterm/linux behavior - this.buffer.lines.shiftElements(this.buffer.y + this.buffer.ybase, this.rows - 1, 1); + const scrollRegionHeight = this.buffer.scrollBottom - this.buffer.scrollTop; + this.buffer.lines.shiftElements(this.buffer.y + this.buffer.ybase, scrollRegionHeight, 1); this.buffer.lines.set(this.buffer.y + this.buffer.ybase, this.blankLine(true)); this.updateRange(this.buffer.scrollTop); this.updateRange(this.buffer.scrollBottom); From 41463d8eb5ae6e604eb524c9be7787935f5a75d7 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 25 Aug 2017 12:41:53 -0700 Subject: [PATCH 102/378] Make reverseIndex scroll region-aware This was causing a issue when scrollback was 0 as the start of the buffer was getting copied --- src/InputHandler.ts | 3 -- src/Terminal.ts | 107 ++++---------------------------------------- test/bar | 0 test/foo | 0 4 files changed, 9 insertions(+), 101 deletions(-) create mode 100644 test/bar create mode 100644 test/foo diff --git a/src/InputHandler.ts b/src/InputHandler.ts index e6ab5540..f2d97751 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -464,13 +464,10 @@ export class InputHandler implements IInputHandler { let j: number; j = this._terminal.rows - 1 - this._terminal.buffer.scrollBottom; j = this._terminal.rows - 1 + this._terminal.buffer.ybase - j; - console.log('deleteLines', params); while (param--) { // test: echo -e '\e[44m\e[1M\e[0m' // blankLine(true) - xterm/linux behavior - console.log('splice delete row: ' + (row - 1) + '(' + this._terminal.buffer.lines.get(row - 1)[0][1] + ')'); this._terminal.buffer.lines.splice(row - 1, 1); - console.log('splice add blank row: ' + (j) + '(' + this._terminal.buffer.lines.get(j)[0][1] + ')'); this._terminal.buffer.lines.splice(j, 0, this._terminal.blankLine(true)); } diff --git a/src/Terminal.ts b/src/Terminal.ts index fa3161f0..ea3800d0 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1163,133 +1163,44 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT /** * Scroll the terminal down 1 row, creating a blank line. - * @param {boolean} isWrapped Whether the new line is wrapped from the previous - * line. + * @param isWrapped Whether the new line is wrapped from the previous line. */ public scroll(isWrapped?: boolean): void { - // The problem is caused when scrollback = 0. This section was written - // around the assumption that there would be buffer to trim when necessary. - - - // shiftElements should be used here - - // If scrollTop is set (non-zero), we should shiftElements - // scrollBottom defines the position at which the new element is inserted - - // Note that both scrollBottom and scrollTop need to be handled individually - // because they can be set to terminal.rows or 0 respectively - - // Don't mess with ybase temporarily, asking for trouble - - // This should work with minimal effort utilizing the CiruclarList - - // TODO: Need to hold onto top given the if? const newLine = this.blankLine(undefined, isWrapped); const topRow = this.buffer.ybase + this.buffer.scrollTop; let bottomRow = this.buffer.ybase + this.buffer.scrollBottom; - - -// TODO: There's a problem with ybase going beyond scrollback? - - - console.log('bottomRow', bottomRow); - console.log('this.buffer.scrollTop: ' + this.buffer.scrollTop); if (this.buffer.scrollTop === 0) { + // Determine whether the buffer is going to be trimmed after insertion. const willBufferBeTrimmed = this.buffer.lines.length === this.buffer.lines.maxLength; - // Remove the line first if there is no scrollback so the list is not - // trimmed - if (!this.buffer.hasScrollback) { - this.buffer.lines.splice(topRow, 1); - // Adjust bottomRow to make up for the deleted row - // bottomRow--; - } - + // Insert the line using the fastest method if (bottomRow === this.buffer.lines.length - 1) { - // Pushing when possible is faster than splicing this.buffer.lines.push(newLine); - console.log('push'); } else { - // Insert a row *below* the bottomRow, pushing the top row into the scrollback - if (!this.buffer.hasScrollback) { - // A line is deleted in this case so bottomRow is pushed up - this.buffer.lines.splice(bottomRow, 0, newLine); - } else { - this.buffer.lines.splice(bottomRow + 1, 0, newLine); - } - console.log('splice'); + this.buffer.lines.splice(bottomRow + 1, 0, newLine); } // Only adjust ybase and ydisp when the buffer is not trimmed if (!willBufferBeTrimmed) { - console.log('increment ydisp/ybase'); this.buffer.ybase++; this.buffer.ydisp++; } - - console.log('this.buffer.ybase: ' + this.buffer.ybase); - console.log('this.buffer.ydisp: ' + this.buffer.ydisp); - } else { + // scrollTop is non-zero which means no line will be going to the + // scrollback, instead we can just shift them in-place. const scrollRegionHeight = bottomRow - topRow + 1/*as it's zero-based*/; - - console.log('shiftElements'); - console.log('topRow: ' + topRow + '(' + this.buffer.lines.get(topRow)[0][1] + ')'); - console.log('scrollRegionHeight: ' + scrollRegionHeight); - this.buffer.lines.shiftElements(topRow + 1, scrollRegionHeight - 1, -1); this.buffer.lines.set(bottomRow, newLine); } + // Move the viewport to the bottom of the buffer unless the user is + // scrolling. if (!this.userScrolling) { this.buffer.ydisp = this.buffer.ybase; } - - // Make room for the new row in lines - // const bufferNeedsTrimming = this.buffer.lines.length === this.buffer.lines.maxLength; - // if (bufferNeedsTrimming) { - // this.buffer.lines.trimStart(1); - // this.buffer.ybase--; - // this.buffer.ydisp = Math.max(this.buffer.ydisp - 1, 0); - // } - - // this.buffer.ybase++; - - // // Scroll the viewport down to the bottom if the user is not scrolling - // if (!this.userScrolling) { - // this.buffer.ydisp = this.buffer.ybase; - // } - - // // last line - // let row = this.buffer.ybase + this.rows - 1; - - // // subtract the bottom scroll region - // row -= this.rows - 1 - this.buffer.scrollBottom; - - // // Same as this? - // // row = this.buffer.ybase + this.buffer.scrollBottom; - - // if (row === this.buffer.lines.length) { - // // Optimization: pushing is faster than splicing when they amount to the same behavior - // this.buffer.lines.push(this.blankLine(undefined, isWrapped)); - // } else { - // // add our new line - // this.buffer.lines.splice(row, 0, this.blankLine(undefined, isWrapped)); - // } - - // if (this.buffer.scrollTop !== 0) { - // if (this.buffer.ybase !== 0) { - // this.buffer.ybase--; - // if (!this.userScrolling) { - // this.buffer.ydisp = this.buffer.ybase; - // } - // } - // this.buffer.lines.splice(this.buffer.ybase + this.buffer.scrollTop, 1); - // } - - // this.maxRange(); + // Flag rows that need updating this.updateRange(this.buffer.scrollTop); this.updateRange(this.buffer.scrollBottom); diff --git a/test/bar b/test/bar new file mode 100644 index 00000000..e69de29b diff --git a/test/foo b/test/foo new file mode 100644 index 00000000..e69de29b From 54cf30ae68f8b80b183f346241752dc2b99e2508 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 25 Aug 2017 12:47:45 -0700 Subject: [PATCH 103/378] Remove test files --- test/bar | 0 test/foo | 0 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 test/bar delete mode 100644 test/foo diff --git a/test/bar b/test/bar deleted file mode 100644 index e69de29b..00000000 diff --git a/test/foo b/test/foo deleted file mode 100644 index e69de29b..00000000 From bc86664726bf2f1f0ca2e700f70545a125f3b369 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 25 Aug 2017 13:14:30 -0700 Subject: [PATCH 104/378] Improve Buffer.resize to work with uninitialized buffers Fixes #924 --- src/Buffer.ts | 138 +++++++++++++++++++++++++------------------------- 1 file changed, 69 insertions(+), 69 deletions(-) diff --git a/src/Buffer.ts b/src/Buffer.ts index 53645837..4f81025f 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -94,11 +94,6 @@ export class Buffer implements IBuffer { * @param newRows The new number of rows. */ public resize(newCols: number, newRows: number): void { - // Don't resize the buffer if it's empty and hasn't been used yet. - if (this._lines.length === 0) { - return; - } - // Increase max length if needed before adjustments to allow space to fill // as required. const newMaxLength = this._getCorrectBufferLength(newRows); @@ -106,83 +101,88 @@ export class Buffer implements IBuffer { this._lines.maxLength = newMaxLength; } - // Deal with columns increasing (we don't do anything when columns reduce) - if (this._terminal.cols < newCols) { - const ch: CharData = [this._terminal.defAttr, ' ', 1]; // does xterm use the default attr? - for (let i = 0; i < this._lines.length; i++) { - // TODO: This should be removed, with tests setup for the case that was - // causing the underlying bug, see https://github.com/sourcelair/xterm.js/issues/824 - if (this._lines.get(i) === undefined) { - this._lines.set(i, this._terminal.blankLine(undefined, undefined, newCols)); - } - while (this._lines.get(i).length < newCols) { - this._lines.get(i).push(ch); + // The following adjustments should only happen if the buffer has been + // initialized/filled. + if (this._lines.length > 0) { + // Deal with columns increasing (we don't do anything when columns reduce) + if (this._terminal.cols < newCols) { + const ch: CharData = [this._terminal.defAttr, ' ', 1]; // does xterm use the default attr? + for (let i = 0; i < this._lines.length; i++) { + // TODO: This should be removed, with tests setup for the case that was + // causing the underlying bug, see https://github.com/sourcelair/xterm.js/issues/824 + if (this._lines.get(i) === undefined) { + this._lines.set(i, this._terminal.blankLine(undefined, undefined, newCols)); + } + while (this._lines.get(i).length < newCols) { + this._lines.get(i).push(ch); + } } } - } - // Resize rows in both directions as needed - let addToY = 0; - if (this._terminal.rows < newRows) { - for (let y = this._terminal.rows; y < newRows; y++) { - if (this._lines.length < newRows + this.ybase) { - if (this.ybase > 0 && this._lines.length <= this.ybase + this.y + addToY + 1) { - // There is room above the buffer and there are no empty elements below the line, - // scroll up - this.ybase--; - addToY++; - if (this.ydisp > 0) { - // Viewport is at the top of the buffer, must increase downwards - this.ydisp--; + // Resize rows in both directions as needed + let addToY = 0; + if (this._terminal.rows < newRows) { + for (let y = this._terminal.rows; y < newRows; y++) { + if (this._lines.length < newRows + this.ybase) { + if (this.ybase > 0 && this._lines.length <= this.ybase + this.y + addToY + 1) { + // There is room above the buffer and there are no empty elements below the line, + // scroll up + this.ybase--; + addToY++; + if (this.ydisp > 0) { + // Viewport is at the top of the buffer, must increase downwards + this.ydisp--; + } + } else { + // Add a blank line if there is no buffer left at the top to scroll to, or if there + // are blank lines after the cursor + this._lines.push(this._terminal.blankLine(undefined, undefined, newCols)); + } + } + } + } else { // (this._terminal.rows >= newRows) + for (let y = this._terminal.rows; y > newRows; y--) { + if (this._lines.length > newRows + this.ybase) { + if (this._lines.length > this.ybase + this.y + 1) { + // The line is a blank line below the cursor, remove it + this._lines.pop(); + } else { + // The line is the cursor, scroll down + this.ybase++; + this.ydisp++; } - } else { - // Add a blank line if there is no buffer left at the top to scroll to, or if there - // are blank lines after the cursor - this._lines.push(this._terminal.blankLine(undefined, undefined, newCols)); } } } - } else { // (this._terminal.rows >= newRows) - for (let y = this._terminal.rows; y > newRows; y--) { - if (this._lines.length > newRows + this.ybase) { - if (this._lines.length > this.ybase + this.y + 1) { - // The line is a blank line below the cursor, remove it - this._lines.pop(); - } else { - // The line is the cursor, scroll down - this.ybase++; - this.ydisp++; - } + + // Reduce max length if needed after adjustments, this is done after as it + // would otherwise cut data from the bottom of the buffer. + if (newMaxLength < this._lines.maxLength) { + // Trim from the top of the buffer and adjust ybase and ydisp. + const amountToTrim = this._lines.length - newMaxLength; + if (amountToTrim > 0) { + this._lines.trimStart(amountToTrim); + this.ybase = Math.max(this.ybase - amountToTrim, 0); + this.ydisp = Math.max(this.ydisp - amountToTrim, 0); } + this._lines.maxLength = newMaxLength; } - } - // Reduce max length if needed after adjustments, this is done after as it - // would otherwise cut data from the bottom of the buffer. - if (newMaxLength < this._lines.maxLength) { - // Trim from the top of the buffer and adjust ybase and ydisp. - const amountToTrim = this._lines.length - newMaxLength; - if (amountToTrim > 0) { - this._lines.trimStart(amountToTrim); - this.ybase = Math.max(this.ybase - amountToTrim, 0); - this.ydisp = Math.max(this.ydisp - amountToTrim, 0); + // Make sure that the cursor stays on screen + if (this.y >= newRows) { + this.y = newRows - 1; } - this._lines.maxLength = newMaxLength; + if (addToY) { + this.y += addToY; + } + + if (this.x >= newCols) { + this.x = newCols - 1; + } + + this.scrollTop = 0; } - // Make sure that the cursor stays on screen - if (this.y >= newRows) { - this.y = newRows - 1; - } - if (addToY) { - this.y += addToY; - } - - if (this.x >= newCols) { - this.x = newCols - 1; - } - - this.scrollTop = 0; this.scrollBottom = newRows - 1; } From 6b8b32015dac0f5afedbc06e10dc370d24f1a4cb Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 27 Aug 2017 10:39:13 -0700 Subject: [PATCH 105/378] Don't include Terminal in search addon Fixes #927 --- gulpfile.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gulpfile.js b/gulpfile.js index ea5688bc..6a20c729 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -7,6 +7,7 @@ const buffer = require('vinyl-buffer'); const coveralls = require('gulp-coveralls'); const fs = require('fs-extra'); const gulp = require('gulp'); +const path = require('path'); const istanbul = require('gulp-istanbul'); const merge = require('merge-stream'); const mocha = require('gulp-mocha'); @@ -96,6 +97,7 @@ gulp.task('browserify-addons', ['tsc'], function() { packageCache: {} }; let searchBundle = browserify(searchOptions) + .external(path.join(outDir, 'Terminal.js')) .bundle() .pipe(source('./addons/search/search.js')) .pipe(buffer()) From a57e511fa3f95ac42a1e1296048399d55e8b0d05 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 29 Aug 2017 21:08:11 -0700 Subject: [PATCH 106/378] Properly setup and maintain tab stops for the alt buffer Fixes #930 --- src/Buffer.ts | 45 ++++++++++++++++++++++++++++++++++++- src/BufferSet.ts | 11 +++++++++ src/InputHandler.ts | 6 ++--- src/Interfaces.ts | 4 ++-- src/Terminal.ts | 45 ++----------------------------------- src/utils/TestUtils.test.ts | 6 +++++ 6 files changed, 68 insertions(+), 49 deletions(-) diff --git a/src/Buffer.ts b/src/Buffer.ts index 4f81025f..4c730063 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -82,10 +82,10 @@ export class Buffer implements IBuffer { this.ybase = 0; this.y = 0; this.x = 0; - this.tabs = {}; this._lines = new CircularList(this._getCorrectBufferLength(this._terminal.rows)); this.scrollTop = 0; this.scrollBottom = this._terminal.rows - 1; + this.setupTabStops(); } /** @@ -233,4 +233,47 @@ export class Buffer implements IBuffer { return lineString.substring(widthAdjustedStartCol, finalEndCol); } + + /** + * Setup the tab stops. + * @param i The index to start setting up tab stops from. + */ + public setupTabStops(i?: number): void { + if (i != null) { + if (!this.tabs[i]) { + i = this.prevStop(i); + } + } else { + this.tabs = {}; + i = 0; + } + + for (; i < this._terminal.cols; i += this._terminal.options.tabStopWidth) { + this.tabs[i] = true; + } + } + + /** + * Move the cursor to the previous tab stop from the given position (default is current). + * @param x The position to move the cursor to the previous tab stop. + */ + public prevStop(x?: number): number { + if (x == null) { + x = this.x; + } + while (!this.tabs[--x] && x > 0); + return x >= this._terminal.cols ? this._terminal.cols - 1 : x < 0 ? 0 : x; + } + + /** + * Move the cursor one tab stop forward from the given position (default is current). + * @param x The position to move the cursor one tab stop forward. + */ + public nextStop(x?: number): number { + if (x == null) { + x = this.x; + } + while (!this.tabs[++x] && x < this._terminal.cols); + return x >= this._terminal.cols ? this._terminal.cols - 1 : x < 0 ? 0 : x; + } } diff --git a/src/BufferSet.ts b/src/BufferSet.ts index d37a650f..64b364e9 100644 --- a/src/BufferSet.ts +++ b/src/BufferSet.ts @@ -28,6 +28,8 @@ export class BufferSet extends EventEmitter implements IBufferSet { // See http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer this._alt = new Buffer(this._terminal, false); this._activeBuffer = this._normal; + + this.setupTabStops(); } /** @@ -87,4 +89,13 @@ export class BufferSet extends EventEmitter implements IBufferSet { this._normal.resize(newCols, newRows); this._alt.resize(newCols, newRows); } + + /** + * Setup the tab stops. + * @param i The index to start setting up tab stops from. + */ + public setupTabStops(i?: number): void { + this._normal.setupTabStops(i); + this._alt.setupTabStops(i); + } } diff --git a/src/InputHandler.ts b/src/InputHandler.ts index f2d97751..abfdfa0e 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -157,7 +157,7 @@ export class InputHandler implements IInputHandler { * Horizontal Tab (HT) (Ctrl-I). */ public tab(): void { - this._terminal.buffer.x = this._terminal.nextStop(); + this._terminal.buffer.x = this._terminal.buffer.nextStop(); } /** @@ -349,7 +349,7 @@ export class InputHandler implements IInputHandler { public cursorForwardTab(params: number[]): void { let param = params[0] || 1; while (param--) { - this._terminal.buffer.x = this._terminal.nextStop(); + this._terminal.buffer.x = this._terminal.buffer.nextStop(); } } @@ -548,7 +548,7 @@ export class InputHandler implements IInputHandler { public cursorBackwardTab(params: number[]): void { let param = params[0] || 1; while (param--) { - this._terminal.buffer.x = this._terminal.prevStop(); + this._terminal.buffer.x = this._terminal.buffer.prevStop(); } } diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 943003ae..fb66f4d7 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -92,14 +92,12 @@ export interface IInputHandlingTerminal extends IEventEmitter { convertEol: boolean; updateRange(y: number): void; scroll(isWrapped?: boolean): void; - nextStop(x?: number): number; 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; - prevStop(x?: number): number; is(term: string): boolean; send(data: string): void; setgCharset(g: number, charset: Charset): void; @@ -146,6 +144,8 @@ export interface IBuffer { savedY: number; savedX: number; translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol?: number, endCol?: number): string; + nextStop(x?: number): number; + prevStop(x?: number): number; } export interface IBufferSet { diff --git a/src/Terminal.ts b/src/Terminal.ts index ea3800d0..827273ff 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -392,8 +392,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT if (this.selectionManager) { this.selectionManager.setBuffer(this.buffer); } - - this.setupStops(); } /** @@ -486,7 +484,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.buffers.resize(this.cols, this.rows); this.viewport.syncScrollArea(); break; - case 'tabStopWidth': this.setupStops(); break; + case 'tabStopWidth': this.buffers.setupTabStops(); break; case 'bellSound': case 'bellStyle': this.syncBellSound(); break; } @@ -1938,7 +1936,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.cols = x; this.rows = y; - this.setupStops(this.cols); + this.buffers.setupTabStops(this.cols); this.charMeasure.measure(); @@ -1971,45 +1969,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.refreshEnd = this.rows - 1; } - /** - * Setup the tab stops. - * @param {number} i - */ - public setupStops(i?: number): void { - if (i != null) { - if (!this.buffer.tabs[i]) { - i = this.prevStop(i); - } - } else { - this.buffer.tabs = {}; - i = 0; - } - - for (; i < this.cols; i += this.getOption('tabStopWidth')) { - this.buffer.tabs[i] = true; - } - } - - /** - * Move the cursor to the previous tab stop from the given position (default is current). - * @param {number} x The position to move the cursor to the previous tab stop. - */ - public prevStop(x?: number): number { - if (x == null) x = this.buffer.x; - while (!this.buffer.tabs[--x] && x > 0); - return x >= this.cols ? this.cols - 1 : x < 0 ? 0 : x; - } - - /** - * Move the cursor one tab stop forward from the given position (default is current). - * @param {number} x The position to move the cursor one tab stop forward. - */ - public nextStop(x?: number): number { - if (x == null) x = this.buffer.x; - while (!this.buffer.tabs[++x] && x < this.cols); - return x >= this.cols ? this.cols - 1 : x < 0 ? 0 : x; - } - /** * Erase in the identified line everything from "x" to the end of the line (right). * @param {number} x The column from which to start erasing to the end of the line. diff --git a/src/utils/TestUtils.test.ts b/src/utils/TestUtils.test.ts index 8d475560..ae83aed4 100644 --- a/src/utils/TestUtils.test.ts +++ b/src/utils/TestUtils.test.ts @@ -189,6 +189,12 @@ export class MockBuffer implements IBuffer { translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol?: number, endCol?: number): string { throw new Error('Method not implemented.'); } + nextStop(x?: number): number { + throw new Error('Method not implemented.'); + } + prevStop(x?: number): number { + throw new Error('Method not implemented.'); + } } export class MockViewport implements IViewport { From c6d4c73c8a8c23ce102918fc0342cf33d2986711 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 30 Aug 2017 14:50:07 -0700 Subject: [PATCH 107/378] Initial prototype --- src/Interfaces.ts | 2 ++ src/Renderer.ts | 76 ++++++++++++++++++++++++++++++++++++++++++++++- src/Terminal.ts | 13 ++++++++ 3 files changed, 90 insertions(+), 1 deletion(-) diff --git a/src/Interfaces.ts b/src/Interfaces.ts index fb66f4d7..b643de45 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -21,6 +21,8 @@ export interface ITerminal extends IEventEmitter { element: HTMLElement; rowContainer: HTMLElement; selectionContainer: HTMLElement; + canvasElement: HTMLCanvasElement; + canvasContext: CanvasRenderingContext2D; selectionManager: ISelectionManager; charMeasure: ICharMeasure; textarea: HTMLTextAreaElement; diff --git a/src/Renderer.ts b/src/Renderer.ts index b51a9ff9..1586d8fe 100644 --- a/src/Renderer.ts +++ b/src/Renderer.ts @@ -97,7 +97,8 @@ export class Renderer { } this._refreshRowsQueue = []; this._refreshAnimationFrame = null; - this._refresh(start, end); + // this._refresh(start, end); + this._canvasRender(start, end); } /** @@ -323,6 +324,79 @@ export class Renderer { this._terminal.emit('refresh', {start, end}); }; + private _imageDataCache = {}; + private _colors = [ + // dark: + '#2e3436', + '#cc0000', + '#4e9a06', + '#c4a000', + '#3465a4', + '#75507b', + '#06989a', + '#d3d7cf', + // bright: + '#555753', + '#ef2929', + '#8ae234', + '#fce94f', + '#729fcf', + '#ad7fa8', + '#34e2e2', + '#eeeeec' + ]; + + private _canvasRender(start: number, end: number): void { + const charWidth = Math.ceil(this._terminal.charMeasure.width); + const charHeight = Math.ceil(this._terminal.charMeasure.height); + const ctx = this._terminal.canvasContext; + ctx.font = '16px Hack'; + ctx.fillStyle = '#000000'; + // console.log('fill', start, end); + // console.log('fill', start * charHeight, (end - start + 1) * charHeight); + ctx.fillRect(0, start * charHeight, charWidth * this._terminal.cols, (end - start + 1) * charHeight); + ctx.fillStyle = 'rgb(255, 255, 255)'; + ctx.textBaseline = 'top'; + + for (let y = start; y <= end; y++) { + let row = y + this._terminal.buffer.ydisp; + let line = this._terminal.buffer.lines.get(row); + for (let x = 0; x < this._terminal.cols; x++) { + let data: any = line[x][0]; + const ch = line[x][CHAR_DATA_CHAR_INDEX]; + + // if (ch === ' ') { + // continue; + // } + + let bg = data & 0x1ff; + let fg = (data >> 9) & 0x1ff; + let flags = data >> 18; + + // if (bg < 16) { + // } + + if (fg < 16) { + ctx.fillStyle = this._colors[fg]; + } + + // let imageData; + // let key = ch + fg; + // if (key in this._imageDataCache) { + // imageData = this._imageDataCache[key]; + // } else { + ctx.fillText(ch, x * charWidth, y * charHeight); + // imageData = ctx.getImageData(x * charWidth, y * charHeight, charWidth, charHeight); + // this._imageDataCache[key] = imageData; + // } + + // ctx.fillText(ch, x * charWidth, y * charHeight); + // ctx.putImageData(imageData, x * charWidth, y * charHeight); + } + } + this._imageDataCache = {}; + } + /** * Refreshes the selection in the DOM. * @param start The selection start. diff --git a/src/Terminal.ts b/src/Terminal.ts index 827273ff..0bb00ecf 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -175,6 +175,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT private body: HTMLBodyElement; private viewportScrollArea: HTMLElement; private viewportElement: HTMLElement; + public canvasElement: HTMLCanvasElement; + public canvasContext: CanvasRenderingContext2D; public selectionContainer: HTMLElement; private helperContainer: HTMLElement; private compositionView: HTMLElement; @@ -703,6 +705,12 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.selectionContainer.classList.add('xterm-selection'); this.element.appendChild(this.selectionContainer); + + this.canvasElement = document.createElement('canvas'); + this.canvasContext = this.canvasElement.getContext('2d'); + this.element.appendChild(this.canvasElement); + + // Create the container that will hold the lines of the terminal and then // produce the lines the lines. this.rowContainer = document.createElement('div'); @@ -746,6 +754,11 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT }); this.charMeasure.measure(); + this.charMeasure.on('charsizechanged', () => { + this.canvasElement.setAttribute('width', `${Math.ceil(this.charMeasure.width) * this.cols}px`); + this.canvasElement.setAttribute('height', `${Math.ceil(this.charMeasure.height) * this.rows}px`); + }); + this.viewport = new Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasure); this.renderer = new Renderer(this); this.selectionManager = new SelectionManager(this, this.buffer, this.rowContainer, this.charMeasure); From 640cd18515e07c93cac7fe08ad229df2073cc415 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 30 Aug 2017 17:07:30 -0700 Subject: [PATCH 108/378] Support bold, underline, fix colors --- src/Renderer.ts | 48 ++++--- src/RendererCanvas.ts | 294 ++++++++++++++++++++++++++++++++++++++++++ src/Terminal.ts | 13 +- 3 files changed, 337 insertions(+), 18 deletions(-) create mode 100644 src/RendererCanvas.ts diff --git a/src/Renderer.ts b/src/Renderer.ts index 1586d8fe..8c155096 100644 --- a/src/Renderer.ts +++ b/src/Renderer.ts @@ -97,7 +97,7 @@ export class Renderer { } this._refreshRowsQueue = []; this._refreshAnimationFrame = null; - // this._refresh(start, end); + this._refresh(start, end); this._canvasRender(start, end); } @@ -347,18 +347,26 @@ export class Renderer { ]; private _canvasRender(start: number, end: number): void { - const charWidth = Math.ceil(this._terminal.charMeasure.width); - const charHeight = Math.ceil(this._terminal.charMeasure.height); + + const charWidth = Math.ceil(this._terminal.charMeasure.width) * window.devicePixelRatio; + const charHeight = Math.ceil(this._terminal.charMeasure.height) * window.devicePixelRatio; const ctx = this._terminal.canvasContext; - ctx.font = '16px Hack'; + ctx.fillStyle = '#000000'; // console.log('fill', start, end); // console.log('fill', start * charHeight, (end - start + 1) * charHeight); - ctx.fillRect(0, start * charHeight, charWidth * this._terminal.cols, (end - start + 1) * charHeight); + // ctx.fillRect(0, start * charHeight, charWidth * this._terminal.cols, (end - start + 1) * charHeight); ctx.fillStyle = 'rgb(255, 255, 255)'; ctx.textBaseline = 'top'; + // Indicates whether to reset the font next cell + let resetFont = true; + for (let y = start; y <= end; y++) { + if (resetFont) { + ctx.font = `${16 * window.devicePixelRatio}px courier`; + resetFont = false; + } let row = y + this._terminal.buffer.ydisp; let line = this._terminal.buffer.lines.get(row); for (let x = 0; x < this._terminal.cols; x++) { @@ -376,25 +384,35 @@ export class Renderer { // if (bg < 16) { // } + if (flags & FLAGS.BOLD) { + ctx.font = `bold ${ctx.font}`; + resetFont = true; + // Convert the FG color to the bold variant + if (fg < 8) { + fg += 8; + } + } + if (fg < 16) { ctx.fillStyle = this._colors[fg]; } - // let imageData; - // let key = ch + fg; - // if (key in this._imageDataCache) { - // imageData = this._imageDataCache[key]; - // } else { + // Simulate cache + let imageData; + let key = ch + fg; + if (key in this._imageDataCache) { + imageData = this._imageDataCache[key]; + } else { ctx.fillText(ch, x * charWidth, y * charHeight); - // imageData = ctx.getImageData(x * charWidth, y * charHeight, charWidth, charHeight); - // this._imageDataCache[key] = imageData; - // } + imageData = ctx.getImageData(x * charWidth, y * charHeight, charWidth, charHeight); + this._imageDataCache[key] = imageData; + } + ctx.putImageData(imageData, x * charWidth, y * charHeight); + // Always write text // ctx.fillText(ch, x * charWidth, y * charHeight); - // ctx.putImageData(imageData, x * charWidth, y * charHeight); } } - this._imageDataCache = {}; } /** diff --git a/src/RendererCanvas.ts b/src/RendererCanvas.ts new file mode 100644 index 00000000..703d1464 --- /dev/null +++ b/src/RendererCanvas.ts @@ -0,0 +1,294 @@ +/** + * @license MIT + */ + +import { ITerminal } from './Interfaces'; +import { DomElementObjectPool } from './utils/DomElementObjectPool'; +import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from './Buffer'; + +/** + * The maximum number of refresh frames to skip when the write buffer is non- + * empty. Note that these frames may be intermingled with frames that are + * skipped via requestAnimationFrame's mechanism. + */ +const MAX_REFRESH_FRAME_SKIP = 5; + +/** + * Flags used to render terminal text properly. + */ +enum FLAGS { + BOLD = 1, + UNDERLINE = 2, + BLINK = 4, + INVERSE = 8, + INVISIBLE = 16 +}; + +let brokenBold: boolean = null; + +export class Renderer { + /** A queue of the rows to be refreshed */ + private _refreshRowsQueue: {start: number, end: number}[] = []; + private _refreshFramesSkipped = 0; + private _refreshAnimationFrame = null; + + constructor(private _terminal: ITerminal) { + // Figure out whether boldness affects + // the character width of monospace fonts. + if (brokenBold === null) { + brokenBold = checkBoldBroken(this._terminal.element); + } + + // TODO: Pull more DOM interactions into Renderer.constructor, element for + // example should be owned by Renderer (and also exposed by Terminal due to + // to established public API). + } + + /** + * Queues a refresh between two rows (inclusive), to be done on next animation + * frame. + * @param {number} start The start row. + * @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)); + } + } + + /** + * Performs the refresh loop callback, calling refresh only if a refresh is + * necessary before queueing up the next one. + */ + private _refreshLoop(): void { + // Skip MAX_REFRESH_FRAME_SKIP frames if the writeBuffer is non-empty as it + // will need to be immediately refreshed anyway. This saves a lot of + // rendering time as the viewport DOM does not need to be refreshed, no + // scroll events, no layouts, etc. + const skipFrame = this._terminal.writeBuffer.length > 0 && this._refreshFramesSkipped++ <= MAX_REFRESH_FRAME_SKIP; + if (skipFrame) { + this._refreshAnimationFrame = window.requestAnimationFrame(this._refreshLoop.bind(this)); + return; + } + + this._refreshFramesSkipped = 0; + 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; + this._refresh(start, end); + this._terminal.emit('refresh', {start, end}); + } + + private _imageDataCache = {}; + private _colors = [ + // dark: + '#2e3436', + '#cc0000', + '#4e9a06', + '#c4a000', + '#3465a4', + '#75507b', + '#06989a', + '#d3d7cf', + // bright: + '#555753', + '#ef2929', + '#8ae234', + '#fce94f', + '#729fcf', + '#ad7fa8', + '#34e2e2', + '#eeeeec' + ]; + + /** + * Refreshes (re-renders) terminal content within two rows (inclusive) + * + * Rendering Engine: + * + * In the screen buffer, each character is stored as a an array with a character + * and a 32-bit integer: + * - First value: a utf-16 character. + * - Second value: + * - Next 9 bits: background color (0-511). + * - Next 9 bits: foreground color (0-511). + * - Next 14 bits: a mask for misc. flags: + * - 1=bold + * - 2=underline + * - 4=blink + * - 8=inverse + * - 16=invisible + * + * @param {number} start The row to start from (between 0 and terminal's height terminal - 1) + * @param {number} end The row to end at (between fromRow and terminal's height terminal - 1) + */ + private _refresh(start: number, end: number): void { + const charWidth = Math.ceil(this._terminal.charMeasure.width) * window.devicePixelRatio; + const charHeight = Math.ceil(this._terminal.charMeasure.height) * window.devicePixelRatio; + const ctx = this._terminal.canvasContext; + + ctx.fillStyle = '#000000'; + // console.log('fill', start, end); + // console.log('fill', start * charHeight, (end - start + 1) * charHeight); + // ctx.fillRect(0, start * charHeight, charWidth * this._terminal.cols, (end - start + 1) * charHeight); + ctx.fillStyle = 'rgb(255, 255, 255)'; + ctx.textBaseline = 'top'; + + // Indicates whether to reset the font next cell + let resetFont = true; + + for (let y = start; y <= end; y++) { + if (resetFont) { + ctx.font = `${16 * window.devicePixelRatio}px courier`; + resetFont = false; + } + let row = y + this._terminal.buffer.ydisp; + let line = this._terminal.buffer.lines.get(row); + for (let x = 0; x < this._terminal.cols; x++) { + let data: number = line[x][0]; + const ch = line[x][CHAR_DATA_CHAR_INDEX]; + + // if (ch === ' ') { + // continue; + // } + + let bg = data & 0x1ff; + let fg = (data >> 9) & 0x1ff; + let flags = data >> 18; + + // if (bg < 16) { + // } + + if (flags & FLAGS.BOLD) { + ctx.font = `bold ${ctx.font}`; + resetFont = true; + // Convert the FG color to the bold variant + if (fg < 8) { + fg += 8; + } + } + + if (fg > 255) { + ctx.fillStyle = '#ffffff'; + } else if (fg > 15) { + // TODO: Support colors 16-255 + } else { + ctx.fillStyle = this._colors[fg]; + } + + // Simulate cache + let imageData; + let key = ch + data; + if (key in this._imageDataCache) { + imageData = this._imageDataCache[key]; + } else { + ctx.fillText(ch, x * charWidth, y * charHeight); + if (flags & FLAGS.UNDERLINE) { + ctx.fillRect(x * charWidth, (y + 1) * charHeight - window.devicePixelRatio, charWidth, window.devicePixelRatio); + } + imageData = ctx.getImageData(x * charWidth, y * charHeight, charWidth, charHeight); + this._imageDataCache[key] = imageData; + } + ctx.putImageData(imageData, x * charWidth, y * charHeight); + + // Always write text + // ctx.fillText(ch, x * charWidth, y * charHeight); + } + } + } + + /** + * Refreshes the selection in the DOM. + * @param start The selection start. + * @param end The selection end. + */ + public refreshSelection(start: [number, number], end: [number, number]): void { + // Remove all selections + while (this._terminal.selectionContainer.children.length) { + this._terminal.selectionContainer.removeChild(this._terminal.selectionContainer.children[0]); + } + + // Selection does not exist + if (!start || !end) { + return; + } + + // Translate from buffer position to viewport position + const viewportStartRow = start[1] - this._terminal.buffer.ydisp; + const viewportEndRow = end[1] - this._terminal.buffer.ydisp; + const viewportCappedStartRow = Math.max(viewportStartRow, 0); + const viewportCappedEndRow = Math.min(viewportEndRow, this._terminal.rows - 1); + + // No need to draw the selection + if (viewportCappedStartRow >= this._terminal.rows || viewportCappedEndRow < 0) { + return; + } + + // Create the selections + const documentFragment = document.createDocumentFragment(); + // Draw first row + const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0; + const endCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : this._terminal.cols; + documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow, startCol, endCol)); + // Draw middle rows + const middleRowsCount = viewportCappedEndRow - viewportCappedStartRow - 1; + documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow + 1, 0, this._terminal.cols, middleRowsCount)); + // Draw final row + if (viewportCappedStartRow !== viewportCappedEndRow) { + // Only draw viewportEndRow if it's not the same as viewporttartRow + const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : this._terminal.cols; + documentFragment.appendChild(this._createSelectionElement(viewportCappedEndRow, 0, endCol)); + } + this._terminal.selectionContainer.appendChild(documentFragment); + } + + /** + * Creates a selection element at the specified position. + * @param row The row of the selection. + * @param colStart The start column. + * @param colEnd The end columns. + */ + private _createSelectionElement(row: number, colStart: number, colEnd: number, rowCount: number = 1): HTMLElement { + const element = document.createElement('div'); + element.style.height = `${rowCount * this._terminal.charMeasure.height}px`; + element.style.top = `${row * this._terminal.charMeasure.height}px`; + element.style.left = `${colStart * this._terminal.charMeasure.width}px`; + element.style.width = `${this._terminal.charMeasure.width * (colEnd - colStart)}px`; + return element; + } +} + + +// If bold is broken, we can't use it in the terminal. +function checkBoldBroken(terminalElement: HTMLElement): boolean { + const document = terminalElement.ownerDocument; + const el = document.createElement('span'); + el.innerHTML = 'hello world'; + terminalElement.appendChild(el); + const w1 = el.offsetWidth; + const h1 = el.offsetHeight; + el.style.fontWeight = 'bold'; + const w2 = el.offsetWidth; + const h2 = el.offsetHeight; + terminalElement.removeChild(el); + return w1 !== w2 || h1 !== h2; +} diff --git a/src/Terminal.ts b/src/Terminal.ts index 0bb00ecf..2ddba9a2 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -29,7 +29,8 @@ import { CircularList } from './utils/CircularList'; import { C0 } from './EscapeSequences'; import { InputHandler } from './InputHandler'; import { Parser } from './Parser'; -import { Renderer } from './Renderer'; +// import { Renderer } from './Renderer'; +import { Renderer } from './RendererCanvas'; import { Linkifier } from './Linkifier'; import { SelectionManager } from './SelectionManager'; import { CharMeasure } from './utils/CharMeasure'; @@ -708,6 +709,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.canvasElement = document.createElement('canvas'); this.canvasContext = this.canvasElement.getContext('2d'); + // Scale the context for HDPI screens + this.canvasContext.scale(window.devicePixelRatio, window.devicePixelRatio); this.element.appendChild(this.canvasElement); @@ -755,8 +758,12 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.charMeasure.measure(); this.charMeasure.on('charsizechanged', () => { - this.canvasElement.setAttribute('width', `${Math.ceil(this.charMeasure.width) * this.cols}px`); - this.canvasElement.setAttribute('height', `${Math.ceil(this.charMeasure.height) * this.rows}px`); + const width = Math.ceil(this.charMeasure.width) * this.cols; + const height = Math.ceil(this.charMeasure.height) * this.rows; + this.canvasElement.width = width * window.devicePixelRatio; + this.canvasElement.height = height * window.devicePixelRatio; + this.canvasElement.style.width = `${width}px`; + this.canvasElement.style.height = `${height}px`; }); this.viewport = new Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasure); From e98794a0a75fe6809bfe4f684fb65a0c53f7785f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 30 Aug 2017 17:27:04 -0700 Subject: [PATCH 109/378] progress --- src/RendererCanvas.ts | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/RendererCanvas.ts b/src/RendererCanvas.ts index 703d1464..0665b880 100644 --- a/src/RendererCanvas.ts +++ b/src/RendererCanvas.ts @@ -98,6 +98,7 @@ export class Renderer { this._terminal.emit('refresh', {start, end}); } + // TODO: This would be better as a large texture atlas rather than a cache of ImageData objects private _imageDataCache = {}; private _colors = [ // dark: @@ -150,20 +151,16 @@ export class Renderer { // console.log('fill', start, end); // console.log('fill', start * charHeight, (end - start + 1) * charHeight); // ctx.fillRect(0, start * charHeight, charWidth * this._terminal.cols, (end - start + 1) * charHeight); - ctx.fillStyle = 'rgb(255, 255, 255)'; + ctx.fillStyle = '#ffffff'; ctx.textBaseline = 'top'; - - // Indicates whether to reset the font next cell - let resetFont = true; + ctx.font = `${16 * window.devicePixelRatio}px courier`; for (let y = start; y <= end; y++) { - if (resetFont) { - ctx.font = `${16 * window.devicePixelRatio}px courier`; - resetFont = false; - } let row = y + this._terminal.buffer.ydisp; let line = this._terminal.buffer.lines.get(row); for (let x = 0; x < this._terminal.cols; x++) { + ctx.save(); + let data: number = line[x][0]; const ch = line[x][CHAR_DATA_CHAR_INDEX]; @@ -180,19 +177,16 @@ export class Renderer { if (flags & FLAGS.BOLD) { ctx.font = `bold ${ctx.font}`; - resetFont = true; // Convert the FG color to the bold variant if (fg < 8) { fg += 8; } } - if (fg > 255) { - ctx.fillStyle = '#ffffff'; - } else if (fg > 15) { - // TODO: Support colors 16-255 - } else { + if (fg < 16) { ctx.fillStyle = this._colors[fg]; + } else if (fg < 256) { + // TODO: Support colors 16-255 } // Simulate cache @@ -212,6 +206,7 @@ export class Renderer { // Always write text // ctx.fillText(ch, x * charWidth, y * charHeight); + ctx.restore(); } } } From 5ce39d3c816404834581b811b789098211b262f8 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 30 Aug 2017 19:12:26 -0700 Subject: [PATCH 110/378] GPU ascii rendering --- src/InputHandler.ts | 15 ++-- src/Interfaces.ts | 2 - src/Renderer.ts | 70 --------------- src/RendererCanvas.ts | 199 ++++++++++++++++++++++++++++++++--------- src/Terminal.ts | 21 +---- src/renderer/Canvas.ts | 14 +++ 6 files changed, 179 insertions(+), 142 deletions(-) create mode 100644 src/renderer/Canvas.ts diff --git a/src/InputHandler.ts b/src/InputHandler.ts index abfdfa0e..e9346c1a 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -36,13 +36,14 @@ export class InputHandler implements IInputHandler { // dont overflow left if (this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1]) { if (!this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][CHAR_DATA_WIDTH_INDEX]) { - // found empty cell after fullwidth, need to go 2 cells back - if (this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 2]) + if (this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 2]) { this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 2][CHAR_DATA_CHAR_INDEX] += char; - + this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 2][3] = char.charCodeAt(0); + } } else { this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][CHAR_DATA_CHAR_INDEX] += char; + this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][3] = char.charCodeAt(0); } this._terminal.updateRange(this._terminal.buffer.y); } @@ -81,21 +82,21 @@ export class InputHandler implements IInputHandler { if (removed[CHAR_DATA_WIDTH_INDEX] === 0 && this._terminal.buffer.lines.get(row)[this._terminal.cols - 2] && this._terminal.buffer.lines.get(row)[this._terminal.cols - 2][CHAR_DATA_WIDTH_INDEX] === 2) { - this._terminal.buffer.lines.get(row)[this._terminal.cols - 2] = [this._terminal.curAttr, ' ', 1]; + this._terminal.buffer.lines.get(row)[this._terminal.cols - 2] = [this._terminal.curAttr, ' ', 1, ' '.charCodeAt(0)]; } // insert empty cell at cursor - this._terminal.buffer.lines.get(row).splice(this._terminal.buffer.x, 0, [this._terminal.curAttr, ' ', 1]); + this._terminal.buffer.lines.get(row).splice(this._terminal.buffer.x, 0, [this._terminal.curAttr, ' ', 1, ' '.charCodeAt(0)]); } } - this._terminal.buffer.lines.get(row)[this._terminal.buffer.x] = [this._terminal.curAttr, char, ch_width]; + this._terminal.buffer.lines.get(row)[this._terminal.buffer.x] = [this._terminal.curAttr, char, ch_width, char.charCodeAt(0)]; this._terminal.buffer.x++; this._terminal.updateRange(this._terminal.buffer.y); // fullwidth char - set next cell width to zero and advance cursor if (ch_width === 2) { - this._terminal.buffer.lines.get(row)[this._terminal.buffer.x] = [this._terminal.curAttr, '', 0]; + this._terminal.buffer.lines.get(row)[this._terminal.buffer.x] = [this._terminal.curAttr, '', 0, undefined]; this._terminal.buffer.x++; } } diff --git a/src/Interfaces.ts b/src/Interfaces.ts index b643de45..fb66f4d7 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -21,8 +21,6 @@ export interface ITerminal extends IEventEmitter { element: HTMLElement; rowContainer: HTMLElement; selectionContainer: HTMLElement; - canvasElement: HTMLCanvasElement; - canvasContext: CanvasRenderingContext2D; selectionManager: ISelectionManager; charMeasure: ICharMeasure; textarea: HTMLTextAreaElement; diff --git a/src/Renderer.ts b/src/Renderer.ts index 8c155096..f4a275bb 100644 --- a/src/Renderer.ts +++ b/src/Renderer.ts @@ -98,7 +98,6 @@ export class Renderer { this._refreshRowsQueue = []; this._refreshAnimationFrame = null; this._refresh(start, end); - this._canvasRender(start, end); } /** @@ -346,75 +345,6 @@ export class Renderer { '#eeeeec' ]; - private _canvasRender(start: number, end: number): void { - - const charWidth = Math.ceil(this._terminal.charMeasure.width) * window.devicePixelRatio; - const charHeight = Math.ceil(this._terminal.charMeasure.height) * window.devicePixelRatio; - const ctx = this._terminal.canvasContext; - - ctx.fillStyle = '#000000'; - // console.log('fill', start, end); - // console.log('fill', start * charHeight, (end - start + 1) * charHeight); - // ctx.fillRect(0, start * charHeight, charWidth * this._terminal.cols, (end - start + 1) * charHeight); - ctx.fillStyle = 'rgb(255, 255, 255)'; - ctx.textBaseline = 'top'; - - // Indicates whether to reset the font next cell - let resetFont = true; - - for (let y = start; y <= end; y++) { - if (resetFont) { - ctx.font = `${16 * window.devicePixelRatio}px courier`; - resetFont = false; - } - let row = y + this._terminal.buffer.ydisp; - let line = this._terminal.buffer.lines.get(row); - for (let x = 0; x < this._terminal.cols; x++) { - let data: any = line[x][0]; - const ch = line[x][CHAR_DATA_CHAR_INDEX]; - - // if (ch === ' ') { - // continue; - // } - - let bg = data & 0x1ff; - let fg = (data >> 9) & 0x1ff; - let flags = data >> 18; - - // if (bg < 16) { - // } - - if (flags & FLAGS.BOLD) { - ctx.font = `bold ${ctx.font}`; - resetFont = true; - // Convert the FG color to the bold variant - if (fg < 8) { - fg += 8; - } - } - - if (fg < 16) { - ctx.fillStyle = this._colors[fg]; - } - - // Simulate cache - let imageData; - let key = ch + fg; - if (key in this._imageDataCache) { - imageData = this._imageDataCache[key]; - } else { - ctx.fillText(ch, x * charWidth, y * charHeight); - imageData = ctx.getImageData(x * charWidth, y * charHeight, charWidth, charHeight); - this._imageDataCache[key] = imageData; - } - ctx.putImageData(imageData, x * charWidth, y * charHeight); - - // Always write text - // ctx.fillText(ch, x * charWidth, y * charHeight); - } - } - } - /** * Refreshes the selection in the DOM. * @param start The selection start. diff --git a/src/RendererCanvas.ts b/src/RendererCanvas.ts index 0665b880..9d4156c4 100644 --- a/src/RendererCanvas.ts +++ b/src/RendererCanvas.ts @@ -5,6 +5,7 @@ import { ITerminal } from './Interfaces'; import { DomElementObjectPool } from './utils/DomElementObjectPool'; import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from './Buffer'; +import { createBackgroundFillData } from './renderer/Canvas'; /** * The maximum number of refresh frames to skip when the write buffer is non- @@ -32,6 +33,38 @@ export class Renderer { private _refreshFramesSkipped = 0; private _refreshAnimationFrame = null; + private _canvasElement: HTMLCanvasElement; + private _canvasContext: CanvasRenderingContext2D; + private _offscreenCanvas: HTMLCanvasElement; + private _offscreenContext: CanvasRenderingContext2D; + + private _charImageDataAtlas: ImageData; + private _charImageDataAtlasBitmap;//: ImageBitmap; + + // TODO: This would be better as a large texture atlas rather than a cache of ImageData objects + private _imageDataCache = {}; + private _colors = [ + // dark: + '#2e3436', + '#cc0000', + '#4e9a06', + '#c4a000', + '#3465a4', + '#75507b', + '#06989a', + '#d3d7cf', + // bright: + '#555753', + '#ef2929', + '#8ae234', + '#fce94f', + '#729fcf', + '#ad7fa8', + '#34e2e2', + '#eeeeec' + ]; + private _imageData: ImageData; + constructor(private _terminal: ITerminal) { // Figure out whether boldness affects // the character width of monospace fonts. @@ -39,11 +72,65 @@ export class Renderer { brokenBold = checkBoldBroken(this._terminal.element); } + + this._offscreenCanvas = document.createElement('canvas'); + this._offscreenContext = this._offscreenCanvas.getContext('2d'); + + this._canvasElement = document.createElement('canvas'); + this._canvasContext = this._canvasElement.getContext('2d'); + // Scale the context for HDPI screens + this._canvasContext.scale(window.devicePixelRatio, window.devicePixelRatio); + this._terminal.element.appendChild(this._canvasElement); + // TODO: Pull more DOM interactions into Renderer.constructor, element for // example should be owned by Renderer (and also exposed by Terminal due to // to established public API). } + public onResize(cols: number, rows: number): void { + // TODO: Could be triggered immediately after onCharSizeChanged + } + + public onCharSizeChanged(charWidth: number, charHeight: number): void { + const width = Math.ceil(charWidth) * this._terminal.cols; + const height = Math.ceil(charHeight) * this._terminal.rows; + this._canvasElement.width = width * window.devicePixelRatio; + this._canvasElement.height = height * window.devicePixelRatio; + this._canvasElement.style.width = `${width}px`; + this._canvasElement.style.height = `${height}px`; + + this._offscreenCanvas.width = 255 * charWidth * window.devicePixelRatio; + this._offscreenCanvas.height = (/*default*/1 + /*0-15*/16) * charHeight * window.devicePixelRatio; + this._refreshCharImageDataAtlas(); + } + + private _refreshCharImageDataAtlas(): void { + const scaledCharWidth = Math.ceil(this._terminal.charMeasure.width) * window.devicePixelRatio; + const scaledCharHeight = Math.ceil(this._terminal.charMeasure.height) * window.devicePixelRatio; + + this._offscreenContext.save(); + this._offscreenContext.fillStyle = '#ffffff'; + this._offscreenContext.font = `${16 * window.devicePixelRatio}px courier`; + this._offscreenContext.textBaseline = 'top'; + // Default color + for (let i = 0; i < 256; i++) { + this._offscreenContext.fillText(String.fromCharCode(i), i * scaledCharWidth, 0); + } + // Colors 0-15 + for (let colorIndex = 0; colorIndex < 16; colorIndex++) { + for (let i = 0; i < 256; i++) { + this._offscreenContext.fillStyle = this._colors[colorIndex]; + this._offscreenContext.fillText(String.fromCharCode(i), i * scaledCharWidth, (colorIndex + 1) * scaledCharHeight); + } + } + this._offscreenContext.restore(); + + this._charImageDataAtlas = this._offscreenContext.getImageData(0, 0, this._offscreenCanvas.width, this._offscreenCanvas.height); + (window).createImageBitmap(this._charImageDataAtlas).then(bitmap => { + this._charImageDataAtlasBitmap = bitmap; + }); + } + /** * Queues a refresh between two rows (inclusive), to be done on next animation * frame. @@ -98,29 +185,6 @@ export class Renderer { this._terminal.emit('refresh', {start, end}); } - // TODO: This would be better as a large texture atlas rather than a cache of ImageData objects - private _imageDataCache = {}; - private _colors = [ - // dark: - '#2e3436', - '#cc0000', - '#4e9a06', - '#c4a000', - '#3465a4', - '#75507b', - '#06989a', - '#d3d7cf', - // bright: - '#555753', - '#ef2929', - '#8ae234', - '#fce94f', - '#729fcf', - '#ad7fa8', - '#34e2e2', - '#eeeeec' - ]; - /** * Refreshes (re-renders) terminal content within two rows (inclusive) * @@ -143,11 +207,22 @@ export class Renderer { * @param {number} end The row to end at (between fromRow and terminal's height terminal - 1) */ private _refresh(start: number, end: number): void { - const charWidth = Math.ceil(this._terminal.charMeasure.width) * window.devicePixelRatio; - const charHeight = Math.ceil(this._terminal.charMeasure.height) * window.devicePixelRatio; - const ctx = this._terminal.canvasContext; + const scaledCharWidth = Math.ceil(this._terminal.charMeasure.width) * window.devicePixelRatio; + const scaledCharHeight = Math.ceil(this._terminal.charMeasure.height) * window.devicePixelRatio; + const ctx = this._canvasContext; + + // TODO: Needs to react to terminal resize + // Initialize image data + if (!this._imageData) { + this._imageData = ctx.createImageData(scaledCharWidth * this._terminal.cols * window.devicePixelRatio, scaledCharHeight * this._terminal.rows * window.devicePixelRatio); + this._imageData.data.set(createBackgroundFillData(this._imageData.width, this._imageData.height, 255, 0, 0, 255)); + } + + // Don't bother rendering until the atlas bitmap is ready + if (!this._charImageDataAtlasBitmap) { + return; + } - ctx.fillStyle = '#000000'; // console.log('fill', start, end); // console.log('fill', start * charHeight, (end - start + 1) * charHeight); // ctx.fillRect(0, start * charHeight, charWidth * this._terminal.cols, (end - start + 1) * charHeight); @@ -155,6 +230,9 @@ export class Renderer { ctx.textBaseline = 'top'; ctx.font = `${16 * window.devicePixelRatio}px courier`; + // Clear out the old data + ctx.clearRect(0, start * scaledCharHeight, scaledCharWidth * this._terminal.cols, (end - start + 1) * scaledCharHeight); + for (let y = start; y <= end; y++) { let row = y + this._terminal.buffer.ydisp; let line = this._terminal.buffer.lines.get(row); @@ -162,7 +240,8 @@ export class Renderer { ctx.save(); let data: number = line[x][0]; - const ch = line[x][CHAR_DATA_CHAR_INDEX]; + // const ch = line[x][CHAR_DATA_CHAR_INDEX]; + const code: number = line[x][3]; // if (ch === ' ') { // continue; @@ -183,32 +262,64 @@ export class Renderer { } } + // if (fg < 16) { + // ctx.fillStyle = this._colors[fg]; + // } else if (fg < 256) { + // // TODO: Support colors 16-255 + // } + + let colorIndex = 0; if (fg < 16) { - ctx.fillStyle = this._colors[fg]; - } else if (fg < 256) { - // TODO: Support colors 16-255 + colorIndex = fg + 1; } // Simulate cache - let imageData; - let key = ch + data; - if (key in this._imageDataCache) { - imageData = this._imageDataCache[key]; - } else { - ctx.fillText(ch, x * charWidth, y * charHeight); - if (flags & FLAGS.UNDERLINE) { - ctx.fillRect(x * charWidth, (y + 1) * charHeight - window.devicePixelRatio, charWidth, window.devicePixelRatio); - } - imageData = ctx.getImageData(x * charWidth, y * charHeight, charWidth, charHeight); - this._imageDataCache[key] = imageData; - } - ctx.putImageData(imageData, x * charWidth, y * charHeight); + // let imageData; + // let key = ch + data; + // if (key in this._imageDataCache) { + // imageData = this._imageDataCache[key]; + // } else { + // ctx.fillText(ch, x * scaledCharWidth, y * scaledCharHeight); + // if (flags & FLAGS.UNDERLINE) { + // ctx.fillRect(x * scaledCharWidth, (y + 1) * scaledCharHeight - window.devicePixelRatio, scaledCharWidth, window.devicePixelRatio); + // } + // imageData = ctx.getImageData(x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); + // this._imageDataCache[key] = imageData; + // } + // ctx.putImageData(imageData, x * scaledCharWidth, y * scaledCharHeight); + + // TODO: Try to get atlas working + // This seems too slow :( + //ctx.putImageData(this._charImageDataAtlas, x * scaledCharWidth - ch.charCodeAt(0) * scaledCharWidth, y * scaledCharHeight, ch.charCodeAt(0) * scaledCharWidth, 0, scaledCharWidth, scaledCharHeight); + + // TODO: Draw background + // Maybe background should be on a separate layer? + // ctx.save(); + // if (bg < 16) { + // ctx.fillStyle = this._colors[fg]; + // } else { + // ctx.fillStyle = '#000000'; + // } + // ctx.fillRect(x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); + // ctx.restore(); + + // ctx.drawImage(this._offscreenCanvas, code * scaledCharWidth, 0, scaledCharWidth, scaledCharHeight, x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); + + // ImageBitmap's draw about twice as fast as from a canvas + ctx.drawImage(this._charImageDataAtlasBitmap, code * scaledCharWidth, colorIndex * scaledCharHeight, scaledCharWidth, scaledCharHeight, x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); // Always write text // ctx.fillText(ch, x * charWidth, y * charHeight); ctx.restore(); } + + + // TODO: Try to get atlas working + // ctx.putImageData(this._charImageDataAtlas, y * scaledCharWidth, y * scaledCharHeight, 65 * scaledCharWidth, 0, scaledCharWidth, scaledCharHeight); } + + // This draws the atlas (for debugging purposes) + //ctx.putImageData(this._charImageDataAtlas, 0, 0); } /** diff --git a/src/Terminal.ts b/src/Terminal.ts index 2ddba9a2..42909298 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -176,8 +176,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT private body: HTMLBodyElement; private viewportScrollArea: HTMLElement; private viewportElement: HTMLElement; - public canvasElement: HTMLCanvasElement; - public canvasContext: CanvasRenderingContext2D; public selectionContainer: HTMLElement; private helperContainer: HTMLElement; private compositionView: HTMLElement; @@ -706,14 +704,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.selectionContainer.classList.add('xterm-selection'); this.element.appendChild(this.selectionContainer); - - this.canvasElement = document.createElement('canvas'); - this.canvasContext = this.canvasElement.getContext('2d'); - // Scale the context for HDPI screens - this.canvasContext.scale(window.devicePixelRatio, window.devicePixelRatio); - this.element.appendChild(this.canvasElement); - - // Create the container that will hold the lines of the terminal and then // produce the lines the lines. this.rowContainer = document.createElement('div'); @@ -757,17 +747,10 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT }); this.charMeasure.measure(); - this.charMeasure.on('charsizechanged', () => { - const width = Math.ceil(this.charMeasure.width) * this.cols; - const height = Math.ceil(this.charMeasure.height) * this.rows; - this.canvasElement.width = width * window.devicePixelRatio; - this.canvasElement.height = height * window.devicePixelRatio; - this.canvasElement.style.width = `${width}px`; - this.canvasElement.style.height = `${height}px`; - }); - this.viewport = new Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasure); this.renderer = new Renderer(this); + this.on('resize', () => this.renderer.onResize(this.cols, this.rows)); + this.charMeasure.on('charsizechanged', () => this.renderer.onCharSizeChanged(this.charMeasure.width, this.charMeasure.height)); this.selectionManager = new SelectionManager(this, this.buffer, this.rowContainer, this.charMeasure); this.selectionManager.on('refresh', data => { this.renderer.refreshSelection(data.start, data.end); diff --git a/src/renderer/Canvas.ts b/src/renderer/Canvas.ts new file mode 100644 index 00000000..97e7f9ea --- /dev/null +++ b/src/renderer/Canvas.ts @@ -0,0 +1,14 @@ +export function createBackgroundFillData(width: number, height: number, r: number, g: number, b: number, a: number): Uint8ClampedArray { + const data = new Uint8ClampedArray(width * height * 4); + let offset = 0; + for (let i = 0; i < height; i++) { + for (let j = 0; j < width; j++) { + data[offset] = r; + data[offset + 1] = g; + data[offset + 2] = b; + data[offset + 3] = a; + offset += 4; + } + } + return data; +} From 137847e85c37668e6fa9120f4df233c9bab814bc Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 30 Aug 2017 19:27:37 -0700 Subject: [PATCH 111/378] Linkify all rows at once to prevent multiple timeout restarts --- src/Linkifier.ts | 24 ++++++++++++++++++++++++ src/Terminal.ts | 7 ++++--- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/Linkifier.ts b/src/Linkifier.ts index 2323f226..5a0fc1e1 100644 --- a/src/Linkifier.ts +++ b/src/Linkifier.ts @@ -47,6 +47,7 @@ export class Linkifier { private _document: Document; private _rows: HTMLElement[]; private _rowTimeoutIds: number[]; + private _rowsTimeoutId: number; private _nextLinkMatcherId = HYPERTEXT_LINK_MATCHER_ID; constructor() { @@ -65,6 +66,29 @@ export class Linkifier { this._rows = rows; } + public linkifyRows(start: number, end: number): void { + // for (let i = start; i <= end; i++) { + // this.linkifyRow(i); + // } + + + // Don't attempt linkify if not yet attached to DOM + if (!this._document) { + return; + } + + if (this._rowsTimeoutId) { + clearTimeout(this._rowsTimeoutId); + } + this._rowsTimeoutId = setTimeout(this._linkifyRows.bind(this, start, end), Linkifier.TIME_BEFORE_LINKIFY); + } + + private _linkifyRows(start: number, end: number): void { + for (let i = start; i <= end; i++) { + this._linkifyRow(i); + } + } + /** * Queues a row for linkification. * @param {number} rowIndex The index of the row to linkify. diff --git a/src/Terminal.ts b/src/Terminal.ts index 42909298..c076d410 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1146,9 +1146,10 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT */ private queueLinkification(start: number, end: number): void { if (this.linkifier) { - for (let i = start; i <= end; i++) { - this.linkifier.linkifyRow(i); - } + this.linkifier.linkifyRows(0, this.rows); + // for (let i = start; i <= end; i++) { + // this.linkifier.linkifyRow(i); + // } } } From 0db42e2056ca343a3d4a7ebfaf65b1da6043abb1 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 30 Aug 2017 21:13:57 -0700 Subject: [PATCH 112/378] Pull bg rendering into an IRenderLayer --- src/Buffer.ts | 1 + src/RendererCanvas.ts | 95 ++++++++++++++------------- src/renderer/BackgroundRenderLayer.ts | 70 ++++++++++++++++++++ src/renderer/Interfaces.ts | 6 ++ src/xterm.css | 17 +++++ 5 files changed, 144 insertions(+), 45 deletions(-) create mode 100644 src/renderer/BackgroundRenderLayer.ts create mode 100644 src/renderer/Interfaces.ts diff --git a/src/Buffer.ts b/src/Buffer.ts index 4c730063..e8ceebf3 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -6,6 +6,7 @@ import { ITerminal, IBuffer } from './Interfaces'; import { CircularList } from './utils/CircularList'; import { LineData, CharData } from './Types'; +export const CHAR_DATA_ATTR_INDEX = 0; export const CHAR_DATA_CHAR_INDEX = 1; export const CHAR_DATA_WIDTH_INDEX = 2; diff --git a/src/RendererCanvas.ts b/src/RendererCanvas.ts index 9d4156c4..93466221 100644 --- a/src/RendererCanvas.ts +++ b/src/RendererCanvas.ts @@ -6,6 +6,8 @@ import { ITerminal } from './Interfaces'; import { DomElementObjectPool } from './utils/DomElementObjectPool'; import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from './Buffer'; import { createBackgroundFillData } from './renderer/Canvas'; +import { IRenderLayer } from './renderer/Interfaces'; +import { BackgroundRenderLayer } from './renderer/BackgroundRenderLayer'; /** * The maximum number of refresh frames to skip when the write buffer is non- @@ -33,13 +35,14 @@ export class Renderer { private _refreshFramesSkipped = 0; private _refreshAnimationFrame = null; - private _canvasElement: HTMLCanvasElement; - private _canvasContext: CanvasRenderingContext2D; + private _textCanvasElement: HTMLCanvasElement; + private _textCanvasContext: CanvasRenderingContext2D; private _offscreenCanvas: HTMLCanvasElement; private _offscreenContext: CanvasRenderingContext2D; - private _charImageDataAtlas: ImageData; - private _charImageDataAtlasBitmap;//: ImageBitmap; + private _renderLayers: IRenderLayer[]; + + private _charAtlasBitmap;//: ImageBitmap; // TODO: This would be better as a large texture atlas rather than a cache of ImageData objects private _imageDataCache = {}; @@ -72,15 +75,20 @@ export class Renderer { brokenBold = checkBoldBroken(this._terminal.element); } + this._textCanvasElement = document.createElement('canvas'); + this._textCanvasElement.classList.add('xterm-text-canvas'); + this._textCanvasContext = this._textCanvasElement.getContext('2d'); + this._textCanvasContext.scale(window.devicePixelRatio, window.devicePixelRatio); this._offscreenCanvas = document.createElement('canvas'); this._offscreenContext = this._offscreenCanvas.getContext('2d'); + this._offscreenContext.scale(window.devicePixelRatio, window.devicePixelRatio); - this._canvasElement = document.createElement('canvas'); - this._canvasContext = this._canvasElement.getContext('2d'); - // Scale the context for HDPI screens - this._canvasContext.scale(window.devicePixelRatio, window.devicePixelRatio); - this._terminal.element.appendChild(this._canvasElement); + this._renderLayers = [ + new BackgroundRenderLayer(this._terminal.element) + ]; + + this._terminal.element.appendChild(this._textCanvasElement); // TODO: Pull more DOM interactions into Renderer.constructor, element for // example should be owned by Renderer (and also exposed by Terminal due to @@ -94,14 +102,16 @@ export class Renderer { public onCharSizeChanged(charWidth: number, charHeight: number): void { const width = Math.ceil(charWidth) * this._terminal.cols; const height = Math.ceil(charHeight) * this._terminal.rows; - this._canvasElement.width = width * window.devicePixelRatio; - this._canvasElement.height = height * window.devicePixelRatio; - this._canvasElement.style.width = `${width}px`; - this._canvasElement.style.height = `${height}px`; - + this._textCanvasElement.width = width * window.devicePixelRatio; + this._textCanvasElement.height = height * window.devicePixelRatio; + this._textCanvasElement.style.width = `${width}px`; + this._textCanvasElement.style.height = `${height}px`; this._offscreenCanvas.width = 255 * charWidth * window.devicePixelRatio; this._offscreenCanvas.height = (/*default*/1 + /*0-15*/16) * charHeight * window.devicePixelRatio; this._refreshCharImageDataAtlas(); + for (let i = 0; i < this._renderLayers.length; i++) { + this._renderLayers[i].resize(width, height, charWidth, charHeight); + } } private _refreshCharImageDataAtlas(): void { @@ -118,6 +128,10 @@ export class Renderer { } // Colors 0-15 for (let colorIndex = 0; colorIndex < 16; colorIndex++) { + // colors 8-15 are bold + if (colorIndex === 8) { + this._offscreenContext.font = `bold ${this._offscreenContext.font}`; + } for (let i = 0; i < 256; i++) { this._offscreenContext.fillStyle = this._colors[colorIndex]; this._offscreenContext.fillText(String.fromCharCode(i), i * scaledCharWidth, (colorIndex + 1) * scaledCharHeight); @@ -125,10 +139,12 @@ export class Renderer { } this._offscreenContext.restore(); - this._charImageDataAtlas = this._offscreenContext.getImageData(0, 0, this._offscreenCanvas.width, this._offscreenCanvas.height); - (window).createImageBitmap(this._charImageDataAtlas).then(bitmap => { - this._charImageDataAtlasBitmap = bitmap; + const charAtlasImageData = this._offscreenContext.getImageData(0, 0, this._offscreenCanvas.width, this._offscreenCanvas.height); + (window).createImageBitmap(charAtlasImageData).then(bitmap => { + this._charAtlasBitmap = bitmap; }); + + this._offscreenContext.clearRect(0, 0, this._offscreenCanvas.width, this._offscreenCanvas.height); } /** @@ -182,6 +198,9 @@ export class Renderer { this._refreshRowsQueue = []; this._refreshAnimationFrame = null; this._refresh(start, end); + for (let i = 0; i < this._renderLayers.length; i++) { + this._renderLayers[i].render(this._terminal, start, end); + } this._terminal.emit('refresh', {start, end}); } @@ -209,35 +228,35 @@ export class Renderer { private _refresh(start: number, end: number): void { const scaledCharWidth = Math.ceil(this._terminal.charMeasure.width) * window.devicePixelRatio; const scaledCharHeight = Math.ceil(this._terminal.charMeasure.height) * window.devicePixelRatio; - const ctx = this._canvasContext; + const textCtx = this._textCanvasContext; // TODO: Needs to react to terminal resize // Initialize image data if (!this._imageData) { - this._imageData = ctx.createImageData(scaledCharWidth * this._terminal.cols * window.devicePixelRatio, scaledCharHeight * this._terminal.rows * window.devicePixelRatio); + this._imageData = textCtx.createImageData(scaledCharWidth * this._terminal.cols * window.devicePixelRatio, scaledCharHeight * this._terminal.rows * window.devicePixelRatio); this._imageData.data.set(createBackgroundFillData(this._imageData.width, this._imageData.height, 255, 0, 0, 255)); } // Don't bother rendering until the atlas bitmap is ready - if (!this._charImageDataAtlasBitmap) { + if (!this._charAtlasBitmap) { return; } // console.log('fill', start, end); // console.log('fill', start * charHeight, (end - start + 1) * charHeight); // ctx.fillRect(0, start * charHeight, charWidth * this._terminal.cols, (end - start + 1) * charHeight); - ctx.fillStyle = '#ffffff'; - ctx.textBaseline = 'top'; - ctx.font = `${16 * window.devicePixelRatio}px courier`; + textCtx.fillStyle = '#ffffff'; + textCtx.textBaseline = 'top'; + textCtx.font = `${16 * window.devicePixelRatio}px courier`; // Clear out the old data - ctx.clearRect(0, start * scaledCharHeight, scaledCharWidth * this._terminal.cols, (end - start + 1) * scaledCharHeight); + textCtx.clearRect(0, start * scaledCharHeight, scaledCharWidth * this._terminal.cols, (end - start + 1) * scaledCharHeight); for (let y = start; y <= end; y++) { let row = y + this._terminal.buffer.ydisp; let line = this._terminal.buffer.lines.get(row); for (let x = 0; x < this._terminal.cols; x++) { - ctx.save(); + textCtx.save(); let data: number = line[x][0]; // const ch = line[x][CHAR_DATA_CHAR_INDEX]; @@ -247,15 +266,12 @@ export class Renderer { // continue; // } - let bg = data & 0x1ff; + // let bg = data & 0x1ff; let fg = (data >> 9) & 0x1ff; let flags = data >> 18; - // if (bg < 16) { - // } - if (flags & FLAGS.BOLD) { - ctx.font = `bold ${ctx.font}`; + textCtx.font = `bold ${textCtx.font}`; // Convert the FG color to the bold variant if (fg < 8) { fg += 8; @@ -290,27 +306,16 @@ export class Renderer { // TODO: Try to get atlas working // This seems too slow :( - //ctx.putImageData(this._charImageDataAtlas, x * scaledCharWidth - ch.charCodeAt(0) * scaledCharWidth, y * scaledCharHeight, ch.charCodeAt(0) * scaledCharWidth, 0, scaledCharWidth, scaledCharHeight); - - // TODO: Draw background - // Maybe background should be on a separate layer? - // ctx.save(); - // if (bg < 16) { - // ctx.fillStyle = this._colors[fg]; - // } else { - // ctx.fillStyle = '#000000'; - // } - // ctx.fillRect(x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); - // ctx.restore(); + // ctx.putImageData(this._charImageDataAtlas, x * scaledCharWidth - ch.charCodeAt(0) * scaledCharWidth, y * scaledCharHeight, ch.charCodeAt(0) * scaledCharWidth, 0, scaledCharWidth, scaledCharHeight); // ctx.drawImage(this._offscreenCanvas, code * scaledCharWidth, 0, scaledCharWidth, scaledCharHeight, x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); // ImageBitmap's draw about twice as fast as from a canvas - ctx.drawImage(this._charImageDataAtlasBitmap, code * scaledCharWidth, colorIndex * scaledCharHeight, scaledCharWidth, scaledCharHeight, x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); + textCtx.drawImage(this._charAtlasBitmap, code * scaledCharWidth, colorIndex * scaledCharHeight, scaledCharWidth, scaledCharHeight, x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); // Always write text // ctx.fillText(ch, x * charWidth, y * charHeight); - ctx.restore(); + textCtx.restore(); } @@ -319,7 +324,7 @@ export class Renderer { } // This draws the atlas (for debugging purposes) - //ctx.putImageData(this._charImageDataAtlas, 0, 0); + // ctx.putImageData(this._charImageDataAtlas, 0, 0); } /** diff --git a/src/renderer/BackgroundRenderLayer.ts b/src/renderer/BackgroundRenderLayer.ts new file mode 100644 index 00000000..a624ef2e --- /dev/null +++ b/src/renderer/BackgroundRenderLayer.ts @@ -0,0 +1,70 @@ +import { IRenderLayer } from './Interfaces'; +import { IBuffer, ICharMeasure, ITerminal } from '../Interfaces'; +import { CHAR_DATA_ATTR_INDEX } from '../Buffer'; + +export class BackgroundRenderLayer implements IRenderLayer { + private _canvas: HTMLCanvasElement; + private _ctx: CanvasRenderingContext2D; + + // TODO: Pull colors into some other class + private _colors = [ + // dark: + '#2e3436', + '#cc0000', + '#4e9a06', + '#c4a000', + '#3465a4', + '#75507b', + '#06989a', + '#d3d7cf', + // bright: + '#555753', + '#ef2929', + '#8ae234', + '#fce94f', + '#729fcf', + '#ad7fa8', + '#34e2e2', + '#eeeeec' + ]; + + constructor(container: HTMLElement) { + this._canvas = document.createElement('canvas'); + this._canvas.classList.add('xterm-bg-canvas'); + this._ctx = this._canvas.getContext('2d'); + this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); + container.appendChild(this._canvas); + } + + public resize(canvasWidth: number, canvasHeight: number, charWidth: number, charHeight: number): void { + this._canvas.width = canvasWidth * window.devicePixelRatio; + this._canvas.height = canvasHeight * window.devicePixelRatio; + this._canvas.style.width = `${canvasWidth}px`; + this._canvas.style.height = `${canvasHeight}px`; + } + + public render(terminal: ITerminal, startRow: number, endRow: number): void { + const scaledCharWidth = Math.ceil(terminal.charMeasure.width) * window.devicePixelRatio; + const scaledCharHeight = Math.ceil(terminal.charMeasure.height) * window.devicePixelRatio; + + for (let y = startRow; y <= endRow; y++) { + let row = y + terminal.buffer.ydisp; + let line = terminal.buffer.lines.get(row); + for (let x = 0; x < terminal.cols; x++) { + const data: number = line[x][CHAR_DATA_ATTR_INDEX]; + const bg = data & 0x1ff; + const flags = data >> 18; + + // TODO: Draw background + if (bg < 16) { + this._ctx.save(); + this._ctx.fillStyle = this._colors[bg]; + this._ctx.fillRect(x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); + this._ctx.restore(); + } else { + this._ctx.clearRect(x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); + } + } + } + } +} diff --git a/src/renderer/Interfaces.ts b/src/renderer/Interfaces.ts new file mode 100644 index 00000000..6e0c3933 --- /dev/null +++ b/src/renderer/Interfaces.ts @@ -0,0 +1,6 @@ +import { ITerminal } from '../Interfaces'; + +export interface IRenderLayer { + resize(canvasWidth: number, canvasHeight: number, charWidth: number, charHeight: number): void; + render(terminal: ITerminal, startRow: number, endRow: number): void; +} diff --git a/src/xterm.css b/src/xterm.css index 14b0a58d..321cf6d6 100644 --- a/src/xterm.css +++ b/src/xterm.css @@ -158,10 +158,27 @@ display: inline-block; } +.terminal canvas { + position: absolute; + left: 0; + top: 0; +} + +.terminal .xterm-bg-canvas { + z-index: 0; +} + +.terminal .xterm-text-canvas { + z-index: 1; +} + .terminal .xterm-rows { position: absolute; left: 0; top: 0; + + /* Hide temporarily, this should be removed eventually */ + visibility: hidden; } .terminal .xterm-rows > div { From 5961bdbd9e773ffca83127abe3a98d7465a58a2b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 30 Aug 2017 21:44:18 -0700 Subject: [PATCH 113/378] typescript@2.3.0 for ImageBitmap interface --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9b550d67..21ab9b89 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "nodemon": "1.10.2", "sorcery": "^0.10.0", "tslint": "^4.0.2", - "typescript": "~2.2.0", + "typescript": "~2.3.0", "vinyl-buffer": "^1.0.0", "vinyl-source-stream": "^1.1.0" }, From 2cb6593b6b8070bd68f46e162ebf8f54cb9ef5f5 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 30 Aug 2017 21:44:41 -0700 Subject: [PATCH 114/378] Move fg rendering into IRenderLayer --- src/RendererCanvas.ts | 286 +++++++------------------- src/renderer/BackgroundRenderLayer.ts | 29 +-- src/renderer/Color.ts | 23 +++ src/renderer/ForegroundRenderLayer.ts | 180 ++++++++++++++++ src/renderer/Interfaces.ts | 2 +- src/renderer/Types.ts | 10 + src/xterm.css | 4 +- 7 files changed, 300 insertions(+), 234 deletions(-) create mode 100644 src/renderer/Color.ts create mode 100644 src/renderer/ForegroundRenderLayer.ts create mode 100644 src/renderer/Types.ts diff --git a/src/RendererCanvas.ts b/src/RendererCanvas.ts index 93466221..bf952b6c 100644 --- a/src/RendererCanvas.ts +++ b/src/RendererCanvas.ts @@ -8,6 +8,7 @@ import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from './Buffer'; import { createBackgroundFillData } from './renderer/Canvas'; import { IRenderLayer } from './renderer/Interfaces'; import { BackgroundRenderLayer } from './renderer/BackgroundRenderLayer'; +import { ForegroundRenderLayer } from './renderer/ForegroundRenderLayer'; /** * The maximum number of refresh frames to skip when the write buffer is non- @@ -16,79 +17,42 @@ import { BackgroundRenderLayer } from './renderer/BackgroundRenderLayer'; */ const MAX_REFRESH_FRAME_SKIP = 5; -/** - * Flags used to render terminal text properly. - */ -enum FLAGS { - BOLD = 1, - UNDERLINE = 2, - BLINK = 4, - INVERSE = 8, - INVISIBLE = 16 -}; - -let brokenBold: boolean = null; - export class Renderer { /** A queue of the rows to be refreshed */ private _refreshRowsQueue: {start: number, end: number}[] = []; private _refreshFramesSkipped = 0; private _refreshAnimationFrame = null; - private _textCanvasElement: HTMLCanvasElement; - private _textCanvasContext: CanvasRenderingContext2D; - private _offscreenCanvas: HTMLCanvasElement; - private _offscreenContext: CanvasRenderingContext2D; + // private _textCanvasElement: HTMLCanvasElement; + // private _textCanvasContext: CanvasRenderingContext2D; + // private _offscreenCanvas: HTMLCanvasElement; + // private _offscreenContext: CanvasRenderingContext2D; private _renderLayers: IRenderLayer[]; - private _charAtlasBitmap;//: ImageBitmap; + // private _charAtlasBitmap;//: ImageBitmap; // TODO: This would be better as a large texture atlas rather than a cache of ImageData objects - private _imageDataCache = {}; - private _colors = [ - // dark: - '#2e3436', - '#cc0000', - '#4e9a06', - '#c4a000', - '#3465a4', - '#75507b', - '#06989a', - '#d3d7cf', - // bright: - '#555753', - '#ef2929', - '#8ae234', - '#fce94f', - '#729fcf', - '#ad7fa8', - '#34e2e2', - '#eeeeec' - ]; - private _imageData: ImageData; + // private _imageDataCache = {}; + // private _imageData: ImageData; constructor(private _terminal: ITerminal) { // Figure out whether boldness affects // the character width of monospace fonts. - if (brokenBold === null) { - brokenBold = checkBoldBroken(this._terminal.element); - } + // if (brokenBold === null) { + // brokenBold = checkBoldBroken(this._terminal.element); + // } - this._textCanvasElement = document.createElement('canvas'); - this._textCanvasElement.classList.add('xterm-text-canvas'); - this._textCanvasContext = this._textCanvasElement.getContext('2d'); - this._textCanvasContext.scale(window.devicePixelRatio, window.devicePixelRatio); - - this._offscreenCanvas = document.createElement('canvas'); - this._offscreenContext = this._offscreenCanvas.getContext('2d'); - this._offscreenContext.scale(window.devicePixelRatio, window.devicePixelRatio); + // this._offscreenCanvas = document.createElement('canvas'); + // this._offscreenContext = this._offscreenCanvas.getContext('2d'); + // this._offscreenContext.scale(window.devicePixelRatio, window.devicePixelRatio); this._renderLayers = [ - new BackgroundRenderLayer(this._terminal.element) + new BackgroundRenderLayer(this._terminal.element), + new ForegroundRenderLayer(this._terminal.element) ]; - this._terminal.element.appendChild(this._textCanvasElement); + // this._terminal.element.appendChild(this._textCanvasElement); // TODO: Pull more DOM interactions into Renderer.constructor, element for // example should be owned by Renderer (and also exposed by Terminal due to @@ -96,56 +60,63 @@ export class Renderer { } public onResize(cols: number, rows: number): void { - // TODO: Could be triggered immediately after onCharSizeChanged + // TODO: This could be triggered immediately after onCharSizeChanged? + const charWidth = this._terminal.charMeasure.width; + const charHeight = this._terminal.charMeasure.height; + const width = Math.ceil(charWidth) * this._terminal.cols; + const height = Math.ceil(charHeight) * this._terminal.rows; + for (let i = 0; i < this._renderLayers.length; i++) { + this._renderLayers[i].resize(width, height, charWidth, charHeight, false); + } } public onCharSizeChanged(charWidth: number, charHeight: number): void { const width = Math.ceil(charWidth) * this._terminal.cols; const height = Math.ceil(charHeight) * this._terminal.rows; - this._textCanvasElement.width = width * window.devicePixelRatio; - this._textCanvasElement.height = height * window.devicePixelRatio; - this._textCanvasElement.style.width = `${width}px`; - this._textCanvasElement.style.height = `${height}px`; - this._offscreenCanvas.width = 255 * charWidth * window.devicePixelRatio; - this._offscreenCanvas.height = (/*default*/1 + /*0-15*/16) * charHeight * window.devicePixelRatio; - this._refreshCharImageDataAtlas(); + // this._textCanvasElement.width = width * window.devicePixelRatio; + // this._textCanvasElement.height = height * window.devicePixelRatio; + // this._textCanvasElement.style.width = `${width}px`; + // this._textCanvasElement.style.height = `${height}px`; + // this._offscreenCanvas.width = 255 * charWidth * window.devicePixelRatio; + // this._offscreenCanvas.height = (/*default*/1 + /*0-15*/16) * charHeight * window.devicePixelRatio; + // this._refreshCharImageDataAtlas(); for (let i = 0; i < this._renderLayers.length; i++) { - this._renderLayers[i].resize(width, height, charWidth, charHeight); + this._renderLayers[i].resize(width, height, charWidth, charHeight, true); } } - private _refreshCharImageDataAtlas(): void { - const scaledCharWidth = Math.ceil(this._terminal.charMeasure.width) * window.devicePixelRatio; - const scaledCharHeight = Math.ceil(this._terminal.charMeasure.height) * window.devicePixelRatio; + // private _refreshCharImageDataAtlas(): void { + // const scaledCharWidth = Math.ceil(this._terminal.charMeasure.width) * window.devicePixelRatio; + // const scaledCharHeight = Math.ceil(this._terminal.charMeasure.height) * window.devicePixelRatio; - this._offscreenContext.save(); - this._offscreenContext.fillStyle = '#ffffff'; - this._offscreenContext.font = `${16 * window.devicePixelRatio}px courier`; - this._offscreenContext.textBaseline = 'top'; - // Default color - for (let i = 0; i < 256; i++) { - this._offscreenContext.fillText(String.fromCharCode(i), i * scaledCharWidth, 0); - } - // Colors 0-15 - for (let colorIndex = 0; colorIndex < 16; colorIndex++) { - // colors 8-15 are bold - if (colorIndex === 8) { - this._offscreenContext.font = `bold ${this._offscreenContext.font}`; - } - for (let i = 0; i < 256; i++) { - this._offscreenContext.fillStyle = this._colors[colorIndex]; - this._offscreenContext.fillText(String.fromCharCode(i), i * scaledCharWidth, (colorIndex + 1) * scaledCharHeight); - } - } - this._offscreenContext.restore(); + // this._offscreenContext.save(); + // this._offscreenContext.fillStyle = '#ffffff'; + // this._offscreenContext.font = `${16 * window.devicePixelRatio}px courier`; + // this._offscreenContext.textBaseline = 'top'; + // // Default color + // for (let i = 0; i < 256; i++) { + // this._offscreenContext.fillText(String.fromCharCode(i), i * scaledCharWidth, 0); + // } + // // Colors 0-15 + // for (let colorIndex = 0; colorIndex < 16; colorIndex++) { + // // colors 8-15 are bold + // if (colorIndex === 8) { + // this._offscreenContext.font = `bold ${this._offscreenContext.font}`; + // } + // for (let i = 0; i < 256; i++) { + // this._offscreenContext.fillStyle = this._colors[colorIndex]; + // this._offscreenContext.fillText(String.fromCharCode(i), i * scaledCharWidth, (colorIndex + 1) * scaledCharHeight); + // } + // } + // this._offscreenContext.restore(); - const charAtlasImageData = this._offscreenContext.getImageData(0, 0, this._offscreenCanvas.width, this._offscreenCanvas.height); - (window).createImageBitmap(charAtlasImageData).then(bitmap => { - this._charAtlasBitmap = bitmap; - }); + // const charAtlasImageData = this._offscreenContext.getImageData(0, 0, this._offscreenCanvas.width, this._offscreenCanvas.height); + // (window).createImageBitmap(charAtlasImageData).then(bitmap => { + // this._charAtlasBitmap = bitmap; + // }); - this._offscreenContext.clearRect(0, 0, this._offscreenCanvas.width, this._offscreenCanvas.height); - } + // this._offscreenContext.clearRect(0, 0, this._offscreenCanvas.width, this._offscreenCanvas.height); + // } /** * Queues a refresh between two rows (inclusive), to be done on next animation @@ -197,7 +168,7 @@ export class Renderer { } this._refreshRowsQueue = []; this._refreshAnimationFrame = null; - this._refresh(start, end); + // this._refresh(start, end); for (let i = 0; i < this._renderLayers.length; i++) { this._renderLayers[i].render(this._terminal, start, end); } @@ -225,107 +196,10 @@ export class Renderer { * @param {number} start The row to start from (between 0 and terminal's height terminal - 1) * @param {number} end The row to end at (between fromRow and terminal's height terminal - 1) */ - private _refresh(start: number, end: number): void { - const scaledCharWidth = Math.ceil(this._terminal.charMeasure.width) * window.devicePixelRatio; - const scaledCharHeight = Math.ceil(this._terminal.charMeasure.height) * window.devicePixelRatio; - const textCtx = this._textCanvasContext; - - // TODO: Needs to react to terminal resize - // Initialize image data - if (!this._imageData) { - this._imageData = textCtx.createImageData(scaledCharWidth * this._terminal.cols * window.devicePixelRatio, scaledCharHeight * this._terminal.rows * window.devicePixelRatio); - this._imageData.data.set(createBackgroundFillData(this._imageData.width, this._imageData.height, 255, 0, 0, 255)); - } - - // Don't bother rendering until the atlas bitmap is ready - if (!this._charAtlasBitmap) { - return; - } - - // console.log('fill', start, end); - // console.log('fill', start * charHeight, (end - start + 1) * charHeight); - // ctx.fillRect(0, start * charHeight, charWidth * this._terminal.cols, (end - start + 1) * charHeight); - textCtx.fillStyle = '#ffffff'; - textCtx.textBaseline = 'top'; - textCtx.font = `${16 * window.devicePixelRatio}px courier`; - - // Clear out the old data - textCtx.clearRect(0, start * scaledCharHeight, scaledCharWidth * this._terminal.cols, (end - start + 1) * scaledCharHeight); - - for (let y = start; y <= end; y++) { - let row = y + this._terminal.buffer.ydisp; - let line = this._terminal.buffer.lines.get(row); - for (let x = 0; x < this._terminal.cols; x++) { - textCtx.save(); - - let data: number = line[x][0]; - // const ch = line[x][CHAR_DATA_CHAR_INDEX]; - const code: number = line[x][3]; - - // if (ch === ' ') { - // continue; - // } - - // let bg = data & 0x1ff; - let fg = (data >> 9) & 0x1ff; - let flags = data >> 18; - - if (flags & FLAGS.BOLD) { - textCtx.font = `bold ${textCtx.font}`; - // Convert the FG color to the bold variant - if (fg < 8) { - fg += 8; - } - } - - // if (fg < 16) { - // ctx.fillStyle = this._colors[fg]; - // } else if (fg < 256) { - // // TODO: Support colors 16-255 - // } - - let colorIndex = 0; - if (fg < 16) { - colorIndex = fg + 1; - } - - // Simulate cache - // let imageData; - // let key = ch + data; - // if (key in this._imageDataCache) { - // imageData = this._imageDataCache[key]; - // } else { - // ctx.fillText(ch, x * scaledCharWidth, y * scaledCharHeight); - // if (flags & FLAGS.UNDERLINE) { - // ctx.fillRect(x * scaledCharWidth, (y + 1) * scaledCharHeight - window.devicePixelRatio, scaledCharWidth, window.devicePixelRatio); - // } - // imageData = ctx.getImageData(x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); - // this._imageDataCache[key] = imageData; - // } - // ctx.putImageData(imageData, x * scaledCharWidth, y * scaledCharHeight); - - // TODO: Try to get atlas working - // This seems too slow :( - // ctx.putImageData(this._charImageDataAtlas, x * scaledCharWidth - ch.charCodeAt(0) * scaledCharWidth, y * scaledCharHeight, ch.charCodeAt(0) * scaledCharWidth, 0, scaledCharWidth, scaledCharHeight); - - // ctx.drawImage(this._offscreenCanvas, code * scaledCharWidth, 0, scaledCharWidth, scaledCharHeight, x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); - - // ImageBitmap's draw about twice as fast as from a canvas - textCtx.drawImage(this._charAtlasBitmap, code * scaledCharWidth, colorIndex * scaledCharHeight, scaledCharWidth, scaledCharHeight, x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); - - // Always write text - // ctx.fillText(ch, x * charWidth, y * charHeight); - textCtx.restore(); - } - - - // TODO: Try to get atlas working - // ctx.putImageData(this._charImageDataAtlas, y * scaledCharWidth, y * scaledCharHeight, 65 * scaledCharWidth, 0, scaledCharWidth, scaledCharHeight); - } - - // This draws the atlas (for debugging purposes) - // ctx.putImageData(this._charImageDataAtlas, 0, 0); - } + // private _refresh(start: number, end: number): void { + // const scaledCharWidth = Math.ceil(this._terminal.charMeasure.width) * window.devicePixelRatio; + // const scaledCharHeight = Math.ceil(this._terminal.charMeasure.height) * window.devicePixelRatio; + // } /** * Refreshes the selection in the DOM. @@ -390,16 +264,16 @@ export class Renderer { // If bold is broken, we can't use it in the terminal. -function checkBoldBroken(terminalElement: HTMLElement): boolean { - const document = terminalElement.ownerDocument; - const el = document.createElement('span'); - el.innerHTML = 'hello world'; - terminalElement.appendChild(el); - const w1 = el.offsetWidth; - const h1 = el.offsetHeight; - el.style.fontWeight = 'bold'; - const w2 = el.offsetWidth; - const h2 = el.offsetHeight; - terminalElement.removeChild(el); - return w1 !== w2 || h1 !== h2; -} +// function checkBoldBroken(terminalElement: HTMLElement): boolean { +// const document = terminalElement.ownerDocument; +// const el = document.createElement('span'); +// el.innerHTML = 'hello world'; +// terminalElement.appendChild(el); +// const w1 = el.offsetWidth; +// const h1 = el.offsetHeight; +// el.style.fontWeight = 'bold'; +// const w2 = el.offsetWidth; +// const h2 = el.offsetHeight; +// terminalElement.removeChild(el); +// return w1 !== w2 || h1 !== h2; +// } diff --git a/src/renderer/BackgroundRenderLayer.ts b/src/renderer/BackgroundRenderLayer.ts index a624ef2e..4f8e9ea7 100644 --- a/src/renderer/BackgroundRenderLayer.ts +++ b/src/renderer/BackgroundRenderLayer.ts @@ -1,42 +1,21 @@ import { IRenderLayer } from './Interfaces'; import { IBuffer, ICharMeasure, ITerminal } from '../Interfaces'; import { CHAR_DATA_ATTR_INDEX } from '../Buffer'; +import { TANGO_COLORS } from './Color'; export class BackgroundRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; private _ctx: CanvasRenderingContext2D; - // TODO: Pull colors into some other class - private _colors = [ - // dark: - '#2e3436', - '#cc0000', - '#4e9a06', - '#c4a000', - '#3465a4', - '#75507b', - '#06989a', - '#d3d7cf', - // bright: - '#555753', - '#ef2929', - '#8ae234', - '#fce94f', - '#729fcf', - '#ad7fa8', - '#34e2e2', - '#eeeeec' - ]; - constructor(container: HTMLElement) { this._canvas = document.createElement('canvas'); - this._canvas.classList.add('xterm-bg-canvas'); + this._canvas.classList.add('xterm-bg-layer'); this._ctx = this._canvas.getContext('2d'); this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); container.appendChild(this._canvas); } - public resize(canvasWidth: number, canvasHeight: number, charWidth: number, charHeight: number): void { + public resize(canvasWidth: number, canvasHeight: number, charWidth: number, charHeight: number, charSizeChanged: boolean): void { this._canvas.width = canvasWidth * window.devicePixelRatio; this._canvas.height = canvasHeight * window.devicePixelRatio; this._canvas.style.width = `${canvasWidth}px`; @@ -58,7 +37,7 @@ export class BackgroundRenderLayer implements IRenderLayer { // TODO: Draw background if (bg < 16) { this._ctx.save(); - this._ctx.fillStyle = this._colors[bg]; + this._ctx.fillStyle = TANGO_COLORS[bg]; this._ctx.fillRect(x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); this._ctx.restore(); } else { diff --git a/src/renderer/Color.ts b/src/renderer/Color.ts new file mode 100644 index 00000000..250c5c50 --- /dev/null +++ b/src/renderer/Color.ts @@ -0,0 +1,23 @@ +// TODO: Ideally colors would be exposed through some theme manager since colors +// are moving to JS. + +export const TANGO_COLORS = [ + // dark: + '#2e3436', + '#cc0000', + '#4e9a06', + '#c4a000', + '#3465a4', + '#75507b', + '#06989a', + '#d3d7cf', + // bright: + '#555753', + '#ef2929', + '#8ae234', + '#fce94f', + '#729fcf', + '#ad7fa8', + '#34e2e2', + '#eeeeec' +]; diff --git a/src/renderer/ForegroundRenderLayer.ts b/src/renderer/ForegroundRenderLayer.ts new file mode 100644 index 00000000..adf6c10a --- /dev/null +++ b/src/renderer/ForegroundRenderLayer.ts @@ -0,0 +1,180 @@ +import { IRenderLayer } from './Interfaces'; +import { IBuffer, ICharMeasure, ITerminal } from '../Interfaces'; +import { CHAR_DATA_ATTR_INDEX } from '../Buffer'; +import { TANGO_COLORS } from './Color'; +import { FLAGS } from './Types'; + +export class ForegroundRenderLayer implements IRenderLayer { + private _canvas: HTMLCanvasElement; + private _ctx: CanvasRenderingContext2D; + private _charAtlas: ImageBitmap; + + private _charAtlasGenerator: CharAtlasGenerator; + + constructor(container: HTMLElement) { + this._canvas = document.createElement('canvas'); + this._canvas.classList.add('xterm-fg-layer'); + this._ctx = this._canvas.getContext('2d'); + this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); + container.appendChild(this._canvas); + this._charAtlasGenerator = new CharAtlasGenerator(); + } + + public resize(canvasWidth: number, canvasHeight: number, charWidth: number, charHeight: number, charSizeChanged: boolean): void { + this._canvas.width = canvasWidth * window.devicePixelRatio; + this._canvas.height = canvasHeight * window.devicePixelRatio; + this._canvas.style.width = `${canvasWidth}px`; + this._canvas.style.height = `${canvasHeight}px`; + if (charSizeChanged) { + this._charAtlas = null; + this._charAtlasGenerator.generate(charWidth, charHeight).then(bitmap => { + this._charAtlas = bitmap; + }); + } + } + + public render(terminal: ITerminal, startRow: number, endRow: number): void { + const scaledCharWidth = Math.ceil(terminal.charMeasure.width) * window.devicePixelRatio; + const scaledCharHeight = Math.ceil(terminal.charMeasure.height) * window.devicePixelRatio; + + // TODO: Needs to react to terminal resize + // Initialize image data + // if (!this._imageData) { + // this._imageData = textCtx.createImageData(scaledCharWidth * this._terminal.cols * window.devicePixelRatio, scaledCharHeight * this._terminal.rows * window.devicePixelRatio); + // this._imageData.data.set(createBackgroundFillData(this._imageData.width, this._imageData.height, 255, 0, 0, 255)); + // } + + // TODO: Ensure that the render is eventually performed + // Don't bother render until the atlas bitmap is ready + if (!this._charAtlas) { + return; + } + + // console.log('fill', start, end); + // console.log('fill', start * charHeight, (end - start + 1) * charHeight); + // ctx.fillRect(0, start * charHeight, charWidth * this._terminal.cols, (end - start + 1) * charHeight); + this._ctx.fillStyle = '#ffffff'; + this._ctx.textBaseline = 'top'; + this._ctx.font = `${16 * window.devicePixelRatio}px courier`; + + // Clear out the old data + // TODO: This should be optimised, we don't want to rewrite every character + this._ctx.clearRect(0, startRow * scaledCharHeight, scaledCharWidth * terminal.cols, (endRow - startRow + 1) * scaledCharHeight); + + for (let y = startRow; y <= endRow; y++) { + let row = y + terminal.buffer.ydisp; + let line = terminal.buffer.lines.get(row); + for (let x = 0; x < terminal.cols; x++) { + console.log('fg', x, y); + this._ctx.save(); + + let data: number = line[x][0]; + // const ch = line[x][CHAR_DATA_CHAR_INDEX]; + const code: number = line[x][3]; + + // if (ch === ' ') { + // continue; + // } + + // let bg = data & 0x1ff; + let fg = (data >> 9) & 0x1ff; + let flags = data >> 18; + + if (flags & FLAGS.BOLD) { + this._ctx.font = `bold ${this._ctx.font}`; + // Convert the FG color to the bold variant + if (fg < 8) { + fg += 8; + } + } + + // if (fg < 16) { + // ctx.fillStyle = this._colors[fg]; + // } else if (fg < 256) { + // // TODO: Support colors 16-255 + // } + + let colorIndex = 0; + if (fg < 16) { + colorIndex = fg + 1; + } + + // Simulate cache + // let imageData; + // let key = ch + data; + // if (key in this._imageDataCache) { + // imageData = this._imageDataCache[key]; + // } else { + // ctx.fillText(ch, x * scaledCharWidth, y * scaledCharHeight); + // if (flags & FLAGS.UNDERLINE) { + // ctx.fillRect(x * scaledCharWidth, (y + 1) * scaledCharHeight - window.devicePixelRatio, scaledCharWidth, window.devicePixelRatio); + // } + // imageData = ctx.getImageData(x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); + // this._imageDataCache[key] = imageData; + // } + // ctx.putImageData(imageData, x * scaledCharWidth, y * scaledCharHeight); + + // TODO: Try to get atlas working + // This seems too slow :( + // ctx.putImageData(this._charImageDataAtlas, x * scaledCharWidth - ch.charCodeAt(0) * scaledCharWidth, y * scaledCharHeight, ch.charCodeAt(0) * scaledCharWidth, 0, scaledCharWidth, scaledCharHeight); + + // ctx.drawImage(this._offscreenCanvas, code * scaledCharWidth, 0, scaledCharWidth, scaledCharHeight, x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); + + // ImageBitmap's draw about twice as fast as from a canvas + this._ctx.drawImage(this._charAtlas, code * scaledCharWidth, colorIndex * scaledCharHeight, scaledCharWidth, scaledCharHeight, x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); + this._ctx.restore(); + } + } + + // This draws the atlas (for debugging purposes) + this._ctx.drawImage(this._charAtlas, 0, 0); + } +} + +class CharAtlasGenerator { + private _canvas: HTMLCanvasElement; + private _ctx: CanvasRenderingContext2D; + + constructor() { + this._canvas = document.createElement('canvas'); + this._ctx = this._canvas.getContext('2d'); + this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); + } + + public generate(charWidth: number, charHeight: number): Promise { + const scaledCharWidth = Math.ceil(charWidth) * window.devicePixelRatio; + const scaledCharHeight = Math.ceil(charHeight) * window.devicePixelRatio; + + this._canvas.width = 255 * scaledCharWidth; + this._canvas.height = (/*default*/1 + /*0-15*/16) * scaledCharHeight; + + this._ctx.save(); + this._ctx.fillStyle = '#ffffff'; + this._ctx.font = `${16 * window.devicePixelRatio}px courier`; + this._ctx.textBaseline = 'top'; + + // Default color + for (let i = 0; i < 256; i++) { + this._ctx.fillText(String.fromCharCode(i), i * scaledCharWidth, 0); + } + + // Colors 0-15 + for (let colorIndex = 0; colorIndex < 16; colorIndex++) { + // colors 8-15 are bold + if (colorIndex === 8) { + this._ctx.font = `bold ${this._ctx.font}`; + } + for (let i = 0; i < 256; i++) { + this._ctx.fillStyle = TANGO_COLORS[colorIndex]; + this._ctx.fillText(String.fromCharCode(i), i * scaledCharWidth, (colorIndex + 1) * scaledCharHeight); + } + } + this._ctx.restore(); + + const charAtlasImageData = this._ctx.getImageData(0, 0, this._canvas.width, this._canvas.height); + const promise = window.createImageBitmap(charAtlasImageData); + // Clear the rect while the promise is in progress + this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); + return promise; + } +} diff --git a/src/renderer/Interfaces.ts b/src/renderer/Interfaces.ts index 6e0c3933..f9572525 100644 --- a/src/renderer/Interfaces.ts +++ b/src/renderer/Interfaces.ts @@ -1,6 +1,6 @@ import { ITerminal } from '../Interfaces'; export interface IRenderLayer { - resize(canvasWidth: number, canvasHeight: number, charWidth: number, charHeight: number): void; + resize(canvasWidth: number, canvasHeight: number, charWidth: number, charHeight: number, charSizeChanged: boolean): void; render(terminal: ITerminal, startRow: number, endRow: number): void; } diff --git a/src/renderer/Types.ts b/src/renderer/Types.ts new file mode 100644 index 00000000..09e74439 --- /dev/null +++ b/src/renderer/Types.ts @@ -0,0 +1,10 @@ +/** + * Flags used to render terminal text properly. + */ +export enum FLAGS { + BOLD = 1, + UNDERLINE = 2, + BLINK = 4, + INVERSE = 8, + INVISIBLE = 16 +}; diff --git a/src/xterm.css b/src/xterm.css index 321cf6d6..2ee98e54 100644 --- a/src/xterm.css +++ b/src/xterm.css @@ -164,11 +164,11 @@ top: 0; } -.terminal .xterm-bg-canvas { +.terminal .xterm-bg-layer { z-index: 0; } -.terminal .xterm-text-canvas { +.terminal .xterm-fg-layer { z-index: 1; } From 18e1134e619fcd55c0a668fd5f888868ab411788 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 30 Aug 2017 21:55:24 -0700 Subject: [PATCH 115/378] Tidy up --- src/RendererCanvas.ts | 112 +------------------------- src/renderer/BackgroundRenderLayer.ts | 2 +- src/renderer/ForegroundRenderLayer.ts | 12 +-- 3 files changed, 9 insertions(+), 117 deletions(-) diff --git a/src/RendererCanvas.ts b/src/RendererCanvas.ts index bf952b6c..27832bed 100644 --- a/src/RendererCanvas.ts +++ b/src/RendererCanvas.ts @@ -23,40 +23,13 @@ export class Renderer { private _refreshFramesSkipped = 0; private _refreshAnimationFrame = null; - // private _textCanvasElement: HTMLCanvasElement; - // private _textCanvasContext: CanvasRenderingContext2D; - // private _offscreenCanvas: HTMLCanvasElement; - // private _offscreenContext: CanvasRenderingContext2D; - private _renderLayers: IRenderLayer[]; - // private _charAtlasBitmap;//: ImageBitmap; - - // TODO: This would be better as a large texture atlas rather than a cache of ImageData objects - // private _imageDataCache = {}; - // private _imageData: ImageData; - constructor(private _terminal: ITerminal) { - // Figure out whether boldness affects - // the character width of monospace fonts. - // if (brokenBold === null) { - // brokenBold = checkBoldBroken(this._terminal.element); - // } - - // this._offscreenCanvas = document.createElement('canvas'); - // this._offscreenContext = this._offscreenCanvas.getContext('2d'); - // this._offscreenContext.scale(window.devicePixelRatio, window.devicePixelRatio); - this._renderLayers = [ new BackgroundRenderLayer(this._terminal.element), new ForegroundRenderLayer(this._terminal.element) ]; - - // this._terminal.element.appendChild(this._textCanvasElement); - - // TODO: Pull more DOM interactions into Renderer.constructor, element for - // example should be owned by Renderer (and also exposed by Terminal due to - // to established public API). } public onResize(cols: number, rows: number): void { @@ -73,51 +46,11 @@ export class Renderer { public onCharSizeChanged(charWidth: number, charHeight: number): void { const width = Math.ceil(charWidth) * this._terminal.cols; const height = Math.ceil(charHeight) * this._terminal.rows; - // this._textCanvasElement.width = width * window.devicePixelRatio; - // this._textCanvasElement.height = height * window.devicePixelRatio; - // this._textCanvasElement.style.width = `${width}px`; - // this._textCanvasElement.style.height = `${height}px`; - // this._offscreenCanvas.width = 255 * charWidth * window.devicePixelRatio; - // this._offscreenCanvas.height = (/*default*/1 + /*0-15*/16) * charHeight * window.devicePixelRatio; - // this._refreshCharImageDataAtlas(); for (let i = 0; i < this._renderLayers.length; i++) { this._renderLayers[i].resize(width, height, charWidth, charHeight, true); } } - // private _refreshCharImageDataAtlas(): void { - // const scaledCharWidth = Math.ceil(this._terminal.charMeasure.width) * window.devicePixelRatio; - // const scaledCharHeight = Math.ceil(this._terminal.charMeasure.height) * window.devicePixelRatio; - - // this._offscreenContext.save(); - // this._offscreenContext.fillStyle = '#ffffff'; - // this._offscreenContext.font = `${16 * window.devicePixelRatio}px courier`; - // this._offscreenContext.textBaseline = 'top'; - // // Default color - // for (let i = 0; i < 256; i++) { - // this._offscreenContext.fillText(String.fromCharCode(i), i * scaledCharWidth, 0); - // } - // // Colors 0-15 - // for (let colorIndex = 0; colorIndex < 16; colorIndex++) { - // // colors 8-15 are bold - // if (colorIndex === 8) { - // this._offscreenContext.font = `bold ${this._offscreenContext.font}`; - // } - // for (let i = 0; i < 256; i++) { - // this._offscreenContext.fillStyle = this._colors[colorIndex]; - // this._offscreenContext.fillText(String.fromCharCode(i), i * scaledCharWidth, (colorIndex + 1) * scaledCharHeight); - // } - // } - // this._offscreenContext.restore(); - - // const charAtlasImageData = this._offscreenContext.getImageData(0, 0, this._offscreenCanvas.width, this._offscreenCanvas.height); - // (window).createImageBitmap(charAtlasImageData).then(bitmap => { - // this._charAtlasBitmap = bitmap; - // }); - - // this._offscreenContext.clearRect(0, 0, this._offscreenCanvas.width, this._offscreenCanvas.height); - // } - /** * Queues a refresh between two rows (inclusive), to be done on next animation * frame. @@ -168,39 +101,14 @@ export class Renderer { } this._refreshRowsQueue = []; this._refreshAnimationFrame = null; - // this._refresh(start, end); + + // Render for (let i = 0; i < this._renderLayers.length; i++) { this._renderLayers[i].render(this._terminal, start, end); } this._terminal.emit('refresh', {start, end}); } - /** - * Refreshes (re-renders) terminal content within two rows (inclusive) - * - * Rendering Engine: - * - * In the screen buffer, each character is stored as a an array with a character - * and a 32-bit integer: - * - First value: a utf-16 character. - * - Second value: - * - Next 9 bits: background color (0-511). - * - Next 9 bits: foreground color (0-511). - * - Next 14 bits: a mask for misc. flags: - * - 1=bold - * - 2=underline - * - 4=blink - * - 8=inverse - * - 16=invisible - * - * @param {number} start The row to start from (between 0 and terminal's height terminal - 1) - * @param {number} end The row to end at (between fromRow and terminal's height terminal - 1) - */ - // private _refresh(start: number, end: number): void { - // const scaledCharWidth = Math.ceil(this._terminal.charMeasure.width) * window.devicePixelRatio; - // const scaledCharHeight = Math.ceil(this._terminal.charMeasure.height) * window.devicePixelRatio; - // } - /** * Refreshes the selection in the DOM. * @param start The selection start. @@ -261,19 +169,3 @@ export class Renderer { return element; } } - - -// If bold is broken, we can't use it in the terminal. -// function checkBoldBroken(terminalElement: HTMLElement): boolean { -// const document = terminalElement.ownerDocument; -// const el = document.createElement('span'); -// el.innerHTML = 'hello world'; -// terminalElement.appendChild(el); -// const w1 = el.offsetWidth; -// const h1 = el.offsetHeight; -// el.style.fontWeight = 'bold'; -// const w2 = el.offsetWidth; -// const h2 = el.offsetHeight; -// terminalElement.removeChild(el); -// return w1 !== w2 || h1 !== h2; -// } diff --git a/src/renderer/BackgroundRenderLayer.ts b/src/renderer/BackgroundRenderLayer.ts index 4f8e9ea7..b283ce15 100644 --- a/src/renderer/BackgroundRenderLayer.ts +++ b/src/renderer/BackgroundRenderLayer.ts @@ -34,13 +34,13 @@ export class BackgroundRenderLayer implements IRenderLayer { const bg = data & 0x1ff; const flags = data >> 18; - // TODO: Draw background if (bg < 16) { this._ctx.save(); this._ctx.fillStyle = TANGO_COLORS[bg]; this._ctx.fillRect(x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); this._ctx.restore(); } else { + // TODO: Only clear if needed this._ctx.clearRect(x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); } } diff --git a/src/renderer/ForegroundRenderLayer.ts b/src/renderer/ForegroundRenderLayer.ts index adf6c10a..d3b1fb05 100644 --- a/src/renderer/ForegroundRenderLayer.ts +++ b/src/renderer/ForegroundRenderLayer.ts @@ -50,9 +50,6 @@ export class ForegroundRenderLayer implements IRenderLayer { return; } - // console.log('fill', start, end); - // console.log('fill', start * charHeight, (end - start + 1) * charHeight); - // ctx.fillRect(0, start * charHeight, charWidth * this._terminal.cols, (end - start + 1) * charHeight); this._ctx.fillStyle = '#ffffff'; this._ctx.textBaseline = 'top'; this._ctx.font = `${16 * window.devicePixelRatio}px courier`; @@ -65,7 +62,6 @@ export class ForegroundRenderLayer implements IRenderLayer { let row = y + terminal.buffer.ydisp; let line = terminal.buffer.lines.get(row); for (let x = 0; x < terminal.cols; x++) { - console.log('fg', x, y); this._ctx.save(); let data: number = line[x][0]; @@ -127,7 +123,7 @@ export class ForegroundRenderLayer implements IRenderLayer { } // This draws the atlas (for debugging purposes) - this._ctx.drawImage(this._charAtlas, 0, 0); + // this._ctx.drawImage(this._charAtlas, 0, 0); } } @@ -164,9 +160,13 @@ class CharAtlasGenerator { if (colorIndex === 8) { this._ctx.font = `bold ${this._ctx.font}`; } + const y = (colorIndex + 1) * scaledCharHeight; + // Clear rectangle as some fonts seem to draw over the bottom boundary + this._ctx.clearRect(0, y, this._canvas.width, scaledCharHeight); + // Draw ascii characters for (let i = 0; i < 256; i++) { this._ctx.fillStyle = TANGO_COLORS[colorIndex]; - this._ctx.fillText(String.fromCharCode(i), i * scaledCharWidth, (colorIndex + 1) * scaledCharHeight); + this._ctx.fillText(String.fromCharCode(i), i * scaledCharWidth, y); } } this._ctx.restore(); From 129633a66d68d5d3cd9f349539342c1e16885c5e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 30 Aug 2017 22:09:25 -0700 Subject: [PATCH 116/378] Only draw background when needed --- src/RendererCanvas.ts | 11 +++----- src/renderer/BackgroundRenderLayer.ts | 37 ++++++++++++++++++++------- src/renderer/ForegroundRenderLayer.ts | 4 +-- src/renderer/Interfaces.ts | 2 +- 4 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/RendererCanvas.ts b/src/RendererCanvas.ts index 27832bed..bca5f70e 100644 --- a/src/RendererCanvas.ts +++ b/src/RendererCanvas.ts @@ -33,13 +33,10 @@ export class Renderer { } public onResize(cols: number, rows: number): void { - // TODO: This could be triggered immediately after onCharSizeChanged? - const charWidth = this._terminal.charMeasure.width; - const charHeight = this._terminal.charMeasure.height; - const width = Math.ceil(charWidth) * this._terminal.cols; - const height = Math.ceil(charHeight) * this._terminal.rows; + const width = Math.ceil(this._terminal.charMeasure.width) * this._terminal.cols; + const height = Math.ceil(this._terminal.charMeasure.height) * this._terminal.rows; for (let i = 0; i < this._renderLayers.length; i++) { - this._renderLayers[i].resize(width, height, charWidth, charHeight, false); + this._renderLayers[i].resize(this._terminal, width, height, false); } } @@ -47,7 +44,7 @@ export class Renderer { const width = Math.ceil(charWidth) * this._terminal.cols; const height = Math.ceil(charHeight) * this._terminal.rows; for (let i = 0; i < this._renderLayers.length; i++) { - this._renderLayers[i].resize(width, height, charWidth, charHeight, true); + this._renderLayers[i].resize(this._terminal, width, height, true); } } diff --git a/src/renderer/BackgroundRenderLayer.ts b/src/renderer/BackgroundRenderLayer.ts index b283ce15..c1d9eac0 100644 --- a/src/renderer/BackgroundRenderLayer.ts +++ b/src/renderer/BackgroundRenderLayer.ts @@ -6,6 +6,7 @@ import { TANGO_COLORS } from './Color'; export class BackgroundRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; private _ctx: CanvasRenderingContext2D; + private _currentState: number[][]; constructor(container: HTMLElement) { this._canvas = document.createElement('canvas'); @@ -13,13 +14,27 @@ export class BackgroundRenderLayer implements IRenderLayer { this._ctx = this._canvas.getContext('2d'); this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); container.appendChild(this._canvas); + + this._currentState = []; } - public resize(canvasWidth: number, canvasHeight: number, charWidth: number, charHeight: number, charSizeChanged: boolean): void { + public resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void { this._canvas.width = canvasWidth * window.devicePixelRatio; this._canvas.height = canvasHeight * window.devicePixelRatio; this._canvas.style.width = `${canvasWidth}px`; this._canvas.style.height = `${canvasHeight}px`; + // Initialize current state grid + this._currentState = []; + for (let y = 0; y < terminal.rows; y++) { + if (this._currentState.length <= y) { + this._currentState.push([]); + } + for (let x = this._currentState[y].length; x < terminal.cols; x++) { + this._currentState[y].push(null); + } + this._currentState[y].length = terminal.cols; + } + this._currentState.length = terminal.rows; } public render(terminal: ITerminal, startRow: number, endRow: number): void { @@ -34,14 +49,18 @@ export class BackgroundRenderLayer implements IRenderLayer { const bg = data & 0x1ff; const flags = data >> 18; - if (bg < 16) { - this._ctx.save(); - this._ctx.fillStyle = TANGO_COLORS[bg]; - this._ctx.fillRect(x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); - this._ctx.restore(); - } else { - // TODO: Only clear if needed - this._ctx.clearRect(x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); + const needsRefresh = (bg < 16 && this._currentState[y][x] !== bg) || this._currentState[y][x] !== null; + if (needsRefresh) { + if (bg < 16) { + this._ctx.save(); + this._ctx.fillStyle = TANGO_COLORS[bg]; + this._ctx.fillRect(x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); + this._ctx.restore(); + this._currentState[y][x] = bg; + } else { + this._ctx.clearRect(x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); + this._currentState[y][x] = null; + } } } } diff --git a/src/renderer/ForegroundRenderLayer.ts b/src/renderer/ForegroundRenderLayer.ts index d3b1fb05..da0519bd 100644 --- a/src/renderer/ForegroundRenderLayer.ts +++ b/src/renderer/ForegroundRenderLayer.ts @@ -20,14 +20,14 @@ export class ForegroundRenderLayer implements IRenderLayer { this._charAtlasGenerator = new CharAtlasGenerator(); } - public resize(canvasWidth: number, canvasHeight: number, charWidth: number, charHeight: number, charSizeChanged: boolean): void { + public resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void { this._canvas.width = canvasWidth * window.devicePixelRatio; this._canvas.height = canvasHeight * window.devicePixelRatio; this._canvas.style.width = `${canvasWidth}px`; this._canvas.style.height = `${canvasHeight}px`; if (charSizeChanged) { this._charAtlas = null; - this._charAtlasGenerator.generate(charWidth, charHeight).then(bitmap => { + this._charAtlasGenerator.generate(terminal.charMeasure.width, terminal.charMeasure.height).then(bitmap => { this._charAtlas = bitmap; }); } diff --git a/src/renderer/Interfaces.ts b/src/renderer/Interfaces.ts index f9572525..4d275375 100644 --- a/src/renderer/Interfaces.ts +++ b/src/renderer/Interfaces.ts @@ -1,6 +1,6 @@ import { ITerminal } from '../Interfaces'; export interface IRenderLayer { - resize(canvasWidth: number, canvasHeight: number, charWidth: number, charHeight: number, charSizeChanged: boolean): void; + resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void; render(terminal: ITerminal, startRow: number, endRow: number): void; } From d93a80398d831d98d745540431e25af8f1d20a73 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 30 Aug 2017 22:22:41 -0700 Subject: [PATCH 117/378] Draw unicode characters --- src/Buffer.ts | 1 + src/renderer/ForegroundRenderLayer.ts | 76 ++++++++++++--------------- 2 files changed, 35 insertions(+), 42 deletions(-) diff --git a/src/Buffer.ts b/src/Buffer.ts index e8ceebf3..d8311f06 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -9,6 +9,7 @@ import { LineData, CharData } from './Types'; export const CHAR_DATA_ATTR_INDEX = 0; export const CHAR_DATA_CHAR_INDEX = 1; export const CHAR_DATA_WIDTH_INDEX = 2; +export const CHAR_DATA_CODE_INDEX = 3; /** * This class represents a terminal buffer (an internal state of the terminal), where the diff --git a/src/renderer/ForegroundRenderLayer.ts b/src/renderer/ForegroundRenderLayer.ts index da0519bd..2ed0bd60 100644 --- a/src/renderer/ForegroundRenderLayer.ts +++ b/src/renderer/ForegroundRenderLayer.ts @@ -1,6 +1,6 @@ import { IRenderLayer } from './Interfaces'; import { IBuffer, ICharMeasure, ITerminal } from '../Interfaces'; -import { CHAR_DATA_ATTR_INDEX } from '../Buffer'; +import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from '../Buffer'; import { TANGO_COLORS } from './Color'; import { FLAGS } from './Types'; @@ -62,19 +62,15 @@ export class ForegroundRenderLayer implements IRenderLayer { let row = y + terminal.buffer.ydisp; let line = terminal.buffer.lines.get(row); for (let x = 0; x < terminal.cols; x++) { - this._ctx.save(); + const code: number = line[x][CHAR_DATA_CODE_INDEX]; - let data: number = line[x][0]; - // const ch = line[x][CHAR_DATA_CHAR_INDEX]; - const code: number = line[x][3]; + if (!code) { + continue; + } - // if (ch === ' ') { - // continue; - // } - - // let bg = data & 0x1ff; - let fg = (data >> 9) & 0x1ff; - let flags = data >> 18; + const attr: number = line[x][CHAR_DATA_ATTR_INDEX]; + let fg = (attr >> 9) & 0x1ff; + const flags = attr >> 18; if (flags & FLAGS.BOLD) { this._ctx.font = `bold ${this._ctx.font}`; @@ -84,47 +80,43 @@ export class ForegroundRenderLayer implements IRenderLayer { } } - // if (fg < 16) { - // ctx.fillStyle = this._colors[fg]; - // } else if (fg < 256) { - // // TODO: Support colors 16-255 - // } - let colorIndex = 0; if (fg < 16) { colorIndex = fg + 1; } - // Simulate cache - // let imageData; - // let key = ch + data; - // if (key in this._imageDataCache) { - // imageData = this._imageDataCache[key]; - // } else { - // ctx.fillText(ch, x * scaledCharWidth, y * scaledCharHeight); - // if (flags & FLAGS.UNDERLINE) { - // ctx.fillRect(x * scaledCharWidth, (y + 1) * scaledCharHeight - window.devicePixelRatio, scaledCharWidth, window.devicePixelRatio); - // } - // imageData = ctx.getImageData(x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); - // this._imageDataCache[key] = imageData; - // } - // ctx.putImageData(imageData, x * scaledCharWidth, y * scaledCharHeight); - - // TODO: Try to get atlas working - // This seems too slow :( - // ctx.putImageData(this._charImageDataAtlas, x * scaledCharWidth - ch.charCodeAt(0) * scaledCharWidth, y * scaledCharHeight, ch.charCodeAt(0) * scaledCharWidth, 0, scaledCharWidth, scaledCharHeight); - - // ctx.drawImage(this._offscreenCanvas, code * scaledCharWidth, 0, scaledCharWidth, scaledCharHeight, x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); - - // ImageBitmap's draw about twice as fast as from a canvas - this._ctx.drawImage(this._charAtlas, code * scaledCharWidth, colorIndex * scaledCharHeight, scaledCharWidth, scaledCharHeight, x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); - this._ctx.restore(); + if (code < 256) { + // ImageBitmap's draw about twice as fast as from a canvas + this._ctx.drawImage(this._charAtlas, code * scaledCharWidth, colorIndex * scaledCharHeight, scaledCharWidth, scaledCharHeight, x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); + } else { + // TODO: Evaluate how long it takes to convert from a number + const char: string = line[x][CHAR_DATA_CHAR_INDEX]; + const width: number = line[x][CHAR_DATA_WIDTH_INDEX]; + this._drawUnicodeChar(char, width, fg, x, y, scaledCharWidth, scaledCharHeight); + } } } // This draws the atlas (for debugging purposes) // this._ctx.drawImage(this._charAtlas, 0, 0); } + + private _drawUnicodeChar(char: string, width: number, fg: number, x: number, y: number, scaledCharWidth: number, scaledCharHeight: number) { + this._ctx.save(); + + this._ctx.font = `${16 * window.devicePixelRatio}px courier`; + this._ctx.textBaseline = 'top'; + + if (fg < 16) { + this._ctx.fillStyle = TANGO_COLORS[fg]; + } else { + this._ctx.fillStyle = '#ffffff'; + } + + // TODO: Do we care about width for rendering wide chars? + this._ctx.fillText(char, x * scaledCharWidth, y * scaledCharHeight); + this._ctx.restore(); + } } class CharAtlasGenerator { From 6a0b9bd209ada7d8355e577b0aa9fb19c7da168a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 30 Aug 2017 22:55:17 -0700 Subject: [PATCH 118/378] Only render fg cells if there are changes --- src/Types.ts | 1 + src/renderer/BackgroundRenderLayer.ts | 26 +++++++------------- src/renderer/ForegroundRenderLayer.ts | 34 +++++++++++++++++++-------- src/renderer/GridCache.ts | 20 ++++++++++++++++ 4 files changed, 53 insertions(+), 28 deletions(-) create mode 100644 src/renderer/GridCache.ts diff --git a/src/Types.ts b/src/Types.ts index 0753d80e..180dc5d6 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -16,5 +16,6 @@ export type LinkMatcherValidationCallback = (uri: string, element: HTMLElement, export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean; export type Charset = {[key: string]: string}; +// TODO: Add code here? export type CharData = [number, string, number]; export type LineData = CharData[]; diff --git a/src/renderer/BackgroundRenderLayer.ts b/src/renderer/BackgroundRenderLayer.ts index c1d9eac0..8577f58d 100644 --- a/src/renderer/BackgroundRenderLayer.ts +++ b/src/renderer/BackgroundRenderLayer.ts @@ -2,11 +2,12 @@ import { IRenderLayer } from './Interfaces'; import { IBuffer, ICharMeasure, ITerminal } from '../Interfaces'; import { CHAR_DATA_ATTR_INDEX } from '../Buffer'; import { TANGO_COLORS } from './Color'; +import { GridCache } from './GridCache'; export class BackgroundRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; private _ctx: CanvasRenderingContext2D; - private _currentState: number[][]; + private _state: GridCache; constructor(container: HTMLElement) { this._canvas = document.createElement('canvas'); @@ -14,8 +15,7 @@ export class BackgroundRenderLayer implements IRenderLayer { this._ctx = this._canvas.getContext('2d'); this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); container.appendChild(this._canvas); - - this._currentState = []; + this._state = new GridCache(); } public resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void { @@ -23,18 +23,7 @@ export class BackgroundRenderLayer implements IRenderLayer { this._canvas.height = canvasHeight * window.devicePixelRatio; this._canvas.style.width = `${canvasWidth}px`; this._canvas.style.height = `${canvasHeight}px`; - // Initialize current state grid - this._currentState = []; - for (let y = 0; y < terminal.rows; y++) { - if (this._currentState.length <= y) { - this._currentState.push([]); - } - for (let x = this._currentState[y].length; x < terminal.cols; x++) { - this._currentState[y].push(null); - } - this._currentState[y].length = terminal.cols; - } - this._currentState.length = terminal.rows; + this._state.resize(terminal.cols, terminal.rows); } public render(terminal: ITerminal, startRow: number, endRow: number): void { @@ -49,17 +38,18 @@ export class BackgroundRenderLayer implements IRenderLayer { const bg = data & 0x1ff; const flags = data >> 18; - const needsRefresh = (bg < 16 && this._currentState[y][x] !== bg) || this._currentState[y][x] !== null; + const cellState = this._state.cache[x][y]; + const needsRefresh = (bg < 16 && cellState !== bg) || cellState !== null; if (needsRefresh) { if (bg < 16) { this._ctx.save(); this._ctx.fillStyle = TANGO_COLORS[bg]; this._ctx.fillRect(x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); this._ctx.restore(); - this._currentState[y][x] = bg; + this._state.cache[x][y] = bg; } else { this._ctx.clearRect(x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); - this._currentState[y][x] = null; + this._state.cache[x][y] = null; } } } diff --git a/src/renderer/ForegroundRenderLayer.ts b/src/renderer/ForegroundRenderLayer.ts index 2ed0bd60..c6263247 100644 --- a/src/renderer/ForegroundRenderLayer.ts +++ b/src/renderer/ForegroundRenderLayer.ts @@ -3,11 +3,14 @@ import { IBuffer, ICharMeasure, ITerminal } from '../Interfaces'; import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from '../Buffer'; import { TANGO_COLORS } from './Color'; import { FLAGS } from './Types'; +import { GridCache } from './GridCache'; +import { CharData } from '../Types'; export class ForegroundRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; private _ctx: CanvasRenderingContext2D; private _charAtlas: ImageBitmap; + private _state: GridCache; private _charAtlasGenerator: CharAtlasGenerator; @@ -18,6 +21,7 @@ export class ForegroundRenderLayer implements IRenderLayer { this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); container.appendChild(this._canvas); this._charAtlasGenerator = new CharAtlasGenerator(); + this._state = new GridCache(); } public resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void { @@ -25,6 +29,7 @@ export class ForegroundRenderLayer implements IRenderLayer { this._canvas.height = canvasHeight * window.devicePixelRatio; this._canvas.style.width = `${canvasWidth}px`; this._canvas.style.height = `${canvasHeight}px`; + this._state.resize(terminal.cols, terminal.rows); if (charSizeChanged) { this._charAtlas = null; this._charAtlasGenerator.generate(terminal.charMeasure.width, terminal.charMeasure.height).then(bitmap => { @@ -54,21 +59,32 @@ export class ForegroundRenderLayer implements IRenderLayer { this._ctx.textBaseline = 'top'; this._ctx.font = `${16 * window.devicePixelRatio}px courier`; - // Clear out the old data - // TODO: This should be optimised, we don't want to rewrite every character - this._ctx.clearRect(0, startRow * scaledCharHeight, scaledCharWidth * terminal.cols, (endRow - startRow + 1) * scaledCharHeight); - for (let y = startRow; y <= endRow; y++) { let row = y + terminal.buffer.ydisp; let line = terminal.buffer.lines.get(row); for (let x = 0; x < terminal.cols; x++) { - const code: number = line[x][CHAR_DATA_CODE_INDEX]; + const charData = line[x]; + const code: number = charData[CHAR_DATA_CODE_INDEX]; + const char: string = charData[CHAR_DATA_CHAR_INDEX]; + const attr: number = charData[CHAR_DATA_ATTR_INDEX]; - if (!code) { + // Skip rendering if the character is identical + const state = this._state.cache[x][y]; + if (state && state[CHAR_DATA_CHAR_INDEX] === char && state[CHAR_DATA_ATTR_INDEX] === attr) { + // Skip render, contents are identical + this._state.cache[x][y] = charData; + continue; + } + this._state.cache[x][y] = charData; + + // Clear the old character + this._ctx.clearRect(x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); + + // Skip rendering if the character is invisible + if (!code || code === 32/*' '*/) { continue; } - const attr: number = line[x][CHAR_DATA_ATTR_INDEX]; let fg = (attr >> 9) & 0x1ff; const flags = attr >> 18; @@ -90,8 +106,7 @@ export class ForegroundRenderLayer implements IRenderLayer { this._ctx.drawImage(this._charAtlas, code * scaledCharWidth, colorIndex * scaledCharHeight, scaledCharWidth, scaledCharHeight, x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); } else { // TODO: Evaluate how long it takes to convert from a number - const char: string = line[x][CHAR_DATA_CHAR_INDEX]; - const width: number = line[x][CHAR_DATA_WIDTH_INDEX]; + const width: number = charData[CHAR_DATA_WIDTH_INDEX]; this._drawUnicodeChar(char, width, fg, x, y, scaledCharWidth, scaledCharHeight); } } @@ -103,7 +118,6 @@ export class ForegroundRenderLayer implements IRenderLayer { private _drawUnicodeChar(char: string, width: number, fg: number, x: number, y: number, scaledCharWidth: number, scaledCharHeight: number) { this._ctx.save(); - this._ctx.font = `${16 * window.devicePixelRatio}px courier`; this._ctx.textBaseline = 'top'; diff --git a/src/renderer/GridCache.ts b/src/renderer/GridCache.ts new file mode 100644 index 00000000..eba3dad6 --- /dev/null +++ b/src/renderer/GridCache.ts @@ -0,0 +1,20 @@ +export class GridCache { + public cache: T[][]; + + public constructor() { + this.cache = []; + } + + public resize(width: number, height: number) { + for (let x = 0; x < width; x++) { + if (this.cache.length <= x) { + this.cache.push([]); + } + for (let y = this.cache[x].length; y < height; y++) { + this.cache[x].push(null); + } + this.cache[x].length = height; + } + this.cache.length = width; + } +} From 1291dc5571947241985538e134ba61129881d218 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 30 Aug 2017 23:05:51 -0700 Subject: [PATCH 119/378] Support inverse --- src/renderer/BackgroundRenderLayer.ts | 17 ++++++++++++++--- src/renderer/ForegroundRenderLayer.ts | 20 +++++++++++--------- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/renderer/BackgroundRenderLayer.ts b/src/renderer/BackgroundRenderLayer.ts index 8577f58d..ea7bea60 100644 --- a/src/renderer/BackgroundRenderLayer.ts +++ b/src/renderer/BackgroundRenderLayer.ts @@ -3,6 +3,7 @@ import { IBuffer, ICharMeasure, ITerminal } from '../Interfaces'; import { CHAR_DATA_ATTR_INDEX } from '../Buffer'; import { TANGO_COLORS } from './Color'; import { GridCache } from './GridCache'; +import { FLAGS } from './Types'; export class BackgroundRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; @@ -34,9 +35,19 @@ export class BackgroundRenderLayer implements IRenderLayer { let row = y + terminal.buffer.ydisp; let line = terminal.buffer.lines.get(row); for (let x = 0; x < terminal.cols; x++) { - const data: number = line[x][CHAR_DATA_ATTR_INDEX]; - const bg = data & 0x1ff; - const flags = data >> 18; + const attr: number = line[x][CHAR_DATA_ATTR_INDEX]; + let bg = attr & 0x1ff; + const flags = attr >> 18; + + + // If inverse flag is on, the background should become the foreground. + if (flags & FLAGS.INVERSE) { + bg = (attr >> 9) & 0x1ff; + // TODO: Is this case still needed + if (bg === 257) { + bg = 15; + } + } const cellState = this._state.cache[x][y]; const needsRefresh = (bg < 16 && cellState !== bg) || cellState !== null; diff --git a/src/renderer/ForegroundRenderLayer.ts b/src/renderer/ForegroundRenderLayer.ts index c6263247..05cceb47 100644 --- a/src/renderer/ForegroundRenderLayer.ts +++ b/src/renderer/ForegroundRenderLayer.ts @@ -42,13 +42,6 @@ export class ForegroundRenderLayer implements IRenderLayer { const scaledCharWidth = Math.ceil(terminal.charMeasure.width) * window.devicePixelRatio; const scaledCharHeight = Math.ceil(terminal.charMeasure.height) * window.devicePixelRatio; - // TODO: Needs to react to terminal resize - // Initialize image data - // if (!this._imageData) { - // this._imageData = textCtx.createImageData(scaledCharWidth * this._terminal.cols * window.devicePixelRatio, scaledCharHeight * this._terminal.rows * window.devicePixelRatio); - // this._imageData.data.set(createBackgroundFillData(this._imageData.width, this._imageData.height, 255, 0, 0, 255)); - // } - // TODO: Ensure that the render is eventually performed // Don't bother render until the atlas bitmap is ready if (!this._charAtlas) { @@ -81,13 +74,22 @@ export class ForegroundRenderLayer implements IRenderLayer { this._ctx.clearRect(x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); // Skip rendering if the character is invisible - if (!code || code === 32/*' '*/) { + if (!code || code === 32 /*' '*/) { continue; } let fg = (attr >> 9) & 0x1ff; const flags = attr >> 18; + // If inverse flag is on, the foreground should become the background. + if (flags & FLAGS.INVERSE) { + fg = attr & 0x1ff; + // TODO: Is this case still needed + if (fg === 257) { + fg = 0; + } + } + if (flags & FLAGS.BOLD) { this._ctx.font = `bold ${this._ctx.font}`; // Convert the FG color to the bold variant @@ -116,7 +118,7 @@ export class ForegroundRenderLayer implements IRenderLayer { // this._ctx.drawImage(this._charAtlas, 0, 0); } - private _drawUnicodeChar(char: string, width: number, fg: number, x: number, y: number, scaledCharWidth: number, scaledCharHeight: number) { + private _drawUnicodeChar(char: string, width: number, fg: number, x: number, y: number, scaledCharWidth: number, scaledCharHeight: number): void { this._ctx.save(); this._ctx.font = `${16 * window.devicePixelRatio}px courier`; this._ctx.textBaseline = 'top'; From a224ed7cc7b8ec1770efe315e8ed1806a75d7b2c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 30 Aug 2017 23:06:55 -0700 Subject: [PATCH 120/378] Move canvas Renderer into folder --- src/Terminal.ts | 2 +- src/{RendererCanvas.ts => renderer/Renderer.ts} | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) rename src/{RendererCanvas.ts => renderer/Renderer.ts} (93%) diff --git a/src/Terminal.ts b/src/Terminal.ts index c076d410..86c18a41 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -30,7 +30,7 @@ import { C0 } from './EscapeSequences'; import { InputHandler } from './InputHandler'; import { Parser } from './Parser'; // import { Renderer } from './Renderer'; -import { Renderer } from './RendererCanvas'; +import { Renderer } from './renderer/Renderer'; import { Linkifier } from './Linkifier'; import { SelectionManager } from './SelectionManager'; import { CharMeasure } from './utils/CharMeasure'; diff --git a/src/RendererCanvas.ts b/src/renderer/Renderer.ts similarity index 93% rename from src/RendererCanvas.ts rename to src/renderer/Renderer.ts index bca5f70e..da1ca14d 100644 --- a/src/RendererCanvas.ts +++ b/src/renderer/Renderer.ts @@ -2,13 +2,13 @@ * @license MIT */ -import { ITerminal } from './Interfaces'; -import { DomElementObjectPool } from './utils/DomElementObjectPool'; -import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from './Buffer'; -import { createBackgroundFillData } from './renderer/Canvas'; -import { IRenderLayer } from './renderer/Interfaces'; -import { BackgroundRenderLayer } from './renderer/BackgroundRenderLayer'; -import { ForegroundRenderLayer } from './renderer/ForegroundRenderLayer'; +import { ITerminal } from '../Interfaces'; +import { DomElementObjectPool } from '../utils/DomElementObjectPool'; +import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from '../Buffer'; +import { createBackgroundFillData } from './Canvas'; +import { IRenderLayer } from './Interfaces'; +import { BackgroundRenderLayer } from './BackgroundRenderLayer'; +import { ForegroundRenderLayer } from './ForegroundRenderLayer'; /** * The maximum number of refresh frames to skip when the write buffer is non- From 7aff45c2dc19f55779ceeaaee78fc8956db78ab2 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 30 Aug 2017 23:39:33 -0700 Subject: [PATCH 121/378] Remove additional frame skipping as rendering is so fast now --- src/renderer/Renderer.ts | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index da1ca14d..61a4b110 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -10,17 +10,9 @@ import { IRenderLayer } from './Interfaces'; import { BackgroundRenderLayer } from './BackgroundRenderLayer'; import { ForegroundRenderLayer } from './ForegroundRenderLayer'; -/** - * The maximum number of refresh frames to skip when the write buffer is non- - * empty. Note that these frames may be intermingled with frames that are - * skipped via requestAnimationFrame's mechanism. - */ -const MAX_REFRESH_FRAME_SKIP = 5; - export class Renderer { /** A queue of the rows to be refreshed */ private _refreshRowsQueue: {start: number, end: number}[] = []; - private _refreshFramesSkipped = 0; private _refreshAnimationFrame = null; private _renderLayers: IRenderLayer[]; @@ -66,17 +58,6 @@ export class Renderer { * necessary before queueing up the next one. */ private _refreshLoop(): void { - // Skip MAX_REFRESH_FRAME_SKIP frames if the writeBuffer is non-empty as it - // will need to be immediately refreshed anyway. This saves a lot of - // rendering time as the viewport DOM does not need to be refreshed, no - // scroll events, no layouts, etc. - const skipFrame = this._terminal.writeBuffer.length > 0 && this._refreshFramesSkipped++ <= MAX_REFRESH_FRAME_SKIP; - if (skipFrame) { - this._refreshAnimationFrame = window.requestAnimationFrame(this._refreshLoop.bind(this)); - return; - } - - this._refreshFramesSkipped = 0; let start; let end; if (this._refreshRowsQueue.length > 4) { From 5953bbab42ffa956d122aef0a1d5a9c554386d64 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 30 Aug 2017 23:44:52 -0700 Subject: [PATCH 122/378] Start SelectionRenderLayer --- src/Terminal.ts | 6 +- src/renderer/BackgroundRenderLayer.ts | 1 - src/renderer/ForegroundRenderLayer.ts | 1 + src/renderer/SelectionRenderLayer.ts | 83 +++++++++++++++++++++++++++ src/renderer/Types.ts | 5 ++ 5 files changed, 92 insertions(+), 4 deletions(-) create mode 100644 src/renderer/SelectionRenderLayer.ts diff --git a/src/Terminal.ts b/src/Terminal.ts index 86c18a41..594a6fb2 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -752,9 +752,9 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.on('resize', () => this.renderer.onResize(this.cols, this.rows)); this.charMeasure.on('charsizechanged', () => this.renderer.onCharSizeChanged(this.charMeasure.width, this.charMeasure.height)); this.selectionManager = new SelectionManager(this, this.buffer, this.rowContainer, this.charMeasure); - this.selectionManager.on('refresh', data => { - this.renderer.refreshSelection(data.start, data.end); - }); + // this.selectionManager.on('refresh', data => { + // this.renderer.refreshSelection(data.start, data.end); + // }); this.selectionManager.on('newselection', text => { // If there's a new selection, put it into the textarea, focus and select it // in order to register it as a selection on the OS. This event is fired diff --git a/src/renderer/BackgroundRenderLayer.ts b/src/renderer/BackgroundRenderLayer.ts index ea7bea60..dac476a0 100644 --- a/src/renderer/BackgroundRenderLayer.ts +++ b/src/renderer/BackgroundRenderLayer.ts @@ -39,7 +39,6 @@ export class BackgroundRenderLayer implements IRenderLayer { let bg = attr & 0x1ff; const flags = attr >> 18; - // If inverse flag is on, the background should become the foreground. if (flags & FLAGS.INVERSE) { bg = (attr >> 9) & 0x1ff; diff --git a/src/renderer/ForegroundRenderLayer.ts b/src/renderer/ForegroundRenderLayer.ts index 05cceb47..2b1ff272 100644 --- a/src/renderer/ForegroundRenderLayer.ts +++ b/src/renderer/ForegroundRenderLayer.ts @@ -55,6 +55,7 @@ export class ForegroundRenderLayer implements IRenderLayer { for (let y = startRow; y <= endRow; y++) { let row = y + terminal.buffer.ydisp; let line = terminal.buffer.lines.get(row); + for (let x = 0; x < terminal.cols; x++) { const charData = line[x]; const code: number = charData[CHAR_DATA_CODE_INDEX]; diff --git a/src/renderer/SelectionRenderLayer.ts b/src/renderer/SelectionRenderLayer.ts new file mode 100644 index 00000000..bc31022c --- /dev/null +++ b/src/renderer/SelectionRenderLayer.ts @@ -0,0 +1,83 @@ +import { IRenderLayer } from './Interfaces'; +import { IBuffer, ICharMeasure, ITerminal } from '../Interfaces'; +import { CHAR_DATA_ATTR_INDEX } from '../Buffer'; +import { TANGO_COLORS } from './Color'; +import { GridCache } from './GridCache'; +import { FLAGS } from './Types'; + +export class BackgroundRenderLayer implements IRenderLayer { + private _canvas: HTMLCanvasElement; + private _ctx: CanvasRenderingContext2D; + private _state: {start: [number, number], end: [number, number]}; + + constructor(container: HTMLElement) { + this._canvas = document.createElement('canvas'); + this._canvas.classList.add('xterm-bg-layer'); + this._ctx = this._canvas.getContext('2d'); + this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); + this._ctx.fillStyle = 'rgba(255, 255, 255, 0.5)'; + container.appendChild(this._canvas); + this._state = { + start: null, + end: null + }; + } + + public resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void { + this._canvas.width = canvasWidth * window.devicePixelRatio; + this._canvas.height = canvasHeight * window.devicePixelRatio; + this._canvas.style.width = `${canvasWidth}px`; + this._canvas.style.height = `${canvasHeight}px`; + } + + public render(terminal: ITerminal, startRow: number, endRow: number): void { + const scaledCharWidth = Math.ceil(terminal.charMeasure.width) * window.devicePixelRatio; + const scaledCharHeight = Math.ceil(terminal.charMeasure.height) * window.devicePixelRatio; + + const start = terminal.selectionManager.selectionStart; + const end = terminal.selectionManager.selectionEnd; + + // TODO: Need to redraw selection if the viewport has moved + + // Selection has not changed + if (this._state.start === start || this._state.end === end) { + return; + } + + // Remove all selections + this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); + + // Selection does not exist + if (!start || !end) { + return; + } + + // Translate from buffer position to viewport position + const viewportStartRow = start[1] - terminal.buffer.ydisp; + const viewportEndRow = end[1] - terminal.buffer.ydisp; + const viewportCappedStartRow = Math.max(viewportStartRow, 0); + const viewportCappedEndRow = Math.min(viewportEndRow, terminal.rows - 1); + + // No need to draw the selection + if (viewportCappedStartRow >= terminal.rows || viewportCappedEndRow < 0) { + return; + } + + // Create the selections + // Draw first row + const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0; + const endCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : terminal.cols; + this._ctx.fillRect(startCol * scaledCharWidth, viewportCappedStartRow * scaledCharHeight, (endCol - startCol) * scaledCharWidth, scaledCharHeight); + // documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow, startCol, endCol)); + // Draw middle rows + // const middleRowsCount = viewportCappedEndRow - viewportCappedStartRow - 1; + // documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow + 1, 0, this._terminal.cols, middleRowsCount)); + // // Draw final row + // if (viewportCappedStartRow !== viewportCappedEndRow) { + // // Only draw viewportEndRow if it's not the same as viewporttartRow + // const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : this._terminal.cols; + // documentFragment.appendChild(this._createSelectionElement(viewportCappedEndRow, 0, endCol)); + // } + // this._terminal.selectionContainer.appendChild(documentFragment); + } +} diff --git a/src/renderer/Types.ts b/src/renderer/Types.ts index 09e74439..621db0bd 100644 --- a/src/renderer/Types.ts +++ b/src/renderer/Types.ts @@ -8,3 +8,8 @@ export enum FLAGS { INVERSE = 8, INVISIBLE = 16 }; + +export type Point = { + x: number, + y: number +}; From 3d1bb2c60fddbc1e0b4e8b7e9585b6e859763d28 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 31 Aug 2017 00:09:23 -0700 Subject: [PATCH 123/378] Support selection --- src/SelectionManager.ts | 5 ++-- src/Terminal.ts | 6 ++-- src/renderer/BackgroundRenderLayer.ts | 4 +-- src/renderer/ForegroundRenderLayer.ts | 4 +-- src/renderer/Interfaces.ts | 7 +++++ src/renderer/Renderer.ts | 32 +++++++++++++++------ src/renderer/SelectionRenderLayer.ts | 40 ++++++++++++--------------- src/xterm.css | 10 ++----- 8 files changed, 59 insertions(+), 49 deletions(-) diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 1aaec14b..1e3fe9b8 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -116,8 +116,6 @@ export class SelectionManager extends EventEmitter implements ISelectionManager this._mouseMoveListener = event => this._onMouseMove(event); this._mouseUpListener = event => this._onMouseUp(event); - this._rowContainer.addEventListener('mousedown', event => this._onMouseDown(event)); - // Only adjust the selection on trim, shiftElements is rarely used (only in // reverseIndex) and delete in a splice is only ever used when the same // number of elements was just added. Given this is could actually be @@ -312,7 +310,8 @@ export class SelectionManager extends EventEmitter implements ISelectionManager * Handles te mousedown event, setting up for a new selection. * @param event The mousedown event. */ - private _onMouseDown(event: MouseEvent): void { + public onMouseDown(event: MouseEvent): void { + console.log('mousedown selectionmanager'); // If we have selection, we want the context menu on right click even if the // terminal is in mouse mode. if (event.button === 2 && this.hasSelection) { diff --git a/src/Terminal.ts b/src/Terminal.ts index 594a6fb2..f88ebb7f 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -751,10 +751,10 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.renderer = new Renderer(this); this.on('resize', () => this.renderer.onResize(this.cols, this.rows)); this.charMeasure.on('charsizechanged', () => this.renderer.onCharSizeChanged(this.charMeasure.width, this.charMeasure.height)); + this.selectionManager = new SelectionManager(this, this.buffer, this.rowContainer, this.charMeasure); - // this.selectionManager.on('refresh', data => { - // this.renderer.refreshSelection(data.start, data.end); - // }); + this.element.addEventListener('mousedown', (e: MouseEvent) => this.selectionManager.onMouseDown(e)); + this.selectionManager.on('refresh', data => this.renderer.onSelectionChanged(data.start, data.end)); this.selectionManager.on('newselection', text => { // If there's a new selection, put it into the textarea, focus and select it // in order to register it as a selection on the OS. This event is fired diff --git a/src/renderer/BackgroundRenderLayer.ts b/src/renderer/BackgroundRenderLayer.ts index dac476a0..162b96a4 100644 --- a/src/renderer/BackgroundRenderLayer.ts +++ b/src/renderer/BackgroundRenderLayer.ts @@ -1,11 +1,11 @@ -import { IRenderLayer } from './Interfaces'; +import { IDataRenderLayer } from './Interfaces'; import { IBuffer, ICharMeasure, ITerminal } from '../Interfaces'; import { CHAR_DATA_ATTR_INDEX } from '../Buffer'; import { TANGO_COLORS } from './Color'; import { GridCache } from './GridCache'; import { FLAGS } from './Types'; -export class BackgroundRenderLayer implements IRenderLayer { +export class BackgroundRenderLayer implements IDataRenderLayer { private _canvas: HTMLCanvasElement; private _ctx: CanvasRenderingContext2D; private _state: GridCache; diff --git a/src/renderer/ForegroundRenderLayer.ts b/src/renderer/ForegroundRenderLayer.ts index 2b1ff272..0f74ff28 100644 --- a/src/renderer/ForegroundRenderLayer.ts +++ b/src/renderer/ForegroundRenderLayer.ts @@ -1,4 +1,4 @@ -import { IRenderLayer } from './Interfaces'; +import { IDataRenderLayer } from './Interfaces'; import { IBuffer, ICharMeasure, ITerminal } from '../Interfaces'; import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from '../Buffer'; import { TANGO_COLORS } from './Color'; @@ -6,7 +6,7 @@ import { FLAGS } from './Types'; import { GridCache } from './GridCache'; import { CharData } from '../Types'; -export class ForegroundRenderLayer implements IRenderLayer { +export class ForegroundRenderLayer implements IDataRenderLayer { private _canvas: HTMLCanvasElement; private _ctx: CanvasRenderingContext2D; private _charAtlas: ImageBitmap; diff --git a/src/renderer/Interfaces.ts b/src/renderer/Interfaces.ts index 4d275375..4d6d2d3b 100644 --- a/src/renderer/Interfaces.ts +++ b/src/renderer/Interfaces.ts @@ -2,5 +2,12 @@ import { ITerminal } from '../Interfaces'; export interface IRenderLayer { resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void; +} + +export interface IDataRenderLayer extends IRenderLayer { render(terminal: ITerminal, startRow: number, endRow: number): void; } + +export interface ISelectionRenderLayer extends IRenderLayer { + render(terminal: ITerminal, start: [number, number], end: [number, number]): void; +} diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 61a4b110..b4d9b153 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -6,37 +6,51 @@ import { ITerminal } from '../Interfaces'; import { DomElementObjectPool } from '../utils/DomElementObjectPool'; import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from '../Buffer'; import { createBackgroundFillData } from './Canvas'; -import { IRenderLayer } from './Interfaces'; +import { IDataRenderLayer, ISelectionRenderLayer } from './Interfaces'; import { BackgroundRenderLayer } from './BackgroundRenderLayer'; import { ForegroundRenderLayer } from './ForegroundRenderLayer'; +import { SelectionRenderLayer } from './SelectionRenderLayer'; export class Renderer { /** A queue of the rows to be refreshed */ private _refreshRowsQueue: {start: number, end: number}[] = []; private _refreshAnimationFrame = null; - private _renderLayers: IRenderLayer[]; + private _dataRenderLayers: IDataRenderLayer[]; + private _selectionRenderLayers: ISelectionRenderLayer[]; constructor(private _terminal: ITerminal) { - this._renderLayers = [ + this._dataRenderLayers = [ new BackgroundRenderLayer(this._terminal.element), new ForegroundRenderLayer(this._terminal.element) ]; + this._selectionRenderLayers = [ + new SelectionRenderLayer(this._terminal.element) + ]; } public onResize(cols: number, rows: number): void { const width = Math.ceil(this._terminal.charMeasure.width) * this._terminal.cols; const height = Math.ceil(this._terminal.charMeasure.height) * this._terminal.rows; - for (let i = 0; i < this._renderLayers.length; i++) { - this._renderLayers[i].resize(this._terminal, width, height, false); + for (let i = 0; i < this._dataRenderLayers.length; i++) { + this._dataRenderLayers[i].resize(this._terminal, width, height, false); } } public onCharSizeChanged(charWidth: number, charHeight: number): void { const width = Math.ceil(charWidth) * this._terminal.cols; const height = Math.ceil(charHeight) * this._terminal.rows; - for (let i = 0; i < this._renderLayers.length; i++) { - this._renderLayers[i].resize(this._terminal, width, height, true); + for (let i = 0; i < this._dataRenderLayers.length; i++) { + this._dataRenderLayers[i].resize(this._terminal, width, height, true); + } + for (let i = 0; i < this._selectionRenderLayers.length; i++) { + this._selectionRenderLayers[i].resize(this._terminal, width, height, true); + } + } + + public onSelectionChanged(start: [number, number], end: [number, number]): void { + for (let i = 0; i < this._selectionRenderLayers.length; i++) { + this._selectionRenderLayers[i].render(this._terminal, start, end); } } @@ -81,8 +95,8 @@ export class Renderer { this._refreshAnimationFrame = null; // Render - for (let i = 0; i < this._renderLayers.length; i++) { - this._renderLayers[i].render(this._terminal, start, end); + for (let i = 0; i < this._dataRenderLayers.length; i++) { + this._dataRenderLayers[i].render(this._terminal, start, end); } this._terminal.emit('refresh', {start, end}); } diff --git a/src/renderer/SelectionRenderLayer.ts b/src/renderer/SelectionRenderLayer.ts index bc31022c..b630359e 100644 --- a/src/renderer/SelectionRenderLayer.ts +++ b/src/renderer/SelectionRenderLayer.ts @@ -1,21 +1,20 @@ -import { IRenderLayer } from './Interfaces'; +import { ISelectionRenderLayer } from './Interfaces'; import { IBuffer, ICharMeasure, ITerminal } from '../Interfaces'; import { CHAR_DATA_ATTR_INDEX } from '../Buffer'; import { TANGO_COLORS } from './Color'; import { GridCache } from './GridCache'; import { FLAGS } from './Types'; -export class BackgroundRenderLayer implements IRenderLayer { +export class SelectionRenderLayer implements ISelectionRenderLayer { private _canvas: HTMLCanvasElement; private _ctx: CanvasRenderingContext2D; private _state: {start: [number, number], end: [number, number]}; constructor(container: HTMLElement) { this._canvas = document.createElement('canvas'); - this._canvas.classList.add('xterm-bg-layer'); + this._canvas.classList.add('xterm-selection-layer'); this._ctx = this._canvas.getContext('2d'); this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); - this._ctx.fillStyle = 'rgba(255, 255, 255, 0.5)'; container.appendChild(this._canvas); this._state = { start: null, @@ -30,15 +29,10 @@ export class BackgroundRenderLayer implements IRenderLayer { this._canvas.style.height = `${canvasHeight}px`; } - public render(terminal: ITerminal, startRow: number, endRow: number): void { + public render(terminal: ITerminal, start: [number, number], end: [number, number]): void { const scaledCharWidth = Math.ceil(terminal.charMeasure.width) * window.devicePixelRatio; const scaledCharHeight = Math.ceil(terminal.charMeasure.height) * window.devicePixelRatio; - const start = terminal.selectionManager.selectionStart; - const end = terminal.selectionManager.selectionEnd; - - // TODO: Need to redraw selection if the viewport has moved - // Selection has not changed if (this._state.start === start || this._state.end === end) { return; @@ -63,21 +57,21 @@ export class BackgroundRenderLayer implements IRenderLayer { return; } - // Create the selections // Draw first row const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0; - const endCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : terminal.cols; - this._ctx.fillRect(startCol * scaledCharWidth, viewportCappedStartRow * scaledCharHeight, (endCol - startCol) * scaledCharWidth, scaledCharHeight); - // documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow, startCol, endCol)); + const startRowEndCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : terminal.cols; + this._ctx.fillStyle = 'rgba(255,255,255,0.3)'; + this._ctx.fillRect(startCol * scaledCharWidth, viewportCappedStartRow * scaledCharHeight, (startRowEndCol - startCol) * scaledCharWidth, scaledCharHeight); + // Draw middle rows - // const middleRowsCount = viewportCappedEndRow - viewportCappedStartRow - 1; - // documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow + 1, 0, this._terminal.cols, middleRowsCount)); - // // Draw final row - // if (viewportCappedStartRow !== viewportCappedEndRow) { - // // Only draw viewportEndRow if it's not the same as viewporttartRow - // const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : this._terminal.cols; - // documentFragment.appendChild(this._createSelectionElement(viewportCappedEndRow, 0, endCol)); - // } - // this._terminal.selectionContainer.appendChild(documentFragment); + const middleRowsCount = Math.max(viewportCappedEndRow - viewportCappedStartRow - 1, 0); + this._ctx.fillRect(0, (viewportCappedStartRow + 1) * scaledCharHeight, terminal.cols * scaledCharWidth, middleRowsCount * scaledCharHeight); + + // Draw final row + if (viewportCappedStartRow !== viewportCappedEndRow) { + // Only draw viewportEndRow if it's not the same as viewporttartRow + const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : terminal.cols; + this._ctx.fillRect(0, viewportCappedEndRow * scaledCharHeight, endCol * scaledCharWidth, scaledCharHeight); + } } } diff --git a/src/xterm.css b/src/xterm.css index 2ee98e54..8a48c6f7 100644 --- a/src/xterm.css +++ b/src/xterm.css @@ -164,13 +164,9 @@ top: 0; } -.terminal .xterm-bg-layer { - z-index: 0; -} - -.terminal .xterm-fg-layer { - z-index: 1; -} +.terminal .xterm-bg-layer { z-index: 0; } +.terminal .xterm-fg-layer { z-index: 1; } +.terminal .xterm-selection-layer { z-index: 2; } .terminal .xterm-rows { position: absolute; From d601f0aec257d78e9312477513e01c1189afec47 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 31 Aug 2017 00:13:01 -0700 Subject: [PATCH 124/378] Hang on to selection state to avoid redrawing --- src/SelectionManager.ts | 1 - src/renderer/SelectionRenderLayer.ts | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 1e3fe9b8..dc58858e 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -311,7 +311,6 @@ export class SelectionManager extends EventEmitter implements ISelectionManager * @param event The mousedown event. */ public onMouseDown(event: MouseEvent): void { - console.log('mousedown selectionmanager'); // If we have selection, we want the context menu on right click even if the // terminal is in mouse mode. if (event.button === 2 && this.hasSelection) { diff --git a/src/renderer/SelectionRenderLayer.ts b/src/renderer/SelectionRenderLayer.ts index b630359e..b0a6a5a6 100644 --- a/src/renderer/SelectionRenderLayer.ts +++ b/src/renderer/SelectionRenderLayer.ts @@ -73,5 +73,9 @@ export class SelectionRenderLayer implements ISelectionRenderLayer { const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : terminal.cols; this._ctx.fillRect(0, viewportCappedEndRow * scaledCharHeight, endCol * scaledCharWidth, scaledCharHeight); } + + // Save state for next render + this._state.start = [start[0], start[1]]; + this._state.end = [end[0], end[1]]; } } From dff3d168317a8bc70070e9755047161d8a0fcd01 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 31 Aug 2017 00:25:52 -0700 Subject: [PATCH 125/378] Support 256 color --- src/renderer/BackgroundRenderLayer.ts | 8 +++---- src/renderer/Color.ts | 31 +++++++++++++++++++++++++++ src/renderer/ForegroundRenderLayer.ts | 15 +++++++------ 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/src/renderer/BackgroundRenderLayer.ts b/src/renderer/BackgroundRenderLayer.ts index 162b96a4..d63a561f 100644 --- a/src/renderer/BackgroundRenderLayer.ts +++ b/src/renderer/BackgroundRenderLayer.ts @@ -1,7 +1,7 @@ import { IDataRenderLayer } from './Interfaces'; import { IBuffer, ICharMeasure, ITerminal } from '../Interfaces'; import { CHAR_DATA_ATTR_INDEX } from '../Buffer'; -import { TANGO_COLORS } from './Color'; +import { COLORS } from './Color'; import { GridCache } from './GridCache'; import { FLAGS } from './Types'; @@ -49,11 +49,11 @@ export class BackgroundRenderLayer implements IDataRenderLayer { } const cellState = this._state.cache[x][y]; - const needsRefresh = (bg < 16 && cellState !== bg) || cellState !== null; + const needsRefresh = (bg < 256 && cellState !== bg) || cellState !== null; if (needsRefresh) { - if (bg < 16) { + if (bg < 256) { this._ctx.save(); - this._ctx.fillStyle = TANGO_COLORS[bg]; + this._ctx.fillStyle = COLORS[bg]; this._ctx.fillRect(x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); this._ctx.restore(); this._state.cache[x][y] = bg; diff --git a/src/renderer/Color.ts b/src/renderer/Color.ts index 250c5c50..4f75664c 100644 --- a/src/renderer/Color.ts +++ b/src/renderer/Color.ts @@ -21,3 +21,34 @@ export const TANGO_COLORS = [ '#34e2e2', '#eeeeec' ]; + +export const COLORS: string[] = (function(): string[] { + let colors = TANGO_COLORS.slice(); + let r = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]; + let i; + + // 16-231 + i = 0; + for (; i < 216; i++) { + out(r[(i / 36) % 6 | 0], r[(i / 6) % 6 | 0], r[i % 6]); + } + + // 232-255 (grey) + i = 0; + let c: number; + for (; i < 24; i++) { + c = 8 + i * 10; + out(c, c, c); + } + + function out(r: number, g: number, b: number): void { + colors.push('#' + hex(r) + hex(g) + hex(b)); + } + + function hex(c: number): string { + let s = c.toString(16); + return s.length < 2 ? '0' + s : s; + } + + return colors; +})(); diff --git a/src/renderer/ForegroundRenderLayer.ts b/src/renderer/ForegroundRenderLayer.ts index 0f74ff28..fbfb1a6f 100644 --- a/src/renderer/ForegroundRenderLayer.ts +++ b/src/renderer/ForegroundRenderLayer.ts @@ -1,7 +1,7 @@ import { IDataRenderLayer } from './Interfaces'; import { IBuffer, ICharMeasure, ITerminal } from '../Interfaces'; import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from '../Buffer'; -import { TANGO_COLORS } from './Color'; +import { COLORS } from './Color'; import { FLAGS } from './Types'; import { GridCache } from './GridCache'; import { CharData } from '../Types'; @@ -104,13 +104,13 @@ export class ForegroundRenderLayer implements IDataRenderLayer { colorIndex = fg + 1; } - if (code < 256) { + if (code < 256 && (colorIndex > 0 || fg > 255)) { // ImageBitmap's draw about twice as fast as from a canvas this._ctx.drawImage(this._charAtlas, code * scaledCharWidth, colorIndex * scaledCharHeight, scaledCharWidth, scaledCharHeight, x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); } else { // TODO: Evaluate how long it takes to convert from a number const width: number = charData[CHAR_DATA_WIDTH_INDEX]; - this._drawUnicodeChar(char, width, fg, x, y, scaledCharWidth, scaledCharHeight); + this._drawUncachedChar(char, width, fg, x, y, scaledCharWidth, scaledCharHeight); } } } @@ -119,13 +119,14 @@ export class ForegroundRenderLayer implements IDataRenderLayer { // this._ctx.drawImage(this._charAtlas, 0, 0); } - private _drawUnicodeChar(char: string, width: number, fg: number, x: number, y: number, scaledCharWidth: number, scaledCharHeight: number): void { + private _drawUncachedChar(char: string, width: number, fg: number, x: number, y: number, scaledCharWidth: number, scaledCharHeight: number): void { this._ctx.save(); this._ctx.font = `${16 * window.devicePixelRatio}px courier`; this._ctx.textBaseline = 'top'; - if (fg < 16) { - this._ctx.fillStyle = TANGO_COLORS[fg]; + // 256 color support + if (fg < 256) { + this._ctx.fillStyle = COLORS[fg]; } else { this._ctx.fillStyle = '#ffffff'; } @@ -174,7 +175,7 @@ class CharAtlasGenerator { this._ctx.clearRect(0, y, this._canvas.width, scaledCharHeight); // Draw ascii characters for (let i = 0; i < 256; i++) { - this._ctx.fillStyle = TANGO_COLORS[colorIndex]; + this._ctx.fillStyle = COLORS[colorIndex]; this._ctx.fillText(String.fromCharCode(i), i * scaledCharWidth, y); } } From d3650d9d8068df292560d2e38734e441b309a0cf Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 31 Aug 2017 07:54:16 -0700 Subject: [PATCH 126/378] Draw selection underneath foreground --- src/renderer/Color.ts | 2 +- src/renderer/SelectionRenderLayer.ts | 1 - src/xterm.css | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/renderer/Color.ts b/src/renderer/Color.ts index 4f75664c..eb3954ea 100644 --- a/src/renderer/Color.ts +++ b/src/renderer/Color.ts @@ -1,7 +1,7 @@ // TODO: Ideally colors would be exposed through some theme manager since colors // are moving to JS. -export const TANGO_COLORS = [ +const TANGO_COLORS = [ // dark: '#2e3436', '#cc0000', diff --git a/src/renderer/SelectionRenderLayer.ts b/src/renderer/SelectionRenderLayer.ts index b0a6a5a6..091788f9 100644 --- a/src/renderer/SelectionRenderLayer.ts +++ b/src/renderer/SelectionRenderLayer.ts @@ -1,7 +1,6 @@ import { ISelectionRenderLayer } from './Interfaces'; import { IBuffer, ICharMeasure, ITerminal } from '../Interfaces'; import { CHAR_DATA_ATTR_INDEX } from '../Buffer'; -import { TANGO_COLORS } from './Color'; import { GridCache } from './GridCache'; import { FLAGS } from './Types'; diff --git a/src/xterm.css b/src/xterm.css index 8a48c6f7..dcbd6b69 100644 --- a/src/xterm.css +++ b/src/xterm.css @@ -165,8 +165,8 @@ } .terminal .xterm-bg-layer { z-index: 0; } -.terminal .xterm-fg-layer { z-index: 1; } -.terminal .xterm-selection-layer { z-index: 2; } +.terminal .xterm-selection-layer { z-index: 1; } +.terminal .xterm-fg-layer { z-index: 2; } .terminal .xterm-rows { position: absolute; From fd18f7cba78843fa4094d99b339edfd0f62be4fc Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 31 Aug 2017 17:02:24 -0700 Subject: [PATCH 127/378] Support basic cursor --- src/renderer/CursorRenderLayer.ts | 75 +++++++++++++++++++++++++++ src/renderer/ForegroundRenderLayer.ts | 4 +- src/renderer/Renderer.ts | 4 +- src/xterm.css | 1 + 4 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 src/renderer/CursorRenderLayer.ts diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts new file mode 100644 index 00000000..d9a21d15 --- /dev/null +++ b/src/renderer/CursorRenderLayer.ts @@ -0,0 +1,75 @@ +import { IDataRenderLayer } from './Interfaces'; +import { IBuffer, ICharMeasure, ITerminal } from '../Interfaces'; +import { CHAR_DATA_ATTR_INDEX } from '../Buffer'; +import { COLORS } from './Color'; +import { GridCache } from './GridCache'; +import { FLAGS } from './Types'; + +export class CursorRenderLayer implements IDataRenderLayer { + private _canvas: HTMLCanvasElement; + private _ctx: CanvasRenderingContext2D; + private _state: [number, number]; + + constructor(container: HTMLElement) { + this._canvas = document.createElement('canvas'); + this._canvas.classList.add('xterm-cursor-layer'); + this._ctx = this._canvas.getContext('2d'); + this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); + container.appendChild(this._canvas); + this._state = null; + } + + public resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void { + this._canvas.width = canvasWidth * window.devicePixelRatio; + this._canvas.height = canvasHeight * window.devicePixelRatio; + this._canvas.style.width = `${canvasWidth}px`; + this._canvas.style.height = `${canvasHeight}px`; + } + + public render(terminal: ITerminal, startRow: number, endRow: number): void { + // TODO: Track blur/focus somehow, support unfocused cursor + + const scaledCharWidth = Math.ceil(terminal.charMeasure.width) * window.devicePixelRatio; + const scaledCharHeight = Math.ceil(terminal.charMeasure.height) * window.devicePixelRatio; + + // Don't draw the cursor if it's hidden + if (!terminal.cursorState || terminal.cursorHidden) { + this._clearCursor(scaledCharWidth, scaledCharHeight); + return; + } + + const cursorY = terminal.buffer.ybase + terminal.buffer.y; + const viewportRelativeCursorY = cursorY - terminal.buffer.ydisp; + + // Don't draw the cursor if it's off-screen + if (viewportRelativeCursorY < 0 || viewportRelativeCursorY >= terminal.rows) { + this._clearCursor(scaledCharWidth, scaledCharHeight); + return; + } + + if (this._state) { + // The cursor is already in the correct spot, don't redraw + if (this._state[0] === terminal.buffer.x && this._state[1] === viewportRelativeCursorY) { + return; + } + this._clearCursor(scaledCharWidth, scaledCharHeight); + } + + // TODO: Draw text in COLORS[0], using the char atlas if possible + // const charData = terminal.buffer.lines.get(viewportRelativeCursorY)[terminal.buffer.x]; + + this._ctx.save(); + this._ctx.fillStyle = COLORS[7]; + this._ctx.fillRect(terminal.buffer.x * scaledCharWidth, viewportRelativeCursorY * scaledCharHeight, scaledCharWidth, scaledCharHeight); + this._ctx.restore(); + + this._state = [terminal.buffer.x, viewportRelativeCursorY]; + } + + private _clearCursor(scaledCharWidth: number, scaledCharHeight: number): void { + if (this._state) { + this._ctx.clearRect(this._state[0] * scaledCharWidth, this._state[1] * scaledCharHeight, scaledCharWidth, scaledCharHeight); + this._state = null; + } + } +} diff --git a/src/renderer/ForegroundRenderLayer.ts b/src/renderer/ForegroundRenderLayer.ts index fbfb1a6f..db23a041 100644 --- a/src/renderer/ForegroundRenderLayer.ts +++ b/src/renderer/ForegroundRenderLayer.ts @@ -53,8 +53,8 @@ export class ForegroundRenderLayer implements IDataRenderLayer { this._ctx.font = `${16 * window.devicePixelRatio}px courier`; for (let y = startRow; y <= endRow; y++) { - let row = y + terminal.buffer.ydisp; - let line = terminal.buffer.lines.get(row); + const row = y + terminal.buffer.ydisp; + const line = terminal.buffer.lines.get(row); for (let x = 0; x < terminal.cols; x++) { const charData = line[x]; diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index b4d9b153..b340afba 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -10,6 +10,7 @@ import { IDataRenderLayer, ISelectionRenderLayer } from './Interfaces'; import { BackgroundRenderLayer } from './BackgroundRenderLayer'; import { ForegroundRenderLayer } from './ForegroundRenderLayer'; import { SelectionRenderLayer } from './SelectionRenderLayer'; +import { CursorRenderLayer } from './CursorRenderLayer'; export class Renderer { /** A queue of the rows to be refreshed */ @@ -22,7 +23,8 @@ export class Renderer { constructor(private _terminal: ITerminal) { this._dataRenderLayers = [ new BackgroundRenderLayer(this._terminal.element), - new ForegroundRenderLayer(this._terminal.element) + new ForegroundRenderLayer(this._terminal.element), + new CursorRenderLayer(this._terminal.element) ]; this._selectionRenderLayers = [ new SelectionRenderLayer(this._terminal.element) diff --git a/src/xterm.css b/src/xterm.css index dcbd6b69..6b044217 100644 --- a/src/xterm.css +++ b/src/xterm.css @@ -167,6 +167,7 @@ .terminal .xterm-bg-layer { z-index: 0; } .terminal .xterm-selection-layer { z-index: 1; } .terminal .xterm-fg-layer { z-index: 2; } +.terminal .xterm-cursor-layer { z-index: 3; } .terminal .xterm-rows { position: absolute; From a91380a76fdff71122ed7b25883a40f5fae9d0ca Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 31 Aug 2017 17:13:15 -0700 Subject: [PATCH 128/378] Pull common parts into BaseRenderLayer --- src/renderer/BackgroundRenderLayer.ts | 18 +++++------------- src/renderer/BaseRenderLayer.ts | 23 +++++++++++++++++++++++ src/renderer/CursorRenderLayer.ts | 20 ++++---------------- src/renderer/ForegroundRenderLayer.ts | 18 +++++------------- src/renderer/Renderer.ts | 8 ++++---- src/renderer/SelectionRenderLayer.ts | 20 ++++---------------- src/xterm.css | 4 ++-- 7 files changed, 47 insertions(+), 64 deletions(-) create mode 100644 src/renderer/BaseRenderLayer.ts diff --git a/src/renderer/BackgroundRenderLayer.ts b/src/renderer/BackgroundRenderLayer.ts index d63a561f..a9722eab 100644 --- a/src/renderer/BackgroundRenderLayer.ts +++ b/src/renderer/BackgroundRenderLayer.ts @@ -4,26 +4,18 @@ import { CHAR_DATA_ATTR_INDEX } from '../Buffer'; import { COLORS } from './Color'; import { GridCache } from './GridCache'; import { FLAGS } from './Types'; +import { BaseRenderLayer } from './BaseRenderLayer'; -export class BackgroundRenderLayer implements IDataRenderLayer { - private _canvas: HTMLCanvasElement; - private _ctx: CanvasRenderingContext2D; +export class BackgroundRenderLayer extends BaseRenderLayer implements IDataRenderLayer { private _state: GridCache; - constructor(container: HTMLElement) { - this._canvas = document.createElement('canvas'); - this._canvas.classList.add('xterm-bg-layer'); - this._ctx = this._canvas.getContext('2d'); - this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); - container.appendChild(this._canvas); + constructor(container: HTMLElement, zIndex: number) { + super(container, 'bg', zIndex); this._state = new GridCache(); } public resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void { - this._canvas.width = canvasWidth * window.devicePixelRatio; - this._canvas.height = canvasHeight * window.devicePixelRatio; - this._canvas.style.width = `${canvasWidth}px`; - this._canvas.style.height = `${canvasHeight}px`; + super.resize(terminal, canvasWidth, canvasHeight, charSizeChanged); this._state.resize(terminal.cols, terminal.rows); } diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts new file mode 100644 index 00000000..6d493960 --- /dev/null +++ b/src/renderer/BaseRenderLayer.ts @@ -0,0 +1,23 @@ +import { IRenderLayer } from './Interfaces'; +import { ITerminal } from '../Interfaces'; + +export abstract class BaseRenderLayer implements IRenderLayer { + protected _canvas: HTMLCanvasElement; + protected _ctx: CanvasRenderingContext2D; + + constructor(container: HTMLElement, id: string, zIndex: number) { + this._canvas = document.createElement('canvas'); + this._canvas.id = `xterm-${id}-layer`; + this._canvas.style.zIndex = zIndex.toString(); + this._ctx = this._canvas.getContext('2d'); + this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); + container.appendChild(this._canvas); + } + + public resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void { + this._canvas.width = canvasWidth * window.devicePixelRatio; + this._canvas.height = canvasHeight * window.devicePixelRatio; + this._canvas.style.width = `${canvasWidth}px`; + this._canvas.style.height = `${canvasHeight}px`; + } +} diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index d9a21d15..11496610 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -4,28 +4,16 @@ import { CHAR_DATA_ATTR_INDEX } from '../Buffer'; import { COLORS } from './Color'; import { GridCache } from './GridCache'; import { FLAGS } from './Types'; +import { BaseRenderLayer } from './BaseRenderLayer'; -export class CursorRenderLayer implements IDataRenderLayer { - private _canvas: HTMLCanvasElement; - private _ctx: CanvasRenderingContext2D; +export class CursorRenderLayer extends BaseRenderLayer implements IDataRenderLayer { private _state: [number, number]; - constructor(container: HTMLElement) { - this._canvas = document.createElement('canvas'); - this._canvas.classList.add('xterm-cursor-layer'); - this._ctx = this._canvas.getContext('2d'); - this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); - container.appendChild(this._canvas); + constructor(container: HTMLElement, zIndex: number) { + super(container, 'cursor', zIndex); this._state = null; } - public resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void { - this._canvas.width = canvasWidth * window.devicePixelRatio; - this._canvas.height = canvasHeight * window.devicePixelRatio; - this._canvas.style.width = `${canvasWidth}px`; - this._canvas.style.height = `${canvasHeight}px`; - } - public render(terminal: ITerminal, startRow: number, endRow: number): void { // TODO: Track blur/focus somehow, support unfocused cursor diff --git a/src/renderer/ForegroundRenderLayer.ts b/src/renderer/ForegroundRenderLayer.ts index db23a041..a073f69d 100644 --- a/src/renderer/ForegroundRenderLayer.ts +++ b/src/renderer/ForegroundRenderLayer.ts @@ -5,30 +5,22 @@ import { COLORS } from './Color'; import { FLAGS } from './Types'; import { GridCache } from './GridCache'; import { CharData } from '../Types'; +import { BaseRenderLayer } from './BaseRenderLayer'; -export class ForegroundRenderLayer implements IDataRenderLayer { - private _canvas: HTMLCanvasElement; - private _ctx: CanvasRenderingContext2D; +export class ForegroundRenderLayer extends BaseRenderLayer implements IDataRenderLayer { private _charAtlas: ImageBitmap; private _state: GridCache; private _charAtlasGenerator: CharAtlasGenerator; - constructor(container: HTMLElement) { - this._canvas = document.createElement('canvas'); - this._canvas.classList.add('xterm-fg-layer'); - this._ctx = this._canvas.getContext('2d'); - this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); - container.appendChild(this._canvas); + constructor(container: HTMLElement, zIndex: number) { + super(container, 'fg', zIndex); this._charAtlasGenerator = new CharAtlasGenerator(); this._state = new GridCache(); } public resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void { - this._canvas.width = canvasWidth * window.devicePixelRatio; - this._canvas.height = canvasHeight * window.devicePixelRatio; - this._canvas.style.width = `${canvasWidth}px`; - this._canvas.style.height = `${canvasHeight}px`; + super.resize(terminal, canvasWidth, canvasHeight, charSizeChanged); this._state.resize(terminal.cols, terminal.rows); if (charSizeChanged) { this._charAtlas = null; diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index b340afba..522f7af2 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -22,12 +22,12 @@ export class Renderer { constructor(private _terminal: ITerminal) { this._dataRenderLayers = [ - new BackgroundRenderLayer(this._terminal.element), - new ForegroundRenderLayer(this._terminal.element), - new CursorRenderLayer(this._terminal.element) + new BackgroundRenderLayer(this._terminal.element, 0), + new ForegroundRenderLayer(this._terminal.element, 2), + new CursorRenderLayer(this._terminal.element, 3) ]; this._selectionRenderLayers = [ - new SelectionRenderLayer(this._terminal.element) + new SelectionRenderLayer(this._terminal.element, 1) ]; } diff --git a/src/renderer/SelectionRenderLayer.ts b/src/renderer/SelectionRenderLayer.ts index 091788f9..048be8e4 100644 --- a/src/renderer/SelectionRenderLayer.ts +++ b/src/renderer/SelectionRenderLayer.ts @@ -3,31 +3,19 @@ import { IBuffer, ICharMeasure, ITerminal } from '../Interfaces'; import { CHAR_DATA_ATTR_INDEX } from '../Buffer'; import { GridCache } from './GridCache'; import { FLAGS } from './Types'; +import { BaseRenderLayer } from './BaseRenderLayer'; -export class SelectionRenderLayer implements ISelectionRenderLayer { - private _canvas: HTMLCanvasElement; - private _ctx: CanvasRenderingContext2D; +export class SelectionRenderLayer extends BaseRenderLayer implements ISelectionRenderLayer { private _state: {start: [number, number], end: [number, number]}; - constructor(container: HTMLElement) { - this._canvas = document.createElement('canvas'); - this._canvas.classList.add('xterm-selection-layer'); - this._ctx = this._canvas.getContext('2d'); - this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); - container.appendChild(this._canvas); + constructor(container: HTMLElement, zIndex: number) { + super(container, 'selection', zIndex); this._state = { start: null, end: null }; } - public resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void { - this._canvas.width = canvasWidth * window.devicePixelRatio; - this._canvas.height = canvasHeight * window.devicePixelRatio; - this._canvas.style.width = `${canvasWidth}px`; - this._canvas.style.height = `${canvasHeight}px`; - } - public render(terminal: ITerminal, start: [number, number], end: [number, number]): void { const scaledCharWidth = Math.ceil(terminal.charMeasure.width) * window.devicePixelRatio; const scaledCharHeight = Math.ceil(terminal.charMeasure.height) * window.devicePixelRatio; diff --git a/src/xterm.css b/src/xterm.css index 6b044217..f281e527 100644 --- a/src/xterm.css +++ b/src/xterm.css @@ -164,10 +164,10 @@ top: 0; } -.terminal .xterm-bg-layer { z-index: 0; } +/* .terminal .xterm-bg-layer { z-index: 0; } .terminal .xterm-selection-layer { z-index: 1; } .terminal .xterm-fg-layer { z-index: 2; } -.terminal .xterm-cursor-layer { z-index: 3; } +.terminal .xterm-cursor-layer { z-index: 3; } */ .terminal .xterm-rows { position: absolute; From fe7b424ab83adc5d290249d714800151bbebf694 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 31 Aug 2017 17:20:50 -0700 Subject: [PATCH 129/378] Move char atlas into BaseRenderLayer --- src/renderer/BaseRenderLayer.ts | 76 +++++++++++++++++++++++++++ src/renderer/ForegroundRenderLayer.ts | 66 +---------------------- 2 files changed, 78 insertions(+), 64 deletions(-) diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 6d493960..66b07fac 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -1,10 +1,16 @@ import { IRenderLayer } from './Interfaces'; import { ITerminal } from '../Interfaces'; +import { COLORS } from './Color'; export abstract class BaseRenderLayer implements IRenderLayer { protected _canvas: HTMLCanvasElement; protected _ctx: CanvasRenderingContext2D; + protected static _charAtlas: ImageBitmap; + private static _charAtlasCharWidth: number; + private static _charAtlasCharHeight: number; + private static _charAtlasGenerator: CharAtlasGenerator; + constructor(container: HTMLElement, id: string, zIndex: number) { this._canvas = document.createElement('canvas'); this._canvas.id = `xterm-${id}-layer`; @@ -12,6 +18,10 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._ctx = this._canvas.getContext('2d'); this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); container.appendChild(this._canvas); + + if (!BaseRenderLayer._charAtlasGenerator) { + BaseRenderLayer._charAtlasGenerator = new CharAtlasGenerator(); + } } public resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void { @@ -19,5 +29,71 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._canvas.height = canvasHeight * window.devicePixelRatio; this._canvas.style.width = `${canvasWidth}px`; this._canvas.style.height = `${canvasHeight}px`; + // Only update the char atlas if the char size changed + if (charSizeChanged) { + // Only update the char atlas if an update for the right dimensions is not + // already in progress + if (BaseRenderLayer._charAtlasCharWidth !== terminal.charMeasure.width || + BaseRenderLayer._charAtlasCharHeight !== terminal.charMeasure.height) { + BaseRenderLayer._charAtlas = null; + BaseRenderLayer._charAtlasCharWidth = terminal.charMeasure.width; + BaseRenderLayer._charAtlasCharHeight = terminal.charMeasure.height; + BaseRenderLayer._charAtlasGenerator.generate(terminal.charMeasure.width, terminal.charMeasure.height).then(bitmap => { + BaseRenderLayer._charAtlas = bitmap; + }); + } + } + } +} + +class CharAtlasGenerator { + private _canvas: HTMLCanvasElement; + private _ctx: CanvasRenderingContext2D; + + constructor() { + this._canvas = document.createElement('canvas'); + this._ctx = this._canvas.getContext('2d'); + this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); + } + + public generate(charWidth: number, charHeight: number): Promise { + const scaledCharWidth = Math.ceil(charWidth) * window.devicePixelRatio; + const scaledCharHeight = Math.ceil(charHeight) * window.devicePixelRatio; + + this._canvas.width = 255 * scaledCharWidth; + this._canvas.height = (/*default*/1 + /*0-15*/16) * scaledCharHeight; + + this._ctx.save(); + this._ctx.fillStyle = '#ffffff'; + this._ctx.font = `${16 * window.devicePixelRatio}px courier`; + this._ctx.textBaseline = 'top'; + + // Default color + for (let i = 0; i < 256; i++) { + this._ctx.fillText(String.fromCharCode(i), i * scaledCharWidth, 0); + } + + // Colors 0-15 + for (let colorIndex = 0; colorIndex < 16; colorIndex++) { + // colors 8-15 are bold + if (colorIndex === 8) { + this._ctx.font = `bold ${this._ctx.font}`; + } + const y = (colorIndex + 1) * scaledCharHeight; + // Clear rectangle as some fonts seem to draw over the bottom boundary + this._ctx.clearRect(0, y, this._canvas.width, scaledCharHeight); + // Draw ascii characters + for (let i = 0; i < 256; i++) { + this._ctx.fillStyle = COLORS[colorIndex]; + this._ctx.fillText(String.fromCharCode(i), i * scaledCharWidth, y); + } + } + this._ctx.restore(); + + const charAtlasImageData = this._ctx.getImageData(0, 0, this._canvas.width, this._canvas.height); + const promise = window.createImageBitmap(charAtlasImageData); + // Clear the rect while the promise is in progress + this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); + return promise; } } diff --git a/src/renderer/ForegroundRenderLayer.ts b/src/renderer/ForegroundRenderLayer.ts index a073f69d..38700712 100644 --- a/src/renderer/ForegroundRenderLayer.ts +++ b/src/renderer/ForegroundRenderLayer.ts @@ -8,26 +8,16 @@ import { CharData } from '../Types'; import { BaseRenderLayer } from './BaseRenderLayer'; export class ForegroundRenderLayer extends BaseRenderLayer implements IDataRenderLayer { - private _charAtlas: ImageBitmap; private _state: GridCache; - private _charAtlasGenerator: CharAtlasGenerator; - constructor(container: HTMLElement, zIndex: number) { super(container, 'fg', zIndex); - this._charAtlasGenerator = new CharAtlasGenerator(); this._state = new GridCache(); } public resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void { super.resize(terminal, canvasWidth, canvasHeight, charSizeChanged); this._state.resize(terminal.cols, terminal.rows); - if (charSizeChanged) { - this._charAtlas = null; - this._charAtlasGenerator.generate(terminal.charMeasure.width, terminal.charMeasure.height).then(bitmap => { - this._charAtlas = bitmap; - }); - } } public render(terminal: ITerminal, startRow: number, endRow: number): void { @@ -36,7 +26,7 @@ export class ForegroundRenderLayer extends BaseRenderLayer implements IDataRende // TODO: Ensure that the render is eventually performed // Don't bother render until the atlas bitmap is ready - if (!this._charAtlas) { + if (!BaseRenderLayer._charAtlas) { return; } @@ -98,7 +88,7 @@ export class ForegroundRenderLayer extends BaseRenderLayer implements IDataRende if (code < 256 && (colorIndex > 0 || fg > 255)) { // ImageBitmap's draw about twice as fast as from a canvas - this._ctx.drawImage(this._charAtlas, code * scaledCharWidth, colorIndex * scaledCharHeight, scaledCharWidth, scaledCharHeight, x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); + this._ctx.drawImage(BaseRenderLayer._charAtlas, code * scaledCharWidth, colorIndex * scaledCharHeight, scaledCharWidth, scaledCharHeight, x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); } else { // TODO: Evaluate how long it takes to convert from a number const width: number = charData[CHAR_DATA_WIDTH_INDEX]; @@ -128,55 +118,3 @@ export class ForegroundRenderLayer extends BaseRenderLayer implements IDataRende this._ctx.restore(); } } - -class CharAtlasGenerator { - private _canvas: HTMLCanvasElement; - private _ctx: CanvasRenderingContext2D; - - constructor() { - this._canvas = document.createElement('canvas'); - this._ctx = this._canvas.getContext('2d'); - this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); - } - - public generate(charWidth: number, charHeight: number): Promise { - const scaledCharWidth = Math.ceil(charWidth) * window.devicePixelRatio; - const scaledCharHeight = Math.ceil(charHeight) * window.devicePixelRatio; - - this._canvas.width = 255 * scaledCharWidth; - this._canvas.height = (/*default*/1 + /*0-15*/16) * scaledCharHeight; - - this._ctx.save(); - this._ctx.fillStyle = '#ffffff'; - this._ctx.font = `${16 * window.devicePixelRatio}px courier`; - this._ctx.textBaseline = 'top'; - - // Default color - for (let i = 0; i < 256; i++) { - this._ctx.fillText(String.fromCharCode(i), i * scaledCharWidth, 0); - } - - // Colors 0-15 - for (let colorIndex = 0; colorIndex < 16; colorIndex++) { - // colors 8-15 are bold - if (colorIndex === 8) { - this._ctx.font = `bold ${this._ctx.font}`; - } - const y = (colorIndex + 1) * scaledCharHeight; - // Clear rectangle as some fonts seem to draw over the bottom boundary - this._ctx.clearRect(0, y, this._canvas.width, scaledCharHeight); - // Draw ascii characters - for (let i = 0; i < 256; i++) { - this._ctx.fillStyle = COLORS[colorIndex]; - this._ctx.fillText(String.fromCharCode(i), i * scaledCharWidth, y); - } - } - this._ctx.restore(); - - const charAtlasImageData = this._ctx.getImageData(0, 0, this._canvas.width, this._canvas.height); - const promise = window.createImageBitmap(charAtlasImageData); - // Clear the rect while the promise is in progress - this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); - return promise; - } -} From 1998675dc07de8f347b337545906c15c11c3757b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 31 Aug 2017 17:28:37 -0700 Subject: [PATCH 130/378] Move char drawing to base and use in cursor layer --- src/renderer/BaseRenderLayer.ts | 34 ++++++++++++++++++++++++++ src/renderer/CursorRenderLayer.ts | 8 +++--- src/renderer/ForegroundRenderLayer.ts | 35 ++------------------------- 3 files changed, 40 insertions(+), 37 deletions(-) diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 66b07fac..92e95cd2 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -44,6 +44,40 @@ export abstract class BaseRenderLayer implements IRenderLayer { } } } + + protected drawChar(char: string, code: number, fg: number, x: number, y: number, scaledCharWidth: number, scaledCharHeight: number): void { + let colorIndex = 0; + if (fg < 256) { + colorIndex = fg + 1; + } + if (code < 256 && (colorIndex > 0 || fg > 255)) { + // ImageBitmap's draw about twice as fast as from a canvas + this._ctx.drawImage(BaseRenderLayer._charAtlas, + code * scaledCharWidth, colorIndex * scaledCharHeight, scaledCharWidth, scaledCharHeight, + x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); + } else { + this._drawUncachedChar(char, fg, x, y, scaledCharWidth, scaledCharHeight); + } + // This draws the atlas (for debugging purposes) + // this._ctx.drawImage(BaseRenderLayer._charAtlas, 0, 0); + } + + private _drawUncachedChar(char: string, fg: number, x: number, y: number, scaledCharWidth: number, scaledCharHeight: number): void { + this._ctx.save(); + this._ctx.font = `${16 * window.devicePixelRatio}px courier`; + this._ctx.textBaseline = 'top'; + + // 256 color support + if (fg < 256) { + this._ctx.fillStyle = COLORS[fg]; + } else { + this._ctx.fillStyle = '#ffffff'; + } + + // TODO: Do we care about width for rendering wide chars? + this._ctx.fillText(char, x * scaledCharWidth, y * scaledCharHeight); + this._ctx.restore(); + } } class CharAtlasGenerator { diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index 11496610..da32aaa8 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -1,6 +1,6 @@ import { IDataRenderLayer } from './Interfaces'; import { IBuffer, ICharMeasure, ITerminal } from '../Interfaces'; -import { CHAR_DATA_ATTR_INDEX } from '../Buffer'; +import { CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX } from '../Buffer'; import { COLORS } from './Color'; import { GridCache } from './GridCache'; import { FLAGS } from './Types'; @@ -43,14 +43,14 @@ export class CursorRenderLayer extends BaseRenderLayer implements IDataRenderLay this._clearCursor(scaledCharWidth, scaledCharHeight); } - // TODO: Draw text in COLORS[0], using the char atlas if possible - // const charData = terminal.buffer.lines.get(viewportRelativeCursorY)[terminal.buffer.x]; - this._ctx.save(); this._ctx.fillStyle = COLORS[7]; this._ctx.fillRect(terminal.buffer.x * scaledCharWidth, viewportRelativeCursorY * scaledCharHeight, scaledCharWidth, scaledCharHeight); this._ctx.restore(); + const charData = terminal.buffer.lines.get(viewportRelativeCursorY)[terminal.buffer.x]; + this.drawChar(charData[CHAR_DATA_CHAR_INDEX], charData[CHAR_DATA_CODE_INDEX], 0, terminal.buffer.x, viewportRelativeCursorY, scaledCharWidth, scaledCharHeight); + this._state = [terminal.buffer.x, viewportRelativeCursorY]; } diff --git a/src/renderer/ForegroundRenderLayer.ts b/src/renderer/ForegroundRenderLayer.ts index 38700712..c3cb833f 100644 --- a/src/renderer/ForegroundRenderLayer.ts +++ b/src/renderer/ForegroundRenderLayer.ts @@ -26,6 +26,7 @@ export class ForegroundRenderLayer extends BaseRenderLayer implements IDataRende // TODO: Ensure that the render is eventually performed // Don't bother render until the atlas bitmap is ready + // TODO: Move this to BaseRenderLayer? if (!BaseRenderLayer._charAtlas) { return; } @@ -81,40 +82,8 @@ export class ForegroundRenderLayer extends BaseRenderLayer implements IDataRende } } - let colorIndex = 0; - if (fg < 16) { - colorIndex = fg + 1; - } - - if (code < 256 && (colorIndex > 0 || fg > 255)) { - // ImageBitmap's draw about twice as fast as from a canvas - this._ctx.drawImage(BaseRenderLayer._charAtlas, code * scaledCharWidth, colorIndex * scaledCharHeight, scaledCharWidth, scaledCharHeight, x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); - } else { - // TODO: Evaluate how long it takes to convert from a number - const width: number = charData[CHAR_DATA_WIDTH_INDEX]; - this._drawUncachedChar(char, width, fg, x, y, scaledCharWidth, scaledCharHeight); - } + this.drawChar(char, code, fg, x, y, scaledCharWidth, scaledCharHeight); } } - - // This draws the atlas (for debugging purposes) - // this._ctx.drawImage(this._charAtlas, 0, 0); - } - - private _drawUncachedChar(char: string, width: number, fg: number, x: number, y: number, scaledCharWidth: number, scaledCharHeight: number): void { - this._ctx.save(); - this._ctx.font = `${16 * window.devicePixelRatio}px courier`; - this._ctx.textBaseline = 'top'; - - // 256 color support - if (fg < 256) { - this._ctx.fillStyle = COLORS[fg]; - } else { - this._ctx.fillStyle = '#ffffff'; - } - - // TODO: Do we care about width for rendering wide chars? - this._ctx.fillText(char, x * scaledCharWidth, y * scaledCharHeight); - this._ctx.restore(); } } From c88824155f4f0af55bba07b172971f51e723147a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 31 Aug 2017 18:09:07 -0700 Subject: [PATCH 131/378] Simplify color generation code --- src/renderer/BaseRenderLayer.ts | 3 +- src/renderer/Color.ts | 64 +++++++++++++++++++------------ src/renderer/CursorRenderLayer.ts | 7 ++-- 3 files changed, 46 insertions(+), 28 deletions(-) diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 92e95cd2..5e53c411 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -6,6 +6,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { protected _canvas: HTMLCanvasElement; protected _ctx: CanvasRenderingContext2D; + // TODO: This will apply to all terminals, should it be per-terminal? protected static _charAtlas: ImageBitmap; private static _charAtlasCharWidth: number; private static _charAtlasCharHeight: number; @@ -29,7 +30,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._canvas.height = canvasHeight * window.devicePixelRatio; this._canvas.style.width = `${canvasWidth}px`; this._canvas.style.height = `${canvasHeight}px`; - // Only update the char atlas if the char size changed + if (charSizeChanged) { // Only update the char atlas if an update for the right dimensions is not // already in progress diff --git a/src/renderer/Color.ts b/src/renderer/Color.ts index eb3954ea..446d440b 100644 --- a/src/renderer/Color.ts +++ b/src/renderer/Color.ts @@ -1,6 +1,25 @@ // TODO: Ideally colors would be exposed through some theme manager since colors // are moving to JS. +export enum COLOR_CODES { + BLACK = 0, + RED = 1, + GREEN = 2, + YELLOW = 3, + BLUE = 4, + MAGENTA = 5, + CYAN = 6, + WHITE = 7, + BRIGHT_BLACK = 8, + BRIGHT_RED = 9, + BRIGHT_GREEN = 10, + BRIGHT_YELLOW = 11, + BRIGHT_BLUE = 12, + BRIGHT_MAGENTA = 13, + BRIGHT_CYAN = 14, + BRIGHT_WHITE = 15 +} + const TANGO_COLORS = [ // dark: '#2e3436', @@ -22,33 +41,30 @@ const TANGO_COLORS = [ '#eeeeec' ]; -export const COLORS: string[] = (function(): string[] { - let colors = TANGO_COLORS.slice(); - let r = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]; - let i; +export const COLORS: string[] = generate256Colors(TANGO_COLORS); - // 16-231 - i = 0; - for (; i < 216; i++) { - out(r[(i / 36) % 6 | 0], r[(i / 6) % 6 | 0], r[i % 6]); +function generate256Colors(first16Colors: string[]): string[] { + let colors = first16Colors.slice(); + + // Generate colors (16-231) + let v = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]; + for (let i = 0; i < 216; i++) { + const r = toPaddedHex(v[(i / 36) % 6 | 0]); + const g = toPaddedHex(v[(i / 6) % 6 | 0]); + const b = toPaddedHex(v[i % 6]); + colors.push(`#${r}${g}${b}`); } - // 232-255 (grey) - i = 0; - let c: number; - for (; i < 24; i++) { - c = 8 + i * 10; - out(c, c, c); - } - - function out(r: number, g: number, b: number): void { - colors.push('#' + hex(r) + hex(g) + hex(b)); - } - - function hex(c: number): string { - let s = c.toString(16); - return s.length < 2 ? '0' + s : s; + // Generate greys (232-255) + for (let i = 0; i < 24; i++) { + const c = toPaddedHex(8 + i * 10); + colors.push(`#${c}${c}${c}`); } return colors; -})(); +} + +function toPaddedHex(c: number): string { + let s = c.toString(16); + return s.length < 2 ? '0' + s : s; +} diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index da32aaa8..75dd4d89 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -1,7 +1,7 @@ import { IDataRenderLayer } from './Interfaces'; import { IBuffer, ICharMeasure, ITerminal } from '../Interfaces'; import { CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX } from '../Buffer'; -import { COLORS } from './Color'; +import { COLORS, COLOR_CODES } from './Color'; import { GridCache } from './GridCache'; import { FLAGS } from './Types'; import { BaseRenderLayer } from './BaseRenderLayer'; @@ -17,6 +17,7 @@ export class CursorRenderLayer extends BaseRenderLayer implements IDataRenderLay public render(terminal: ITerminal, startRow: number, endRow: number): void { // TODO: Track blur/focus somehow, support unfocused cursor + // TODO: scaledCharWidth should probably be on Base as a per-terminal thing const scaledCharWidth = Math.ceil(terminal.charMeasure.width) * window.devicePixelRatio; const scaledCharHeight = Math.ceil(terminal.charMeasure.height) * window.devicePixelRatio; @@ -44,12 +45,12 @@ export class CursorRenderLayer extends BaseRenderLayer implements IDataRenderLay } this._ctx.save(); - this._ctx.fillStyle = COLORS[7]; + this._ctx.fillStyle = COLORS[COLOR_CODES.WHITE]; this._ctx.fillRect(terminal.buffer.x * scaledCharWidth, viewportRelativeCursorY * scaledCharHeight, scaledCharWidth, scaledCharHeight); this._ctx.restore(); const charData = terminal.buffer.lines.get(viewportRelativeCursorY)[terminal.buffer.x]; - this.drawChar(charData[CHAR_DATA_CHAR_INDEX], charData[CHAR_DATA_CODE_INDEX], 0, terminal.buffer.x, viewportRelativeCursorY, scaledCharWidth, scaledCharHeight); + this.drawChar(charData[CHAR_DATA_CHAR_INDEX], charData[CHAR_DATA_CODE_INDEX], COLOR_CODES.BLACK, terminal.buffer.x, viewportRelativeCursorY, scaledCharWidth, scaledCharHeight); this._state = [terminal.buffer.x, viewportRelativeCursorY]; } From 15c73ef94f7cacdff3e91cf6ee58bd1f5ed14ff6 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 31 Aug 2017 22:42:39 -0700 Subject: [PATCH 132/378] Add fontSize and fontFamily options --- src/Interfaces.ts | 4 +- src/Terminal.ts | 36 +++++++------- src/renderer/BackgroundRenderLayer.ts | 5 ++ src/renderer/BaseRenderLayer.ts | 19 +++++--- src/renderer/CursorRenderLayer.ts | 8 ++- src/renderer/ForegroundRenderLayer.ts | 11 +++-- src/renderer/GridCache.ts | 8 +++ src/renderer/Interfaces.ts | 8 +++ src/renderer/Renderer.ts | 70 ++++----------------------- src/renderer/SelectionRenderLayer.ts | 10 ++++ src/utils/CharMeasure.test.ts | 12 ++--- src/utils/CharMeasure.ts | 17 ++++--- src/xterm.css | 5 -- typings/xterm.d.ts | 8 +-- 14 files changed, 107 insertions(+), 114 deletions(-) diff --git a/src/Interfaces.ts b/src/Interfaces.ts index fb66f4d7..1b1625bc 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -122,6 +122,8 @@ export interface ITerminalOptions { cursorStyle?: string; debug?: boolean; disableStdin?: boolean; + fontSize?: number; + fontFamily?: string; geometry?: [number, number]; handler?: (data: string) => void; rows?: number; @@ -186,7 +188,7 @@ export interface ICompositionHelper { export interface ICharMeasure { width: number; height: number; - measure(): void; + measure(options: ITerminalOptions): void; } export interface ILinkifier { diff --git a/src/Terminal.ts b/src/Terminal.ts index f88ebb7f..c9c6466c 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -151,6 +151,8 @@ const DEFAULT_OPTIONS: ITerminalOptions = { cursorStyle: 'block', bellSound: BellSound, bellStyle: 'none', + fontFamily: 'courier-new, courier, monospace', + fontSize: 15, scrollback: 1000, screenKeys: false, debug: false, @@ -481,6 +483,12 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.element.classList.toggle(`xterm-cursor-style-underline`, value === 'underline'); this.element.classList.toggle(`xterm-cursor-style-bar`, value === 'bar'); break; + case 'fontFamily': + case 'fontSize': + // When the font changes the size of the cells may change which requires a renderer clear + this.renderer.clear(); + this.charMeasure.measure(this.options); + break; case 'scrollback': this.buffers.resize(this.cols, this.rows); this.viewport.syncScrollArea(); @@ -742,15 +750,15 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.parent.appendChild(this.element); this.charMeasure = new CharMeasure(document, this.helperContainer); - this.charMeasure.on('charsizechanged', () => { - this.updateCharSizeStyles(); - }); - this.charMeasure.measure(); this.viewport = new Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasure); this.renderer = new Renderer(this); this.on('resize', () => this.renderer.onResize(this.cols, this.rows)); - this.charMeasure.on('charsizechanged', () => this.renderer.onCharSizeChanged(this.charMeasure.width, this.charMeasure.height)); + this.charMeasure.on('charsizechanged', () => { + this.renderer.onCharSizeChanged(this.charMeasure.width, this.charMeasure.height); + // Force a refresh for the char size change + this.renderer.queueRefresh(0, this.rows - 1); + }); this.selectionManager = new SelectionManager(this, this.buffer, this.rowContainer, this.charMeasure); this.element.addEventListener('mousedown', (e: MouseEvent) => this.selectionManager.onMouseDown(e)); @@ -766,6 +774,9 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.on('scroll', () => this.selectionManager.refresh()); this.viewportElement.addEventListener('scroll', () => this.selectionManager.refresh()); + // Measure the character size + this.charMeasure.measure(this.options); + // Setup loop that draws to screen this.refresh(0, this.rows - 1); @@ -796,17 +807,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT } } - /** - * Updates the helper CSS class with any changes necessary after the terminal's - * character width has been changed. - */ - public updateCharSizeStyles(): void { - this.charSizeStyleElement.textContent = - `.xterm-wide-char{width:${this.charMeasure.width * 2}px;}` + - `.xterm-normal-char{width:${this.charMeasure.width}px;}` + - `.xterm-rows > div{height:${this.charMeasure.height}px;}`; - } - /** * XTerm mouse events * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking @@ -1918,7 +1918,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT if (x === this.cols && y === this.rows) { // Check if we still need to measure the char size (fixes #785). if (!this.charMeasure.width || !this.charMeasure.height) { - this.charMeasure.measure(); + this.charMeasure.measure(this.options); } return; } @@ -1942,7 +1942,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.rows = y; this.buffers.setupTabStops(this.cols); - this.charMeasure.measure(); + this.charMeasure.measure(this.options); this.refresh(0, this.rows - 1); diff --git a/src/renderer/BackgroundRenderLayer.ts b/src/renderer/BackgroundRenderLayer.ts index a9722eab..37b9c69e 100644 --- a/src/renderer/BackgroundRenderLayer.ts +++ b/src/renderer/BackgroundRenderLayer.ts @@ -19,6 +19,11 @@ export class BackgroundRenderLayer extends BaseRenderLayer implements IDataRende this._state.resize(terminal.cols, terminal.rows); } + public clear(terminal: ITerminal): void { + this._state.clear(); + this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); + } + public render(terminal: ITerminal, startRow: number, endRow: number): void { const scaledCharWidth = Math.ceil(terminal.charMeasure.width) * window.devicePixelRatio; const scaledCharHeight = Math.ceil(terminal.charMeasure.height) * window.devicePixelRatio; diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 5e53c411..419b814c 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -39,14 +39,16 @@ export abstract class BaseRenderLayer implements IRenderLayer { BaseRenderLayer._charAtlas = null; BaseRenderLayer._charAtlasCharWidth = terminal.charMeasure.width; BaseRenderLayer._charAtlasCharHeight = terminal.charMeasure.height; - BaseRenderLayer._charAtlasGenerator.generate(terminal.charMeasure.width, terminal.charMeasure.height).then(bitmap => { + BaseRenderLayer._charAtlasGenerator.generate(terminal, terminal.charMeasure.width, terminal.charMeasure.height).then(bitmap => { BaseRenderLayer._charAtlas = bitmap; }); } } } - protected drawChar(char: string, code: number, fg: number, x: number, y: number, scaledCharWidth: number, scaledCharHeight: number): void { + public abstract clear(terminal: ITerminal): void; + + protected drawChar(terminal: ITerminal, char: string, code: number, fg: number, x: number, y: number, scaledCharWidth: number, scaledCharHeight: number): void { let colorIndex = 0; if (fg < 256) { colorIndex = fg + 1; @@ -57,15 +59,15 @@ export abstract class BaseRenderLayer implements IRenderLayer { code * scaledCharWidth, colorIndex * scaledCharHeight, scaledCharWidth, scaledCharHeight, x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); } else { - this._drawUncachedChar(char, fg, x, y, scaledCharWidth, scaledCharHeight); + this._drawUncachedChar(terminal, char, fg, x, y, scaledCharWidth, scaledCharHeight); } // This draws the atlas (for debugging purposes) // this._ctx.drawImage(BaseRenderLayer._charAtlas, 0, 0); } - private _drawUncachedChar(char: string, fg: number, x: number, y: number, scaledCharWidth: number, scaledCharHeight: number): void { + private _drawUncachedChar(terminal: ITerminal, char: string, fg: number, x: number, y: number, scaledCharWidth: number, scaledCharHeight: number): void { this._ctx.save(); - this._ctx.font = `${16 * window.devicePixelRatio}px courier`; + this._ctx.font = `${terminal.options.fontSize * window.devicePixelRatio}px ${terminal.options.fontFamily}`; this._ctx.textBaseline = 'top'; // 256 color support @@ -91,16 +93,17 @@ class CharAtlasGenerator { this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); } - public generate(charWidth: number, charHeight: number): Promise { + public generate(terminal: ITerminal, charWidth: number, charHeight: number): Promise { const scaledCharWidth = Math.ceil(charWidth) * window.devicePixelRatio; const scaledCharHeight = Math.ceil(charHeight) * window.devicePixelRatio; - +console.log('generate'); this._canvas.width = 255 * scaledCharWidth; this._canvas.height = (/*default*/1 + /*0-15*/16) * scaledCharHeight; this._ctx.save(); this._ctx.fillStyle = '#ffffff'; - this._ctx.font = `${16 * window.devicePixelRatio}px courier`; + this._ctx.font = `${terminal.options.fontSize * window.devicePixelRatio}px ${terminal.options.fontFamily}`; + console.log(this._ctx.font, scaledCharWidth, scaledCharHeight); this._ctx.textBaseline = 'top'; // Default color diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index 75dd4d89..76642229 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -14,6 +14,12 @@ export class CursorRenderLayer extends BaseRenderLayer implements IDataRenderLay this._state = null; } + public clear(terminal: ITerminal): void { + const scaledCharWidth = Math.ceil(terminal.charMeasure.width) * window.devicePixelRatio; + const scaledCharHeight = Math.ceil(terminal.charMeasure.height) * window.devicePixelRatio; + this._clearCursor(scaledCharWidth, scaledCharHeight); + } + public render(terminal: ITerminal, startRow: number, endRow: number): void { // TODO: Track blur/focus somehow, support unfocused cursor @@ -50,7 +56,7 @@ export class CursorRenderLayer extends BaseRenderLayer implements IDataRenderLay this._ctx.restore(); const charData = terminal.buffer.lines.get(viewportRelativeCursorY)[terminal.buffer.x]; - this.drawChar(charData[CHAR_DATA_CHAR_INDEX], charData[CHAR_DATA_CODE_INDEX], COLOR_CODES.BLACK, terminal.buffer.x, viewportRelativeCursorY, scaledCharWidth, scaledCharHeight); + this.drawChar(terminal, charData[CHAR_DATA_CHAR_INDEX], charData[CHAR_DATA_CODE_INDEX], COLOR_CODES.BLACK, terminal.buffer.x, viewportRelativeCursorY, scaledCharWidth, scaledCharHeight); this._state = [terminal.buffer.x, viewportRelativeCursorY]; } diff --git a/src/renderer/ForegroundRenderLayer.ts b/src/renderer/ForegroundRenderLayer.ts index c3cb833f..3cd7ae1e 100644 --- a/src/renderer/ForegroundRenderLayer.ts +++ b/src/renderer/ForegroundRenderLayer.ts @@ -20,6 +20,11 @@ export class ForegroundRenderLayer extends BaseRenderLayer implements IDataRende this._state.resize(terminal.cols, terminal.rows); } + public clear(terminal: ITerminal): void { + this._state.clear(); + this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); + } + public render(terminal: ITerminal, startRow: number, endRow: number): void { const scaledCharWidth = Math.ceil(terminal.charMeasure.width) * window.devicePixelRatio; const scaledCharHeight = Math.ceil(terminal.charMeasure.height) * window.devicePixelRatio; @@ -31,10 +36,6 @@ export class ForegroundRenderLayer extends BaseRenderLayer implements IDataRende return; } - this._ctx.fillStyle = '#ffffff'; - this._ctx.textBaseline = 'top'; - this._ctx.font = `${16 * window.devicePixelRatio}px courier`; - for (let y = startRow; y <= endRow; y++) { const row = y + terminal.buffer.ydisp; const line = terminal.buffer.lines.get(row); @@ -82,7 +83,7 @@ export class ForegroundRenderLayer extends BaseRenderLayer implements IDataRende } } - this.drawChar(char, code, fg, x, y, scaledCharWidth, scaledCharHeight); + this.drawChar(terminal, char, code, fg, x, y, scaledCharWidth, scaledCharHeight); } } } diff --git a/src/renderer/GridCache.ts b/src/renderer/GridCache.ts index eba3dad6..a4657fcb 100644 --- a/src/renderer/GridCache.ts +++ b/src/renderer/GridCache.ts @@ -17,4 +17,12 @@ export class GridCache { } this.cache.length = width; } + + public clear() { + for (let x = 0; x < this.cache.length; x++) { + for (let y = 0; y < this.cache[x].length; y++) { + this.cache[x][y] = null; + } + } + } } diff --git a/src/renderer/Interfaces.ts b/src/renderer/Interfaces.ts index 4d6d2d3b..8eb3acdd 100644 --- a/src/renderer/Interfaces.ts +++ b/src/renderer/Interfaces.ts @@ -1,7 +1,15 @@ import { ITerminal } from '../Interfaces'; export interface IRenderLayer { + /** + * Resize the render layer. + */ resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void; + + /** + * Clear the state of the render layer. + */ + clear(terminal: ITerminal): void; } export interface IDataRenderLayer extends IRenderLayer { diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 522f7af2..26b472cf 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -40,6 +40,7 @@ export class Renderer { } public onCharSizeChanged(charWidth: number, charHeight: number): void { + console.log('Renderer.onCharSizeChanged', charWidth, charHeight); const width = Math.ceil(charWidth) * this._terminal.cols; const height = Math.ceil(charHeight) * this._terminal.rows; for (let i = 0; i < this._dataRenderLayers.length; i++) { @@ -56,6 +57,15 @@ export class Renderer { } } + public clear(): void { + for (let i = 0; i < this._dataRenderLayers.length; i++) { + this._dataRenderLayers[i].clear(this._terminal); + } + for (let i = 0; i < this._selectionRenderLayers.length; i++) { + this._selectionRenderLayers[i].clear(this._terminal); + } + } + /** * Queues a refresh between two rows (inclusive), to be done on next animation * frame. @@ -102,64 +112,4 @@ export class Renderer { } this._terminal.emit('refresh', {start, end}); } - - /** - * Refreshes the selection in the DOM. - * @param start The selection start. - * @param end The selection end. - */ - public refreshSelection(start: [number, number], end: [number, number]): void { - // Remove all selections - while (this._terminal.selectionContainer.children.length) { - this._terminal.selectionContainer.removeChild(this._terminal.selectionContainer.children[0]); - } - - // Selection does not exist - if (!start || !end) { - return; - } - - // Translate from buffer position to viewport position - const viewportStartRow = start[1] - this._terminal.buffer.ydisp; - const viewportEndRow = end[1] - this._terminal.buffer.ydisp; - const viewportCappedStartRow = Math.max(viewportStartRow, 0); - const viewportCappedEndRow = Math.min(viewportEndRow, this._terminal.rows - 1); - - // No need to draw the selection - if (viewportCappedStartRow >= this._terminal.rows || viewportCappedEndRow < 0) { - return; - } - - // Create the selections - const documentFragment = document.createDocumentFragment(); - // Draw first row - const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0; - const endCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : this._terminal.cols; - documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow, startCol, endCol)); - // Draw middle rows - const middleRowsCount = viewportCappedEndRow - viewportCappedStartRow - 1; - documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow + 1, 0, this._terminal.cols, middleRowsCount)); - // Draw final row - if (viewportCappedStartRow !== viewportCappedEndRow) { - // Only draw viewportEndRow if it's not the same as viewporttartRow - const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : this._terminal.cols; - documentFragment.appendChild(this._createSelectionElement(viewportCappedEndRow, 0, endCol)); - } - this._terminal.selectionContainer.appendChild(documentFragment); - } - - /** - * Creates a selection element at the specified position. - * @param row The row of the selection. - * @param colStart The start column. - * @param colEnd The end columns. - */ - private _createSelectionElement(row: number, colStart: number, colEnd: number, rowCount: number = 1): HTMLElement { - const element = document.createElement('div'); - element.style.height = `${rowCount * this._terminal.charMeasure.height}px`; - element.style.top = `${row * this._terminal.charMeasure.height}px`; - element.style.left = `${colStart * this._terminal.charMeasure.width}px`; - element.style.width = `${this._terminal.charMeasure.width * (colEnd - colStart)}px`; - return element; - } } diff --git a/src/renderer/SelectionRenderLayer.ts b/src/renderer/SelectionRenderLayer.ts index 048be8e4..a2cfb1d6 100644 --- a/src/renderer/SelectionRenderLayer.ts +++ b/src/renderer/SelectionRenderLayer.ts @@ -16,6 +16,16 @@ export class SelectionRenderLayer extends BaseRenderLayer implements ISelectionR }; } + public clear(terminal: ITerminal): void { + if (this._state.start && this._state.end) { + this._state = { + start: null, + end: null + }; + this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); + } + } + public render(terminal: ITerminal, start: [number, number], end: [number, number]): void { const scaledCharWidth = Math.ceil(terminal.charMeasure.width) * window.devicePixelRatio; const scaledCharHeight = Math.ceil(terminal.charMeasure.height) * window.devicePixelRatio; diff --git a/src/utils/CharMeasure.test.ts b/src/utils/CharMeasure.test.ts index 68e1b6a7..02a8dae9 100644 --- a/src/utils/CharMeasure.test.ts +++ b/src/utils/CharMeasure.test.ts @@ -25,13 +25,13 @@ describe('CharMeasure', () => { describe('measure', () => { it('should set _measureElement on first call', () => { - charMeasure.measure(); + charMeasure.measure({}); assert.isDefined((charMeasure)._measureElement, 'CharMeasure.measure should have created _measureElement'); }); it('should be performed async on first call', done => { assert.equal(charMeasure.width, null); - charMeasure.measure(); + charMeasure.measure({}); // Mock getBoundingClientRect since jsdom doesn't have a layout engine (charMeasure)._measureElement.getBoundingClientRect = () => { return { width: 1, height: 1 }; @@ -44,7 +44,7 @@ describe('CharMeasure', () => { }); it('should be performed sync on successive calls', done => { - charMeasure.measure(); + charMeasure.measure({}); // Mock getBoundingClientRect since jsdom doesn't have a layout engine (charMeasure)._measureElement.getBoundingClientRect = () => { return { width: 1, height: 1 }; @@ -55,19 +55,19 @@ describe('CharMeasure', () => { (charMeasure)._measureElement.getBoundingClientRect = () => { return { width: 2, height: 2 }; }; - charMeasure.measure(); + charMeasure.measure({}); assert.equal(charMeasure.width, firstWidth * 2); done(); }, 0); }); it('should NOT do a measure when the parent is hidden', done => { - charMeasure.measure(); + charMeasure.measure({}); setTimeout(() => { const firstWidth = charMeasure.width; container.style.display = 'none'; container.style.fontSize = '2em'; - charMeasure.measure(); + charMeasure.measure({}); assert.equal(charMeasure.width, firstWidth); done(); }, 0); diff --git a/src/utils/CharMeasure.ts b/src/utils/CharMeasure.ts index 45b06f59..ff777811 100644 --- a/src/utils/CharMeasure.ts +++ b/src/utils/CharMeasure.ts @@ -4,11 +4,14 @@ */ import { EventEmitter } from '../EventEmitter.js'; +import { ICharMeasure, ITerminal, ITerminalOptions } from '../Interfaces'; /** - * Utility class that measures the size of a character. + * Utility class that measures the size of a character. Measurements are done in + * the DOM rather than with a canvas context because support for extracting the + * height of characters is patchy across browsers. */ -export class CharMeasure extends EventEmitter { +export class CharMeasure extends EventEmitter implements ICharMeasure { private _document: Document; private _parentElement: HTMLElement; private _measureElement: HTMLElement; @@ -29,7 +32,7 @@ export class CharMeasure extends EventEmitter { return this._height; } - public measure(): void { + public measure(options: ITerminalOptions): void { if (!this._measureElement) { this._measureElement = this._document.createElement('span'); this._measureElement.style.position = 'absolute'; @@ -40,13 +43,15 @@ export class CharMeasure extends EventEmitter { this._parentElement.appendChild(this._measureElement); // Perform _doMeasure async if the element was just attached as sometimes // getBoundingClientRect does not return accurate values without this. - setTimeout(() => this._doMeasure(), 0); + setTimeout(() => this._doMeasure(options), 0); } else { - this._doMeasure(); + this._doMeasure(options); } } - private _doMeasure(): void { + private _doMeasure(options: ITerminalOptions): void { + this._measureElement.style.fontFamily = options.fontFamily; + this._measureElement.style.fontSize = `${options.fontSize}px`; const geometry = this._measureElement.getBoundingClientRect(); // The element is likely currently display:none, we should retain the // previous value. diff --git a/src/xterm.css b/src/xterm.css index f281e527..6a7504f3 100644 --- a/src/xterm.css +++ b/src/xterm.css @@ -164,11 +164,6 @@ top: 0; } -/* .terminal .xterm-bg-layer { z-index: 0; } -.terminal .xterm-selection-layer { z-index: 1; } -.terminal .xterm-fg-layer { z-index: 2; } -.terminal .xterm-cursor-layer { z-index: 3; } */ - .terminal .xterm-rows { position: absolute; left: 0; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index ccc83ed9..dce15bca 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -313,7 +313,7 @@ declare module 'xterm' { * Retrieves an option's value from the terminal. * @param key The option key. */ - getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'termName'): string; + getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'termName'): string; /** * Retrieves an option's value from the terminal. * @param key The option key. @@ -328,7 +328,7 @@ declare module 'xterm' { * Retrieves an option's value from the terminal. * @param key The option key. */ - getOption(key: 'cols' | 'rows' | 'tabStopWidth' | 'scrollback'): number; + getOption(key: 'cols' | 'fontSize' | 'rows' | 'tabStopWidth' | 'scrollback'): number; /** * Retrieves an option's value from the terminal. * @param key The option key. @@ -350,7 +350,7 @@ declare module 'xterm' { * @param key The option key. * @param value The option value. */ - setOption(key: 'termName' | 'bellSound', value: string): void; + setOption(key: 'fontFamily' | 'termName' | 'bellSound', value: string): void; /** * Sets an option on the terminal. * @param key The option key. @@ -380,7 +380,7 @@ declare module 'xterm' { * @param key The option key. * @param value The option value. */ - setOption(key: 'cols' | 'rows' | 'tabStopWidth' | 'scrollback', value: number): void; + setOption(key: 'cols' | 'fontSize' | 'rows' | 'tabStopWidth' | 'scrollback', value: number): void; /** * Sets an option on the terminal. * @param key The option key. From 6588e9e01b53b099f71cb0f13f69f82b32ce37ec Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 31 Aug 2017 22:43:48 -0700 Subject: [PATCH 133/378] Fix cursor which cursor character is rendered --- src/renderer/CursorRenderLayer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index 76642229..c18bb130 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -55,7 +55,7 @@ export class CursorRenderLayer extends BaseRenderLayer implements IDataRenderLay this._ctx.fillRect(terminal.buffer.x * scaledCharWidth, viewportRelativeCursorY * scaledCharHeight, scaledCharWidth, scaledCharHeight); this._ctx.restore(); - const charData = terminal.buffer.lines.get(viewportRelativeCursorY)[terminal.buffer.x]; + const charData = terminal.buffer.lines.get(cursorY)[terminal.buffer.x]; this.drawChar(terminal, charData[CHAR_DATA_CHAR_INDEX], charData[CHAR_DATA_CODE_INDEX], COLOR_CODES.BLACK, terminal.buffer.x, viewportRelativeCursorY, scaledCharWidth, scaledCharHeight); this._state = [terminal.buffer.x, viewportRelativeCursorY]; From 9323833fccc85508f10f32892b338300dab7f517 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 31 Aug 2017 22:48:08 -0700 Subject: [PATCH 134/378] Move scaledCharWidth/Height into base render layer --- src/renderer/BackgroundRenderLayer.ts | 7 ++----- src/renderer/BaseRenderLayer.ts | 4 ++++ src/renderer/CursorRenderLayer.ts | 22 ++++++++-------------- src/renderer/ForegroundRenderLayer.ts | 7 ++----- src/renderer/SelectionRenderLayer.ts | 9 +++------ 5 files changed, 19 insertions(+), 30 deletions(-) diff --git a/src/renderer/BackgroundRenderLayer.ts b/src/renderer/BackgroundRenderLayer.ts index 37b9c69e..1e8169d7 100644 --- a/src/renderer/BackgroundRenderLayer.ts +++ b/src/renderer/BackgroundRenderLayer.ts @@ -25,9 +25,6 @@ export class BackgroundRenderLayer extends BaseRenderLayer implements IDataRende } public render(terminal: ITerminal, startRow: number, endRow: number): void { - const scaledCharWidth = Math.ceil(terminal.charMeasure.width) * window.devicePixelRatio; - const scaledCharHeight = Math.ceil(terminal.charMeasure.height) * window.devicePixelRatio; - for (let y = startRow; y <= endRow; y++) { let row = y + terminal.buffer.ydisp; let line = terminal.buffer.lines.get(row); @@ -51,11 +48,11 @@ export class BackgroundRenderLayer extends BaseRenderLayer implements IDataRende if (bg < 256) { this._ctx.save(); this._ctx.fillStyle = COLORS[bg]; - this._ctx.fillRect(x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); + this._ctx.fillRect(x * this.scaledCharWidth, y * this.scaledCharHeight, this.scaledCharWidth, this.scaledCharHeight); this._ctx.restore(); this._state.cache[x][y] = bg; } else { - this._ctx.clearRect(x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); + this._ctx.clearRect(x * this.scaledCharWidth, y * this.scaledCharHeight, this.scaledCharWidth, this.scaledCharHeight); this._state.cache[x][y] = null; } } diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 419b814c..7790e034 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -5,6 +5,8 @@ import { COLORS } from './Color'; export abstract class BaseRenderLayer implements IRenderLayer { protected _canvas: HTMLCanvasElement; protected _ctx: CanvasRenderingContext2D; + protected scaledCharWidth: number; + protected scaledCharHeight: number; // TODO: This will apply to all terminals, should it be per-terminal? protected static _charAtlas: ImageBitmap; @@ -26,6 +28,8 @@ export abstract class BaseRenderLayer implements IRenderLayer { } public resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void { + this.scaledCharWidth = Math.ceil(terminal.charMeasure.width) * window.devicePixelRatio; + this.scaledCharHeight = Math.ceil(terminal.charMeasure.height) * window.devicePixelRatio; this._canvas.width = canvasWidth * window.devicePixelRatio; this._canvas.height = canvasHeight * window.devicePixelRatio; this._canvas.style.width = `${canvasWidth}px`; diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index c18bb130..bb0ff928 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -15,21 +15,15 @@ export class CursorRenderLayer extends BaseRenderLayer implements IDataRenderLay } public clear(terminal: ITerminal): void { - const scaledCharWidth = Math.ceil(terminal.charMeasure.width) * window.devicePixelRatio; - const scaledCharHeight = Math.ceil(terminal.charMeasure.height) * window.devicePixelRatio; - this._clearCursor(scaledCharWidth, scaledCharHeight); + this._clearCursor(); } public render(terminal: ITerminal, startRow: number, endRow: number): void { // TODO: Track blur/focus somehow, support unfocused cursor - // TODO: scaledCharWidth should probably be on Base as a per-terminal thing - const scaledCharWidth = Math.ceil(terminal.charMeasure.width) * window.devicePixelRatio; - const scaledCharHeight = Math.ceil(terminal.charMeasure.height) * window.devicePixelRatio; - // Don't draw the cursor if it's hidden if (!terminal.cursorState || terminal.cursorHidden) { - this._clearCursor(scaledCharWidth, scaledCharHeight); + this._clearCursor(); return; } @@ -38,7 +32,7 @@ export class CursorRenderLayer extends BaseRenderLayer implements IDataRenderLay // Don't draw the cursor if it's off-screen if (viewportRelativeCursorY < 0 || viewportRelativeCursorY >= terminal.rows) { - this._clearCursor(scaledCharWidth, scaledCharHeight); + this._clearCursor(); return; } @@ -47,23 +41,23 @@ export class CursorRenderLayer extends BaseRenderLayer implements IDataRenderLay if (this._state[0] === terminal.buffer.x && this._state[1] === viewportRelativeCursorY) { return; } - this._clearCursor(scaledCharWidth, scaledCharHeight); + this._clearCursor(); } this._ctx.save(); this._ctx.fillStyle = COLORS[COLOR_CODES.WHITE]; - this._ctx.fillRect(terminal.buffer.x * scaledCharWidth, viewportRelativeCursorY * scaledCharHeight, scaledCharWidth, scaledCharHeight); + this._ctx.fillRect(terminal.buffer.x * this.scaledCharWidth, viewportRelativeCursorY * this.scaledCharHeight, this.scaledCharWidth, this.scaledCharHeight); this._ctx.restore(); const charData = terminal.buffer.lines.get(cursorY)[terminal.buffer.x]; - this.drawChar(terminal, charData[CHAR_DATA_CHAR_INDEX], charData[CHAR_DATA_CODE_INDEX], COLOR_CODES.BLACK, terminal.buffer.x, viewportRelativeCursorY, scaledCharWidth, scaledCharHeight); + this.drawChar(terminal, charData[CHAR_DATA_CHAR_INDEX], charData[CHAR_DATA_CODE_INDEX], COLOR_CODES.BLACK, terminal.buffer.x, viewportRelativeCursorY, this.scaledCharWidth, this.scaledCharHeight); this._state = [terminal.buffer.x, viewportRelativeCursorY]; } - private _clearCursor(scaledCharWidth: number, scaledCharHeight: number): void { + private _clearCursor(): void { if (this._state) { - this._ctx.clearRect(this._state[0] * scaledCharWidth, this._state[1] * scaledCharHeight, scaledCharWidth, scaledCharHeight); + this._ctx.clearRect(this._state[0] * this.scaledCharWidth, this._state[1] * this.scaledCharHeight, this.scaledCharWidth, this.scaledCharHeight); this._state = null; } } diff --git a/src/renderer/ForegroundRenderLayer.ts b/src/renderer/ForegroundRenderLayer.ts index 3cd7ae1e..68d00857 100644 --- a/src/renderer/ForegroundRenderLayer.ts +++ b/src/renderer/ForegroundRenderLayer.ts @@ -26,9 +26,6 @@ export class ForegroundRenderLayer extends BaseRenderLayer implements IDataRende } public render(terminal: ITerminal, startRow: number, endRow: number): void { - const scaledCharWidth = Math.ceil(terminal.charMeasure.width) * window.devicePixelRatio; - const scaledCharHeight = Math.ceil(terminal.charMeasure.height) * window.devicePixelRatio; - // TODO: Ensure that the render is eventually performed // Don't bother render until the atlas bitmap is ready // TODO: Move this to BaseRenderLayer? @@ -56,7 +53,7 @@ export class ForegroundRenderLayer extends BaseRenderLayer implements IDataRende this._state.cache[x][y] = charData; // Clear the old character - this._ctx.clearRect(x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); + this._ctx.clearRect(x * this.scaledCharWidth, y * this.scaledCharHeight, this.scaledCharWidth, this.scaledCharHeight); // Skip rendering if the character is invisible if (!code || code === 32 /*' '*/) { @@ -83,7 +80,7 @@ export class ForegroundRenderLayer extends BaseRenderLayer implements IDataRende } } - this.drawChar(terminal, char, code, fg, x, y, scaledCharWidth, scaledCharHeight); + this.drawChar(terminal, char, code, fg, x, y, this.scaledCharWidth, this.scaledCharHeight); } } } diff --git a/src/renderer/SelectionRenderLayer.ts b/src/renderer/SelectionRenderLayer.ts index a2cfb1d6..602bb486 100644 --- a/src/renderer/SelectionRenderLayer.ts +++ b/src/renderer/SelectionRenderLayer.ts @@ -27,9 +27,6 @@ export class SelectionRenderLayer extends BaseRenderLayer implements ISelectionR } public render(terminal: ITerminal, start: [number, number], end: [number, number]): void { - const scaledCharWidth = Math.ceil(terminal.charMeasure.width) * window.devicePixelRatio; - const scaledCharHeight = Math.ceil(terminal.charMeasure.height) * window.devicePixelRatio; - // Selection has not changed if (this._state.start === start || this._state.end === end) { return; @@ -58,17 +55,17 @@ export class SelectionRenderLayer extends BaseRenderLayer implements ISelectionR const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0; const startRowEndCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : terminal.cols; this._ctx.fillStyle = 'rgba(255,255,255,0.3)'; - this._ctx.fillRect(startCol * scaledCharWidth, viewportCappedStartRow * scaledCharHeight, (startRowEndCol - startCol) * scaledCharWidth, scaledCharHeight); + this._ctx.fillRect(startCol * this.scaledCharWidth, viewportCappedStartRow * this.scaledCharHeight, (startRowEndCol - startCol) * this.scaledCharWidth, this.scaledCharHeight); // Draw middle rows const middleRowsCount = Math.max(viewportCappedEndRow - viewportCappedStartRow - 1, 0); - this._ctx.fillRect(0, (viewportCappedStartRow + 1) * scaledCharHeight, terminal.cols * scaledCharWidth, middleRowsCount * scaledCharHeight); + this._ctx.fillRect(0, (viewportCappedStartRow + 1) * this.scaledCharHeight, terminal.cols * this.scaledCharWidth, middleRowsCount * this.scaledCharHeight); // Draw final row if (viewportCappedStartRow !== viewportCappedEndRow) { // Only draw viewportEndRow if it's not the same as viewporttartRow const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : terminal.cols; - this._ctx.fillRect(0, viewportCappedEndRow * scaledCharHeight, endCol * scaledCharWidth, scaledCharHeight); + this._ctx.fillRect(0, viewportCappedEndRow * this.scaledCharHeight, endCol * this.scaledCharWidth, this.scaledCharHeight); } // Save state for next render From 510940389c2714ba985475c9f140b02705aa4646 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 31 Aug 2017 23:01:15 -0700 Subject: [PATCH 135/378] Provide convenience draw methods that deal with cells --- src/renderer/BackgroundRenderLayer.ts | 8 +++---- src/renderer/BaseRenderLayer.ts | 32 ++++++++++++++++++--------- src/renderer/CursorRenderLayer.ts | 8 +++---- src/renderer/ForegroundRenderLayer.ts | 16 ++++++++------ src/renderer/Interfaces.ts | 2 +- src/renderer/Renderer.ts | 4 ++-- src/renderer/SelectionRenderLayer.ts | 12 +++++----- 7 files changed, 47 insertions(+), 35 deletions(-) diff --git a/src/renderer/BackgroundRenderLayer.ts b/src/renderer/BackgroundRenderLayer.ts index 1e8169d7..a37c4387 100644 --- a/src/renderer/BackgroundRenderLayer.ts +++ b/src/renderer/BackgroundRenderLayer.ts @@ -19,9 +19,9 @@ export class BackgroundRenderLayer extends BaseRenderLayer implements IDataRende this._state.resize(terminal.cols, terminal.rows); } - public clear(terminal: ITerminal): void { + public reset(terminal: ITerminal): void { this._state.clear(); - this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); + this.clearAll(); } public render(terminal: ITerminal, startRow: number, endRow: number): void { @@ -48,11 +48,11 @@ export class BackgroundRenderLayer extends BaseRenderLayer implements IDataRende if (bg < 256) { this._ctx.save(); this._ctx.fillStyle = COLORS[bg]; - this._ctx.fillRect(x * this.scaledCharWidth, y * this.scaledCharHeight, this.scaledCharWidth, this.scaledCharHeight); + this.fillCells(x, y, 1, 1); this._ctx.restore(); this._state.cache[x][y] = bg; } else { - this._ctx.clearRect(x * this.scaledCharWidth, y * this.scaledCharHeight, this.scaledCharWidth, this.scaledCharHeight); + this.clearCells(x, y, 1, 1); this._state.cache[x][y] = null; } } diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 7790e034..e286b220 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -3,13 +3,13 @@ import { ITerminal } from '../Interfaces'; import { COLORS } from './Color'; export abstract class BaseRenderLayer implements IRenderLayer { - protected _canvas: HTMLCanvasElement; + private _canvas: HTMLCanvasElement; protected _ctx: CanvasRenderingContext2D; - protected scaledCharWidth: number; - protected scaledCharHeight: number; + private scaledCharWidth: number; + private scaledCharHeight: number; // TODO: This will apply to all terminals, should it be per-terminal? - protected static _charAtlas: ImageBitmap; + private static _charAtlas: ImageBitmap; private static _charAtlasCharWidth: number; private static _charAtlasCharHeight: number; private static _charAtlasGenerator: CharAtlasGenerator; @@ -50,9 +50,21 @@ export abstract class BaseRenderLayer implements IRenderLayer { } } - public abstract clear(terminal: ITerminal): void; + public abstract reset(terminal: ITerminal): void; - protected drawChar(terminal: ITerminal, char: string, code: number, fg: number, x: number, y: number, scaledCharWidth: number, scaledCharHeight: number): void { + protected fillCells(startCol: number, startRow: number, colWidth: number, colHeight: number): void { + this._ctx.fillRect(startCol * this.scaledCharWidth, startRow * this.scaledCharHeight, colWidth * this.scaledCharWidth, colHeight * this.scaledCharHeight); + } + + protected clearAll(): void { + this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); + } + + protected clearCells(startCol: number, startRow: number, colWidth: number, colHeight: number): void { + this._ctx.clearRect(startCol * this.scaledCharWidth, startRow * this.scaledCharHeight, colWidth * this.scaledCharWidth, colHeight * this.scaledCharHeight); + } + + protected drawChar(terminal: ITerminal, char: string, code: number, fg: number, x: number, y: number): void { let colorIndex = 0; if (fg < 256) { colorIndex = fg + 1; @@ -60,10 +72,10 @@ export abstract class BaseRenderLayer implements IRenderLayer { if (code < 256 && (colorIndex > 0 || fg > 255)) { // ImageBitmap's draw about twice as fast as from a canvas this._ctx.drawImage(BaseRenderLayer._charAtlas, - code * scaledCharWidth, colorIndex * scaledCharHeight, scaledCharWidth, scaledCharHeight, - x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); + code * this.scaledCharWidth, colorIndex * this.scaledCharHeight, this.scaledCharWidth, this.scaledCharHeight, + x * this.scaledCharWidth, y * this.scaledCharHeight, this.scaledCharWidth, this.scaledCharHeight); } else { - this._drawUncachedChar(terminal, char, fg, x, y, scaledCharWidth, scaledCharHeight); + this._drawUncachedChar(terminal, char, fg, x, y, this.scaledCharWidth, this.scaledCharHeight); } // This draws the atlas (for debugging purposes) // this._ctx.drawImage(BaseRenderLayer._charAtlas, 0, 0); @@ -100,14 +112,12 @@ class CharAtlasGenerator { public generate(terminal: ITerminal, charWidth: number, charHeight: number): Promise { const scaledCharWidth = Math.ceil(charWidth) * window.devicePixelRatio; const scaledCharHeight = Math.ceil(charHeight) * window.devicePixelRatio; -console.log('generate'); this._canvas.width = 255 * scaledCharWidth; this._canvas.height = (/*default*/1 + /*0-15*/16) * scaledCharHeight; this._ctx.save(); this._ctx.fillStyle = '#ffffff'; this._ctx.font = `${terminal.options.fontSize * window.devicePixelRatio}px ${terminal.options.fontFamily}`; - console.log(this._ctx.font, scaledCharWidth, scaledCharHeight); this._ctx.textBaseline = 'top'; // Default color diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index bb0ff928..7b5be052 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -14,7 +14,7 @@ export class CursorRenderLayer extends BaseRenderLayer implements IDataRenderLay this._state = null; } - public clear(terminal: ITerminal): void { + public reset(terminal: ITerminal): void { this._clearCursor(); } @@ -46,18 +46,18 @@ export class CursorRenderLayer extends BaseRenderLayer implements IDataRenderLay this._ctx.save(); this._ctx.fillStyle = COLORS[COLOR_CODES.WHITE]; - this._ctx.fillRect(terminal.buffer.x * this.scaledCharWidth, viewportRelativeCursorY * this.scaledCharHeight, this.scaledCharWidth, this.scaledCharHeight); + this.fillCells(terminal.buffer.x, viewportRelativeCursorY, 1, 1); this._ctx.restore(); const charData = terminal.buffer.lines.get(cursorY)[terminal.buffer.x]; - this.drawChar(terminal, charData[CHAR_DATA_CHAR_INDEX], charData[CHAR_DATA_CODE_INDEX], COLOR_CODES.BLACK, terminal.buffer.x, viewportRelativeCursorY, this.scaledCharWidth, this.scaledCharHeight); + this.drawChar(terminal, charData[CHAR_DATA_CHAR_INDEX], charData[CHAR_DATA_CODE_INDEX], COLOR_CODES.BLACK, terminal.buffer.x, viewportRelativeCursorY); this._state = [terminal.buffer.x, viewportRelativeCursorY]; } private _clearCursor(): void { if (this._state) { - this._ctx.clearRect(this._state[0] * this.scaledCharWidth, this._state[1] * this.scaledCharHeight, this.scaledCharWidth, this.scaledCharHeight); + this.clearCells(this._state[0], this._state[1], 1, 1); this._state = null; } } diff --git a/src/renderer/ForegroundRenderLayer.ts b/src/renderer/ForegroundRenderLayer.ts index 68d00857..e5b814ce 100644 --- a/src/renderer/ForegroundRenderLayer.ts +++ b/src/renderer/ForegroundRenderLayer.ts @@ -20,18 +20,18 @@ export class ForegroundRenderLayer extends BaseRenderLayer implements IDataRende this._state.resize(terminal.cols, terminal.rows); } - public clear(terminal: ITerminal): void { + public reset(terminal: ITerminal): void { this._state.clear(); - this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); + this.clearAll(); } public render(terminal: ITerminal, startRow: number, endRow: number): void { // TODO: Ensure that the render is eventually performed // Don't bother render until the atlas bitmap is ready // TODO: Move this to BaseRenderLayer? - if (!BaseRenderLayer._charAtlas) { - return; - } + // if (!BaseRenderLayer._charAtlas) { + // return; + // } for (let y = startRow; y <= endRow; y++) { const row = y + terminal.buffer.ydisp; @@ -53,7 +53,7 @@ export class ForegroundRenderLayer extends BaseRenderLayer implements IDataRende this._state.cache[x][y] = charData; // Clear the old character - this._ctx.clearRect(x * this.scaledCharWidth, y * this.scaledCharHeight, this.scaledCharWidth, this.scaledCharHeight); + this.clearCells(x, y, 1, 1); // Skip rendering if the character is invisible if (!code || code === 32 /*' '*/) { @@ -72,6 +72,7 @@ export class ForegroundRenderLayer extends BaseRenderLayer implements IDataRende } } + this._ctx.save(); if (flags & FLAGS.BOLD) { this._ctx.font = `bold ${this._ctx.font}`; // Convert the FG color to the bold variant @@ -80,7 +81,8 @@ export class ForegroundRenderLayer extends BaseRenderLayer implements IDataRende } } - this.drawChar(terminal, char, code, fg, x, y, this.scaledCharWidth, this.scaledCharHeight); + this.drawChar(terminal, char, code, fg, x, y); + this._ctx.restore(); } } } diff --git a/src/renderer/Interfaces.ts b/src/renderer/Interfaces.ts index 8eb3acdd..82b2e77a 100644 --- a/src/renderer/Interfaces.ts +++ b/src/renderer/Interfaces.ts @@ -9,7 +9,7 @@ export interface IRenderLayer { /** * Clear the state of the render layer. */ - clear(terminal: ITerminal): void; + reset(terminal: ITerminal): void; } export interface IDataRenderLayer extends IRenderLayer { diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 26b472cf..a512c983 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -59,10 +59,10 @@ export class Renderer { public clear(): void { for (let i = 0; i < this._dataRenderLayers.length; i++) { - this._dataRenderLayers[i].clear(this._terminal); + this._dataRenderLayers[i].reset(this._terminal); } for (let i = 0; i < this._selectionRenderLayers.length; i++) { - this._selectionRenderLayers[i].clear(this._terminal); + this._selectionRenderLayers[i].reset(this._terminal); } } diff --git a/src/renderer/SelectionRenderLayer.ts b/src/renderer/SelectionRenderLayer.ts index 602bb486..daaf160d 100644 --- a/src/renderer/SelectionRenderLayer.ts +++ b/src/renderer/SelectionRenderLayer.ts @@ -16,13 +16,13 @@ export class SelectionRenderLayer extends BaseRenderLayer implements ISelectionR }; } - public clear(terminal: ITerminal): void { + public reset(terminal: ITerminal): void { if (this._state.start && this._state.end) { this._state = { start: null, end: null }; - this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); + this.clearAll(); } } @@ -33,7 +33,7 @@ export class SelectionRenderLayer extends BaseRenderLayer implements ISelectionR } // Remove all selections - this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); + this.clearAll(); // Selection does not exist if (!start || !end) { @@ -55,17 +55,17 @@ export class SelectionRenderLayer extends BaseRenderLayer implements ISelectionR const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0; const startRowEndCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : terminal.cols; this._ctx.fillStyle = 'rgba(255,255,255,0.3)'; - this._ctx.fillRect(startCol * this.scaledCharWidth, viewportCappedStartRow * this.scaledCharHeight, (startRowEndCol - startCol) * this.scaledCharWidth, this.scaledCharHeight); + this.fillCells(startCol, viewportCappedStartRow, startRowEndCol - startCol, 1); // Draw middle rows const middleRowsCount = Math.max(viewportCappedEndRow - viewportCappedStartRow - 1, 0); - this._ctx.fillRect(0, (viewportCappedStartRow + 1) * this.scaledCharHeight, terminal.cols * this.scaledCharWidth, middleRowsCount * this.scaledCharHeight); + this.fillCells(0, viewportCappedStartRow + 1, terminal.cols, middleRowsCount); // Draw final row if (viewportCappedStartRow !== viewportCappedEndRow) { // Only draw viewportEndRow if it's not the same as viewporttartRow const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : terminal.cols; - this._ctx.fillRect(0, viewportCappedEndRow * this.scaledCharHeight, endCol * this.scaledCharWidth, this.scaledCharHeight); + this.fillCells(0, viewportCappedEndRow, endCol, 1); } // Save state for next render From 42e5b3473dddaebe35af22ab9ea66dd356e549c5 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 31 Aug 2017 23:12:42 -0700 Subject: [PATCH 136/378] Don't clear fg char if it's null or ' ' --- src/Terminal.ts | 2 +- src/renderer/ForegroundRenderLayer.ts | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index c9c6466c..640aafaa 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -2046,7 +2046,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT public blankLine(cur?: boolean, isWrapped?: boolean, cols?: number): LineData { const attr = cur ? this.eraseAttr() : this.defAttr; - const ch: CharData = [attr, ' ', 1]; // width defaults to 1 halfwidth character + const ch: CharData = [attr, ' ', 1, 32 /* ' '.charCodeAt(0) */]; // width defaults to 1 halfwidth character const line: LineData = []; // TODO: It is not ideal that this is a property on an array, a buffer line diff --git a/src/renderer/ForegroundRenderLayer.ts b/src/renderer/ForegroundRenderLayer.ts index e5b814ce..680f1abc 100644 --- a/src/renderer/ForegroundRenderLayer.ts +++ b/src/renderer/ForegroundRenderLayer.ts @@ -50,10 +50,12 @@ export class ForegroundRenderLayer extends BaseRenderLayer implements IDataRende this._state.cache[x][y] = charData; continue; } - this._state.cache[x][y] = charData; - // Clear the old character - this.clearCells(x, y, 1, 1); + // Clear the old character if present + if (state && state[CHAR_DATA_CODE_INDEX] !== 32 /*' '*/) { + this.clearCells(x, y, 1, 1); + } + this._state.cache[x][y] = charData; // Skip rendering if the character is invisible if (!code || code === 32 /*' '*/) { From 81d93b8fbf817c83cd7dbb918ad33bbd6fbb35b6 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 31 Aug 2017 23:23:04 -0700 Subject: [PATCH 137/378] Ensure CharMeasure exposes integers --- src/renderer/BaseRenderLayer.ts | 10 ++++------ src/renderer/Renderer.ts | 9 ++++----- src/utils/CharMeasure.ts | 4 ++-- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index e286b220..331bc678 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -28,8 +28,8 @@ export abstract class BaseRenderLayer implements IRenderLayer { } public resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void { - this.scaledCharWidth = Math.ceil(terminal.charMeasure.width) * window.devicePixelRatio; - this.scaledCharHeight = Math.ceil(terminal.charMeasure.height) * window.devicePixelRatio; + this.scaledCharWidth = terminal.charMeasure.width * window.devicePixelRatio; + this.scaledCharHeight = terminal.charMeasure.height * window.devicePixelRatio; this._canvas.width = canvasWidth * window.devicePixelRatio; this._canvas.height = canvasHeight * window.devicePixelRatio; this._canvas.style.width = `${canvasWidth}px`; @@ -43,7 +43,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { BaseRenderLayer._charAtlas = null; BaseRenderLayer._charAtlasCharWidth = terminal.charMeasure.width; BaseRenderLayer._charAtlasCharHeight = terminal.charMeasure.height; - BaseRenderLayer._charAtlasGenerator.generate(terminal, terminal.charMeasure.width, terminal.charMeasure.height).then(bitmap => { + BaseRenderLayer._charAtlasGenerator.generate(terminal, this.scaledCharWidth, this.scaledCharHeight).then(bitmap => { BaseRenderLayer._charAtlas = bitmap; }); } @@ -109,9 +109,7 @@ class CharAtlasGenerator { this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); } - public generate(terminal: ITerminal, charWidth: number, charHeight: number): Promise { - const scaledCharWidth = Math.ceil(charWidth) * window.devicePixelRatio; - const scaledCharHeight = Math.ceil(charHeight) * window.devicePixelRatio; + public generate(terminal: ITerminal, scaledCharWidth: number, scaledCharHeight: number): Promise { this._canvas.width = 255 * scaledCharWidth; this._canvas.height = (/*default*/1 + /*0-15*/16) * scaledCharHeight; diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index a512c983..2cce350b 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -32,17 +32,16 @@ export class Renderer { } public onResize(cols: number, rows: number): void { - const width = Math.ceil(this._terminal.charMeasure.width) * this._terminal.cols; - const height = Math.ceil(this._terminal.charMeasure.height) * this._terminal.rows; + const width = this._terminal.charMeasure.width * this._terminal.cols; + const height = this._terminal.charMeasure.height * this._terminal.rows; for (let i = 0; i < this._dataRenderLayers.length; i++) { this._dataRenderLayers[i].resize(this._terminal, width, height, false); } } public onCharSizeChanged(charWidth: number, charHeight: number): void { - console.log('Renderer.onCharSizeChanged', charWidth, charHeight); - const width = Math.ceil(charWidth) * this._terminal.cols; - const height = Math.ceil(charHeight) * this._terminal.rows; + const width = charWidth * this._terminal.cols; + const height = charHeight * this._terminal.rows; for (let i = 0; i < this._dataRenderLayers.length; i++) { this._dataRenderLayers[i].resize(this._terminal, width, height, true); } diff --git a/src/utils/CharMeasure.ts b/src/utils/CharMeasure.ts index ff777811..1366bb63 100644 --- a/src/utils/CharMeasure.ts +++ b/src/utils/CharMeasure.ts @@ -59,8 +59,8 @@ export class CharMeasure extends EventEmitter implements ICharMeasure { return; } if (this._width !== geometry.width || this._height !== geometry.height) { - this._width = geometry.width; - this._height = geometry.height; + this._width = Math.ceil(geometry.width); + this._height = Math.ceil(geometry.height); this.emit('charsizechanged'); } } From 848c85c1ebf667080ea44664f25f04e87e56cc16 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 31 Aug 2017 23:28:42 -0700 Subject: [PATCH 138/378] Fix more cases where ' ' doesn't have code --- src/Terminal.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 640aafaa..92fc5440 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1983,7 +1983,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT if (!line) { return; } - const ch: CharData = [this.eraseAttr(), ' ', 1]; // xterm + const ch: CharData = [this.eraseAttr(), ' ', 1, 32 /* ' '.charCodeAt(0) */]; // xterm for (; x < this.cols; x++) { line[x] = ch; } @@ -2000,7 +2000,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT if (!line) { return; } - const ch: CharData = [this.eraseAttr(), ' ', 1]; // xterm + const ch: CharData = [this.eraseAttr(), ' ', 1, 32 /* ' '.charCodeAt(0) */]; // xterm x++; while (x--) { line[x] = ch; @@ -2068,7 +2068,10 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * @param cur */ public ch(cur?: boolean): CharData { - return cur ? [this.eraseAttr(), ' ', 1] : [this.defAttr, ' ', 1]; + if (cur) { + return [this.eraseAttr(), ' ', 1, 32 /* ' '.charCodeAt(0) */]; + } + return [this.defAttr, ' ', 1, 32 /* ' '.charCodeAt(0) */]; } /** From 882d7e4a301c50eea93a41a40604bb49e8352301 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 1 Sep 2017 11:34:52 -0700 Subject: [PATCH 139/378] Add basic support for cursor blinking --- src/Terminal.ts | 1 + src/renderer/BaseRenderLayer.ts | 22 +++++++- src/renderer/CursorRenderLayer.ts | 88 ++++++++++++++++++++++++++++--- src/renderer/Interfaces.ts | 10 +++- src/renderer/Renderer.ts | 9 ++++ 5 files changed, 121 insertions(+), 9 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 92fc5440..0c6eb65d 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -497,6 +497,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT case 'bellSound': case 'bellStyle': this.syncBellSound(); break; } + this.renderer.onOptionsChanged(); } private restartCursorBlinking(): void { diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 331bc678..047269c3 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -1,5 +1,5 @@ import { IRenderLayer } from './Interfaces'; -import { ITerminal } from '../Interfaces'; +import { ITerminal, ITerminalOptions } from '../Interfaces'; import { COLORS } from './Color'; export abstract class BaseRenderLayer implements IRenderLayer { @@ -27,6 +27,10 @@ export abstract class BaseRenderLayer implements IRenderLayer { } } + public onOptionsChanged(options: ITerminal): void { + // TODO: Should this do anything? + } + public resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void { this.scaledCharWidth = terminal.charMeasure.width * window.devicePixelRatio; this.scaledCharHeight = terminal.charMeasure.height * window.devicePixelRatio; @@ -56,6 +60,22 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._ctx.fillRect(startCol * this.scaledCharWidth, startRow * this.scaledCharHeight, colWidth * this.scaledCharWidth, colHeight * this.scaledCharHeight); } + protected fillBottomLineAtCell(x: number, y: number): void { + this._ctx.fillRect( + x * this.scaledCharWidth, + (y + 1) * this.scaledCharHeight - window.devicePixelRatio - 1 /* Ensure it's drawn within the cell */, + this.scaledCharWidth, + window.devicePixelRatio); + } + + protected fillLeftLineAtCell(x: number, y: number): void { + this._ctx.fillRect( + x * this.scaledCharWidth, + y * this.scaledCharHeight, + window.devicePixelRatio, + this.scaledCharHeight); + } + protected clearAll(): void { this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); } diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index 7b5be052..89707590 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -1,28 +1,57 @@ import { IDataRenderLayer } from './Interfaces'; -import { IBuffer, ICharMeasure, ITerminal } from '../Interfaces'; +import { IBuffer, ICharMeasure, ITerminal, ITerminalOptions } from '../Interfaces'; import { CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX } from '../Buffer'; import { COLORS, COLOR_CODES } from './Color'; import { GridCache } from './GridCache'; import { FLAGS } from './Types'; import { BaseRenderLayer } from './BaseRenderLayer'; +import { CharData } from '../Types'; + +/** + * The time between cursor blinks. + */ +const BLINK_INTERVAL = 600; export class CursorRenderLayer extends BaseRenderLayer implements IDataRenderLayer { private _state: [number, number]; + private _cursorRenderers: {[key: string]: (terminal: ITerminal, x: number, y: number, charData: CharData) => void}; + private _animationFrame: number; + private _blinkInterval: number; + private _isVisible: boolean; constructor(container: HTMLElement, zIndex: number) { super(container, 'cursor', zIndex); this._state = null; + this._isVisible = true; + this._cursorRenderers = { + 'bar': this._renderBarCursor.bind(this), + 'block': this._renderBlockCursor.bind(this), + 'underline': this._renderUnderlineCursor.bind(this) + }; } public reset(terminal: ITerminal): void { this._clearCursor(); + this._isVisible = true; + } + + public onOptionsChanged(terminal: ITerminal): void { + super.onOptionsChanged(terminal); + this._refreshBlinkState(terminal); } public render(terminal: ITerminal, startRow: number, endRow: number): void { + // Only render if the animation frame is not active + if (!this._blinkInterval) { + this._render(terminal, false); + } + } + + private _render(terminal: ITerminal, triggeredByAnimationFrame: boolean): void { // TODO: Track blur/focus somehow, support unfocused cursor // Don't draw the cursor if it's hidden - if (!terminal.cursorState || terminal.cursorHidden) { + if (!terminal.cursorState || terminal.cursorHidden || !this._isVisible) { this._clearCursor(); return; } @@ -44,14 +73,11 @@ export class CursorRenderLayer extends BaseRenderLayer implements IDataRenderLay this._clearCursor(); } + const charData = terminal.buffer.lines.get(cursorY)[terminal.buffer.x]; this._ctx.save(); this._ctx.fillStyle = COLORS[COLOR_CODES.WHITE]; - this.fillCells(terminal.buffer.x, viewportRelativeCursorY, 1, 1); + this._cursorRenderers[terminal.options.cursorStyle || 'block'](terminal, terminal.buffer.x, viewportRelativeCursorY, charData); this._ctx.restore(); - - const charData = terminal.buffer.lines.get(cursorY)[terminal.buffer.x]; - this.drawChar(terminal, charData[CHAR_DATA_CHAR_INDEX], charData[CHAR_DATA_CODE_INDEX], COLOR_CODES.BLACK, terminal.buffer.x, viewportRelativeCursorY); - this._state = [terminal.buffer.x, viewportRelativeCursorY]; } @@ -61,4 +87,52 @@ export class CursorRenderLayer extends BaseRenderLayer implements IDataRenderLay this._state = null; } } + + private _renderBarCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void { + this.fillLeftLineAtCell(x, y); + } + + private _renderBlockCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void { + this.fillCells(x, y, 1, 1); + this.drawChar(terminal, charData[CHAR_DATA_CHAR_INDEX], charData[CHAR_DATA_CODE_INDEX], COLOR_CODES.BLACK, x, y); + } + + private _renderUnderlineCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void { + this.fillBottomLineAtCell(x, y); + } + + private _refreshBlinkState(terminal: ITerminal): void { + if (terminal.options.cursorBlink) { + if (!this._blinkInterval) { + this._blinkInterval = setInterval(() => { + this._isVisible = !this._isVisible; + this._animationFrame = window.requestAnimationFrame(() => { + this._render(terminal, true); + this._animationFrame = null; + }); + }, BLINK_INTERVAL); + } + } else { + if (this._animationFrame) { + window.clearInterval(this._blinkInterval); + this._blinkInterval = null; + window.cancelAnimationFrame(this._animationFrame); + this._animationFrame = null; + this._isVisible = true; + } + } + } + + private _restartBlinkAnimation(terminal: ITerminal): void { + // TODO: Restart the blink animation when input is received + // How can this be done efficiently, without thrashing with restarting the timers? + } + + private _pauseBlinkAnimation(): void { + // TODO: Pause the blink animation on blur + } + + private _resumeBlinkAnimation(): void { + // TODO: Resume the blink animation on focus + } } diff --git a/src/renderer/Interfaces.ts b/src/renderer/Interfaces.ts index 82b2e77a..55cffbc5 100644 --- a/src/renderer/Interfaces.ts +++ b/src/renderer/Interfaces.ts @@ -1,6 +1,8 @@ -import { ITerminal } from '../Interfaces'; +import { ITerminal, ITerminalOptions } from '../Interfaces'; export interface IRenderLayer { + onOptionsChanged(options: ITerminal): void; + /** * Resize the render layer. */ @@ -12,10 +14,16 @@ export interface IRenderLayer { reset(terminal: ITerminal): void; } +/** + * A render layer that renders when there is a data change. + */ export interface IDataRenderLayer extends IRenderLayer { render(terminal: ITerminal, startRow: number, endRow: number): void; } +/** + * A render layer that renders when there is a selection change. + */ export interface ISelectionRenderLayer extends IRenderLayer { render(terminal: ITerminal, start: [number, number], end: [number, number]): void; } diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 2cce350b..2903460a 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -56,6 +56,15 @@ export class Renderer { } } + public onOptionsChanged(): void { + for (let i = 0; i < this._dataRenderLayers.length; i++) { + this._dataRenderLayers[i].onOptionsChanged(this._terminal); + } + for (let i = 0; i < this._selectionRenderLayers.length; i++) { + this._selectionRenderLayers[i].onOptionsChanged(this._terminal); + } + } + public clear(): void { for (let i = 0; i < this._dataRenderLayers.length; i++) { this._dataRenderLayers[i].reset(this._terminal); From 3fc500b291de87731e160e3323b6de5070faffde Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 1 Sep 2017 12:11:07 -0700 Subject: [PATCH 140/378] Pull cursor animation state management into a helper class --- src/Interfaces.ts | 1 + src/renderer/CursorRenderLayer.ts | 78 ++++++++++++++++++++++--------- 2 files changed, 57 insertions(+), 22 deletions(-) diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 1b1625bc..afa2de58 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -47,6 +47,7 @@ export interface ITerminal extends IEventEmitter { reset(): void; showCursor(): void; blankLine(cur?: boolean, isWrapped?: boolean, cols?: number): LineData; + refresh(start: number, end: number): void; } /** diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index 89707590..11e56347 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -19,6 +19,15 @@ export class CursorRenderLayer extends BaseRenderLayer implements IDataRenderLay private _blinkInterval: number; private _isVisible: boolean; + /** + * The time at which the animation frame was restarted, this is used on the + * next render to restart the timers so they don't need to restart the timers + * multiple times over a short period. + */ + private _animationTimeRestarted: number; + + private _cursorBlinkStateManager: CursorBlinkStateManager; + constructor(container: HTMLElement, zIndex: number) { super(container, 'cursor', zIndex); this._state = null; @@ -28,6 +37,7 @@ export class CursorRenderLayer extends BaseRenderLayer implements IDataRenderLay 'block': this._renderBlockCursor.bind(this), 'underline': this._renderUnderlineCursor.bind(this) }; + // TODO: Consider initial options? Maybe onOptionsChanged should be called at the end of open? } public reset(terminal: ITerminal): void { @@ -37,21 +47,36 @@ export class CursorRenderLayer extends BaseRenderLayer implements IDataRenderLay public onOptionsChanged(terminal: ITerminal): void { super.onOptionsChanged(terminal); - this._refreshBlinkState(terminal); + if (terminal.options.cursorBlink) { + if (!this._cursorBlinkStateManager) { + this._cursorBlinkStateManager = new CursorBlinkStateManager(terminal, () => { + this._render(terminal, true); + }); + } + } else { + if (this._cursorBlinkStateManager) { + this._cursorBlinkStateManager.dispose(); + this._cursorBlinkStateManager = null; + } + // Request a refresh from the terminal as management of rendering is being + // moved back to the terminal + terminal.refresh(terminal.buffer.y, terminal.buffer.y); + } } public render(terminal: ITerminal, startRow: number, endRow: number): void { // Only render if the animation frame is not active - if (!this._blinkInterval) { + if (!this._cursorBlinkStateManager) { this._render(terminal, false); } } private _render(terminal: ITerminal, triggeredByAnimationFrame: boolean): void { // TODO: Track blur/focus somehow, support unfocused cursor - // Don't draw the cursor if it's hidden - if (!terminal.cursorState || terminal.cursorHidden || !this._isVisible) { + if (!terminal.cursorState || + terminal.cursorHidden || + (this._cursorBlinkStateManager && !this._cursorBlinkStateManager.isCursorVisible)) { this._clearCursor(); return; } @@ -100,32 +125,41 @@ export class CursorRenderLayer extends BaseRenderLayer implements IDataRenderLay private _renderUnderlineCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void { this.fillBottomLineAtCell(x, y); } +} - private _refreshBlinkState(terminal: ITerminal): void { - if (terminal.options.cursorBlink) { - if (!this._blinkInterval) { - this._blinkInterval = setInterval(() => { - this._isVisible = !this._isVisible; - this._animationFrame = window.requestAnimationFrame(() => { - this._render(terminal, true); - this._animationFrame = null; - }); - }, BLINK_INTERVAL); - } - } else { - if (this._animationFrame) { - window.clearInterval(this._blinkInterval); - this._blinkInterval = null; - window.cancelAnimationFrame(this._animationFrame); +class CursorBlinkStateManager { + public isCursorVisible: boolean; + + private _animationFrame: number; + private _blinkInterval: number; + + constructor( + terminal: ITerminal, + private renderCallback: () => void + ) { + this.isCursorVisible = true; + this._blinkInterval = setInterval(() => { + this.isCursorVisible = !this.isCursorVisible; + this._animationFrame = window.requestAnimationFrame(() => { + this.renderCallback(); this._animationFrame = null; - this._isVisible = true; - } + }); + }, BLINK_INTERVAL); + } + + public dispose(): void { + window.clearInterval(this._blinkInterval); + this._blinkInterval = null; + if (this._animationFrame) { + window.cancelAnimationFrame(this._animationFrame); + this._animationFrame = null; } } private _restartBlinkAnimation(terminal: ITerminal): void { // TODO: Restart the blink animation when input is received // How can this be done efficiently, without thrashing with restarting the timers? + // Could record the time it was restarted and diff that on next render? } private _pauseBlinkAnimation(): void { From 076156b66e927814db251d5a52f04f5e95f1b833 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 1 Sep 2017 12:59:18 -0700 Subject: [PATCH 141/378] Properly support blinking cursors --- src/Buffer.ts | 4 +- src/Parser.ts | 10 +++ src/Terminal.ts | 4 ++ src/renderer/BaseRenderLayer.ts | 4 +- src/renderer/CursorRenderLayer.ts | 109 +++++++++++++++++++++++------- src/renderer/Interfaces.ts | 1 + src/renderer/Renderer.ts | 9 +++ 7 files changed, 113 insertions(+), 28 deletions(-) diff --git a/src/Buffer.ts b/src/Buffer.ts index d8311f06..101244f1 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -34,8 +34,8 @@ export class Buffer implements IBuffer { /** * Create a new Buffer. * @param _terminal The terminal the Buffer will belong to. - * @param _hasScrollback Whether the buffer should respecr the scrollback of - * the terminal.. + * @param _hasScrollback Whether the buffer should respect the scrollback of + * the terminal. */ constructor( private _terminal: ITerminal, diff --git a/src/Parser.ts b/src/Parser.ts index 983b3536..b047f91f 100644 --- a/src/Parser.ts +++ b/src/Parser.ts @@ -189,6 +189,9 @@ export class Parser { let code; let low; + const cursorStartX = this._terminal.buffer.x; + const cursorStartY = this._terminal.buffer.y; + if (this._terminal.debug) { this._terminal.log('data: ' + data); } @@ -580,6 +583,13 @@ export class Parser { break; } } + + // Fire the cursormove event if it's moved. This is done inside the parser + // as a render cannot happen in the middle of a parsing round. + if (this._terminal.buffer.x !== cursorStartX || this._terminal.buffer.y !== cursorStartY) { + this._terminal.emit('cursormove'); + } + return this._state; } diff --git a/src/Terminal.ts b/src/Terminal.ts index 0c6eb65d..17fd0d97 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -754,6 +754,10 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.viewport = new Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasure); this.renderer = new Renderer(this); + this.on('cursormove', () => { + console.log('cursormove fired'); + this.renderer.onCursorMove(); + }); this.on('resize', () => this.renderer.onResize(this.cols, this.rows)); this.charMeasure.on('charsizechanged', () => { this.renderer.onCharSizeChanged(this.charMeasure.width, this.charMeasure.height); diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 047269c3..378ac1a1 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -27,9 +27,9 @@ export abstract class BaseRenderLayer implements IRenderLayer { } } - public onOptionsChanged(options: ITerminal): void { // TODO: Should this do anything? - } + public onOptionsChanged(options: ITerminal): void {} + public onCursorMove(options: ITerminal): void {} public resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void { this.scaledCharWidth = terminal.charMeasure.width * window.devicePixelRatio; diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index 11e56347..3ccf2ec2 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -15,23 +15,11 @@ const BLINK_INTERVAL = 600; export class CursorRenderLayer extends BaseRenderLayer implements IDataRenderLayer { private _state: [number, number]; private _cursorRenderers: {[key: string]: (terminal: ITerminal, x: number, y: number, charData: CharData) => void}; - private _animationFrame: number; - private _blinkInterval: number; - private _isVisible: boolean; - - /** - * The time at which the animation frame was restarted, this is used on the - * next render to restart the timers so they don't need to restart the timers - * multiple times over a short period. - */ - private _animationTimeRestarted: number; - private _cursorBlinkStateManager: CursorBlinkStateManager; constructor(container: HTMLElement, zIndex: number) { super(container, 'cursor', zIndex); this._state = null; - this._isVisible = true; this._cursorRenderers = { 'bar': this._renderBarCursor.bind(this), 'block': this._renderBlockCursor.bind(this), @@ -42,7 +30,11 @@ export class CursorRenderLayer extends BaseRenderLayer implements IDataRenderLay public reset(terminal: ITerminal): void { this._clearCursor(); - this._isVisible = true; + if (this._cursorBlinkStateManager) { + this._cursorBlinkStateManager.dispose(); + this._cursorBlinkStateManager = null; + this.onOptionsChanged(terminal); + } } public onOptionsChanged(terminal: ITerminal): void { @@ -64,6 +56,12 @@ export class CursorRenderLayer extends BaseRenderLayer implements IDataRenderLay } } + public onCursorMove(terminal: ITerminal): void { + if (this._cursorBlinkStateManager) { + this._cursorBlinkStateManager.restartBlinkAnimation(terminal); + } + } + public render(terminal: ITerminal, startRow: number, endRow: number): void { // Only render if the animation frame is not active if (!this._cursorBlinkStateManager) { @@ -131,20 +129,22 @@ class CursorBlinkStateManager { public isCursorVisible: boolean; private _animationFrame: number; + private _blinkStartTimeout: number; private _blinkInterval: number; + /** + * The time at which the animation frame was restarted, this is used on the + * next render to restart the timers so they don't need to restart the timers + * multiple times over a short period. + */ + private _animationTimeRestarted: number; + constructor( terminal: ITerminal, private renderCallback: () => void ) { this.isCursorVisible = true; - this._blinkInterval = setInterval(() => { - this.isCursorVisible = !this.isCursorVisible; - this._animationFrame = window.requestAnimationFrame(() => { - this.renderCallback(); - this._animationFrame = null; - }); - }, BLINK_INTERVAL); + this._restartInterval(); } public dispose(): void { @@ -156,10 +156,71 @@ class CursorBlinkStateManager { } } - private _restartBlinkAnimation(terminal: ITerminal): void { - // TODO: Restart the blink animation when input is received - // How can this be done efficiently, without thrashing with restarting the timers? - // Could record the time it was restarted and diff that on next render? + public restartBlinkAnimation(terminal: ITerminal): void { + console.log('restartBlinkAnimation'); + // Save a timestamp so that the restart can be done on the next interval + this._animationTimeRestarted = Date.now(); + // Force a cursor render to ensure it's visible and in the correct position + this.isCursorVisible = true; + if (!this._animationFrame) { + this._animationFrame = window.requestAnimationFrame(() => { + this.renderCallback(); + this._animationFrame = null; + }); + } + } + + private _restartInterval(timeToStart: number = BLINK_INTERVAL): void { + // Clear any existing interval + if (this._blinkInterval) { + window.clearInterval(this._blinkInterval); + } + + console.log('restartInterval'); + // Setup the initial timeout which will hide the cursor, this is done before + // the regular interval is setup in order to support restarting the blink + // animation in a lightweight way (without thrashing clearInterval and + // setInterval). + this._blinkStartTimeout = setTimeout(() => { + // Check if another animation restart was requested while this was being + // started + if (this._animationTimeRestarted) { + const time = BLINK_INTERVAL - (Date.now() - this._animationTimeRestarted); + this._animationTimeRestarted = null; + this._restartInterval(time); + return; + } + + console.log('timeout'); + // Hide the cursor + this.isCursorVisible = false; + this._animationFrame = window.requestAnimationFrame(() => { + this.renderCallback(); + this._animationFrame = null; + }); + + // Setup the blink interval + this._blinkInterval = setInterval(() => { + console.log('interval'); + // Adjust the animation time if it was restarted + if (this._animationTimeRestarted) { + // calc time diff + // Make restart interval do a setTimeout initially? + const time = BLINK_INTERVAL - (Date.now() - this._animationTimeRestarted); + this._animationTimeRestarted = null; + console.log(' restart in ', time); + this._restartInterval(time); + return; + } + + // Invert visibility and render + this.isCursorVisible = !this.isCursorVisible; + this._animationFrame = window.requestAnimationFrame(() => { + this.renderCallback(); + this._animationFrame = null; + }); + }, BLINK_INTERVAL); + }, timeToStart); } private _pauseBlinkAnimation(): void { diff --git a/src/renderer/Interfaces.ts b/src/renderer/Interfaces.ts index 55cffbc5..5da13109 100644 --- a/src/renderer/Interfaces.ts +++ b/src/renderer/Interfaces.ts @@ -1,6 +1,7 @@ import { ITerminal, ITerminalOptions } from '../Interfaces'; export interface IRenderLayer { + onCursorMove(options: ITerminal): void; onOptionsChanged(options: ITerminal): void; /** diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 2903460a..7c6f6d40 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -56,6 +56,15 @@ export class Renderer { } } + public onCursorMove(): void { + for (let i = 0; i < this._dataRenderLayers.length; i++) { + this._dataRenderLayers[i].onCursorMove(this._terminal); + } + for (let i = 0; i < this._selectionRenderLayers.length; i++) { + this._selectionRenderLayers[i].onCursorMove(this._terminal); + } + } + public onOptionsChanged(): void { for (let i = 0; i < this._dataRenderLayers.length; i++) { this._dataRenderLayers[i].onOptionsChanged(this._terminal); From 62fea4d3edfb7f80f7d23f89253cacef26077fd2 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 1 Sep 2017 15:50:18 -0700 Subject: [PATCH 142/378] Add theme support --- src/Interfaces.ts | 22 ++++++ src/Terminal.ts | 9 ++- src/renderer/BackgroundRenderLayer.ts | 9 +-- src/renderer/BaseRenderLayer.ts | 102 +++++++------------------ src/renderer/Color.ts | 70 ----------------- src/renderer/ColorManager.ts | 104 ++++++++++++++++++++++++++ src/renderer/CursorRenderLayer.ts | 15 ++-- src/renderer/ForegroundRenderLayer.ts | 7 +- src/renderer/Interfaces.ts | 12 ++- src/renderer/Renderer.ts | 32 ++++++-- src/renderer/SelectionRenderLayer.ts | 6 +- src/renderer/Types.ts | 5 -- src/utils/CharAtlas.ts | 66 ++++++++++++++++ 13 files changed, 278 insertions(+), 181 deletions(-) delete mode 100644 src/renderer/Color.ts create mode 100644 src/renderer/ColorManager.ts create mode 100644 src/utils/CharAtlas.ts diff --git a/src/Interfaces.ts b/src/Interfaces.ts index afa2de58..3a0c9929 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -294,3 +294,25 @@ export interface IInputHandler { /** CSI s */ saveCursor(params?: number[]): void; /** CSI u */ restoreCursor(params?: number[]): void; } + +export interface ITheme { + foreground?: string; + background?: string; + // cursor?: 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; +} diff --git a/src/Terminal.ts b/src/Terminal.ts index 17fd0d97..a6633235 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -39,7 +39,7 @@ import * as Mouse from './utils/Mouse'; import { CHARSETS } from './Charsets'; import { getRawByteCoords } from './utils/Mouse'; import { CustomKeyEventHandler, Charset, LinkMatcherHandler, LinkMatcherValidationCallback, CharData, LineData } from './Types'; -import { ITerminal, IBrowser, ITerminalOptions, IInputHandlingTerminal, ILinkMatcherOptions, IViewport, ICompositionHelper } from './Interfaces'; +import { ITerminal, IBrowser, ITerminalOptions, IInputHandlingTerminal, ILinkMatcherOptions, IViewport, ICompositionHelper, ITheme } from './Interfaces'; import { BellSound } from './utils/Sounds'; // Declare for RequireJS in loadAddon @@ -412,6 +412,13 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.textarea.focus(); } + public setTheme(theme: ITheme): void { + // TODO: Allow setting of theme before renderer is ready + if (this.renderer) { + this.renderer.setTheme(theme); + } + } + /** * Retrieves an option's value from the terminal. * @param {string} key The option key. diff --git a/src/renderer/BackgroundRenderLayer.ts b/src/renderer/BackgroundRenderLayer.ts index a37c4387..cf122cc2 100644 --- a/src/renderer/BackgroundRenderLayer.ts +++ b/src/renderer/BackgroundRenderLayer.ts @@ -1,7 +1,6 @@ -import { IDataRenderLayer } from './Interfaces'; +import { IDataRenderLayer, IColorSet } from './Interfaces'; import { IBuffer, ICharMeasure, ITerminal } from '../Interfaces'; import { CHAR_DATA_ATTR_INDEX } from '../Buffer'; -import { COLORS } from './Color'; import { GridCache } from './GridCache'; import { FLAGS } from './Types'; import { BaseRenderLayer } from './BaseRenderLayer'; @@ -9,8 +8,8 @@ import { BaseRenderLayer } from './BaseRenderLayer'; export class BackgroundRenderLayer extends BaseRenderLayer implements IDataRenderLayer { private _state: GridCache; - constructor(container: HTMLElement, zIndex: number) { - super(container, 'bg', zIndex); + constructor(container: HTMLElement, zIndex: number, colors: IColorSet) { + super(container, 'bg', zIndex, colors); this._state = new GridCache(); } @@ -47,7 +46,7 @@ export class BackgroundRenderLayer extends BaseRenderLayer implements IDataRende if (needsRefresh) { if (bg < 256) { this._ctx.save(); - this._ctx.fillStyle = COLORS[bg]; + this._ctx.fillStyle = this.colors.ansi[bg]; this.fillCells(x, y, 1, 1); this._ctx.restore(); this._state.cache[x][y] = bg; diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 378ac1a1..d131bee0 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -1,6 +1,6 @@ -import { IRenderLayer } from './Interfaces'; +import { IRenderLayer, IColorSet } from './Interfaces'; import { ITerminal, ITerminalOptions } from '../Interfaces'; -import { COLORS } from './Color'; +import { acquireCharAtlas } from '../utils/CharAtlas'; export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; @@ -8,28 +8,34 @@ export abstract class BaseRenderLayer implements IRenderLayer { private scaledCharWidth: number; private scaledCharHeight: number; - // TODO: This will apply to all terminals, should it be per-terminal? - private static _charAtlas: ImageBitmap; - private static _charAtlasCharWidth: number; - private static _charAtlasCharHeight: number; - private static _charAtlasGenerator: CharAtlasGenerator; + // TODO: This should be shared between terminals, but not for static as some + // terminals may have different styles + private _charAtlas: ImageBitmap; - constructor(container: HTMLElement, id: string, zIndex: number) { + constructor( + container: HTMLElement, + id: string, + zIndex: number, + protected colors: IColorSet + ) { this._canvas = document.createElement('canvas'); this._canvas.id = `xterm-${id}-layer`; this._canvas.style.zIndex = zIndex.toString(); this._ctx = this._canvas.getContext('2d'); this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); container.appendChild(this._canvas); - - if (!BaseRenderLayer._charAtlasGenerator) { - BaseRenderLayer._charAtlasGenerator = new CharAtlasGenerator(); - } } - // TODO: Should this do anything? - public onOptionsChanged(options: ITerminal): void {} - public onCursorMove(options: ITerminal): void {} + // TODO: Should this do anything? + public onOptionsChanged(terminal: ITerminal): void {} + public onCursorMove(terminal: ITerminal): void {} + + public onThemeChanged(terminal: ITerminal, colorSet: IColorSet): void { + this._charAtlas = null; + acquireCharAtlas(terminal, this.colors).then(bitmap => { + this._charAtlas = bitmap; + }); + } public resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void { this.scaledCharWidth = terminal.charMeasure.width * window.devicePixelRatio; @@ -40,17 +46,9 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._canvas.style.height = `${canvasHeight}px`; if (charSizeChanged) { - // Only update the char atlas if an update for the right dimensions is not - // already in progress - if (BaseRenderLayer._charAtlasCharWidth !== terminal.charMeasure.width || - BaseRenderLayer._charAtlasCharHeight !== terminal.charMeasure.height) { - BaseRenderLayer._charAtlas = null; - BaseRenderLayer._charAtlasCharWidth = terminal.charMeasure.width; - BaseRenderLayer._charAtlasCharHeight = terminal.charMeasure.height; - BaseRenderLayer._charAtlasGenerator.generate(terminal, this.scaledCharWidth, this.scaledCharHeight).then(bitmap => { - BaseRenderLayer._charAtlas = bitmap; - }); - } + acquireCharAtlas(terminal, this.colors).then(bitmap => { + this._charAtlas = bitmap; + }); } } @@ -91,7 +89,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { } if (code < 256 && (colorIndex > 0 || fg > 255)) { // ImageBitmap's draw about twice as fast as from a canvas - this._ctx.drawImage(BaseRenderLayer._charAtlas, + this._ctx.drawImage(this._charAtlas, code * this.scaledCharWidth, colorIndex * this.scaledCharHeight, this.scaledCharWidth, this.scaledCharHeight, x * this.scaledCharWidth, y * this.scaledCharHeight, this.scaledCharWidth, this.scaledCharHeight); } else { @@ -108,7 +106,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { // 256 color support if (fg < 256) { - this._ctx.fillStyle = COLORS[fg]; + this._ctx.fillStyle = this.colors.ansi[fg]; } else { this._ctx.fillStyle = '#ffffff'; } @@ -119,51 +117,3 @@ export abstract class BaseRenderLayer implements IRenderLayer { } } -class CharAtlasGenerator { - private _canvas: HTMLCanvasElement; - private _ctx: CanvasRenderingContext2D; - - constructor() { - this._canvas = document.createElement('canvas'); - this._ctx = this._canvas.getContext('2d'); - this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); - } - - public generate(terminal: ITerminal, scaledCharWidth: number, scaledCharHeight: number): Promise { - this._canvas.width = 255 * scaledCharWidth; - this._canvas.height = (/*default*/1 + /*0-15*/16) * scaledCharHeight; - - this._ctx.save(); - this._ctx.fillStyle = '#ffffff'; - this._ctx.font = `${terminal.options.fontSize * window.devicePixelRatio}px ${terminal.options.fontFamily}`; - this._ctx.textBaseline = 'top'; - - // Default color - for (let i = 0; i < 256; i++) { - this._ctx.fillText(String.fromCharCode(i), i * scaledCharWidth, 0); - } - - // Colors 0-15 - for (let colorIndex = 0; colorIndex < 16; colorIndex++) { - // colors 8-15 are bold - if (colorIndex === 8) { - this._ctx.font = `bold ${this._ctx.font}`; - } - const y = (colorIndex + 1) * scaledCharHeight; - // Clear rectangle as some fonts seem to draw over the bottom boundary - this._ctx.clearRect(0, y, this._canvas.width, scaledCharHeight); - // Draw ascii characters - for (let i = 0; i < 256; i++) { - this._ctx.fillStyle = COLORS[colorIndex]; - this._ctx.fillText(String.fromCharCode(i), i * scaledCharWidth, y); - } - } - this._ctx.restore(); - - const charAtlasImageData = this._ctx.getImageData(0, 0, this._canvas.width, this._canvas.height); - const promise = window.createImageBitmap(charAtlasImageData); - // Clear the rect while the promise is in progress - this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); - return promise; - } -} diff --git a/src/renderer/Color.ts b/src/renderer/Color.ts deleted file mode 100644 index 446d440b..00000000 --- a/src/renderer/Color.ts +++ /dev/null @@ -1,70 +0,0 @@ -// TODO: Ideally colors would be exposed through some theme manager since colors -// are moving to JS. - -export enum COLOR_CODES { - BLACK = 0, - RED = 1, - GREEN = 2, - YELLOW = 3, - BLUE = 4, - MAGENTA = 5, - CYAN = 6, - WHITE = 7, - BRIGHT_BLACK = 8, - BRIGHT_RED = 9, - BRIGHT_GREEN = 10, - BRIGHT_YELLOW = 11, - BRIGHT_BLUE = 12, - BRIGHT_MAGENTA = 13, - BRIGHT_CYAN = 14, - BRIGHT_WHITE = 15 -} - -const TANGO_COLORS = [ - // dark: - '#2e3436', - '#cc0000', - '#4e9a06', - '#c4a000', - '#3465a4', - '#75507b', - '#06989a', - '#d3d7cf', - // bright: - '#555753', - '#ef2929', - '#8ae234', - '#fce94f', - '#729fcf', - '#ad7fa8', - '#34e2e2', - '#eeeeec' -]; - -export const COLORS: string[] = generate256Colors(TANGO_COLORS); - -function generate256Colors(first16Colors: string[]): string[] { - let colors = first16Colors.slice(); - - // Generate colors (16-231) - let v = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]; - for (let i = 0; i < 216; i++) { - const r = toPaddedHex(v[(i / 36) % 6 | 0]); - const g = toPaddedHex(v[(i / 6) % 6 | 0]); - const b = toPaddedHex(v[i % 6]); - colors.push(`#${r}${g}${b}`); - } - - // Generate greys (232-255) - for (let i = 0; i < 24; i++) { - const c = toPaddedHex(8 + i * 10); - colors.push(`#${c}${c}${c}`); - } - - return colors; -} - -function toPaddedHex(c: number): string { - let s = c.toString(16); - return s.length < 2 ? '0' + s : s; -} diff --git a/src/renderer/ColorManager.ts b/src/renderer/ColorManager.ts new file mode 100644 index 00000000..ca8015a2 --- /dev/null +++ b/src/renderer/ColorManager.ts @@ -0,0 +1,104 @@ +import { IColorSet } from './Interfaces'; +import { ITheme } from '../Interfaces'; + +// TODO: Ideally colors would be exposed through some theme manager since colors +// are moving to JS. + +export enum COLOR_CODES { + BLACK = 0, + RED = 1, + GREEN = 2, + YELLOW = 3, + BLUE = 4, + MAGENTA = 5, + CYAN = 6, + WHITE = 7, + BRIGHT_BLACK = 8, + BRIGHT_RED = 9, + BRIGHT_GREEN = 10, + BRIGHT_YELLOW = 11, + BRIGHT_BLUE = 12, + BRIGHT_MAGENTA = 13, + BRIGHT_CYAN = 14, + BRIGHT_WHITE = 15 +} + +const DEFAULT_ANSI_COLORS = [ + // dark: + '#2e3436', + '#cc0000', + '#4e9a06', + '#c4a000', + '#3465a4', + '#75507b', + '#06989a', + '#d3d7cf', + // bright: + '#555753', + '#ef2929', + '#8ae234', + '#fce94f', + '#729fcf', + '#ad7fa8', + '#34e2e2', + '#eeeeec' +]; + +function generate256Colors(first16Colors: string[]): string[] { + let colors = first16Colors.slice(); + + // Generate colors (16-231) + let v = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]; + for (let i = 0; i < 216; i++) { + const r = toPaddedHex(v[(i / 36) % 6 | 0]); + const g = toPaddedHex(v[(i / 6) % 6 | 0]); + const b = toPaddedHex(v[i % 6]); + colors.push(`#${r}${g}${b}`); + } + + // Generate greys (232-255) + for (let i = 0; i < 24; i++) { + const c = toPaddedHex(8 + i * 10); + colors.push(`#${c}${c}${c}`); + } + + return colors; +} + +function toPaddedHex(c: number): string { + let s = c.toString(16); + return s.length < 2 ? '0' + s : s; +} + +export class ColorManager { + public colors: IColorSet; + + constructor() { + this.colors = { + foreground: '#ffffff', + background: '#000000', + ansi: generate256Colors(DEFAULT_ANSI_COLORS) + }; + } + + public setTheme(theme: ITheme): void { + if (theme.foreground) this.colors.foreground = theme.foreground; + if (theme.background) this.colors.background = theme.background; + if (theme.black) this.colors.ansi[0] = theme.black; + if (theme.red) this.colors.ansi[1] = theme.red; + if (theme.green) this.colors.ansi[2] = theme.green; + if (theme.yellow) this.colors.ansi[3] = theme.yellow; + if (theme.blue) this.colors.ansi[4] = theme.blue; + if (theme.magenta) this.colors.ansi[5] = theme.magenta; + if (theme.cyan) this.colors.ansi[6] = theme.cyan; + if (theme.white) this.colors.ansi[7] = theme.white; + if (theme.brightBlack) this.colors.ansi[8] = theme.brightBlack; + if (theme.brightRed) this.colors.ansi[9] = theme.brightRed; + if (theme.brightGreen) this.colors.ansi[10] = theme.brightGreen; + if (theme.brightYellow) this.colors.ansi[11] = theme.brightYellow; + if (theme.brightBlue) this.colors.ansi[12] = theme.brightBlue; + if (theme.brightMagenta) this.colors.ansi[13] = theme.brightMagenta; + if (theme.brightCyan) this.colors.ansi[14] = theme.brightCyan; + if (theme.brightWhite) this.colors.ansi[15] = theme.brightWhite; + } +} diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index 3ccf2ec2..bfec1d8e 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -1,11 +1,11 @@ -import { IDataRenderLayer } from './Interfaces'; +import { IDataRenderLayer, IColorSet } from './Interfaces'; import { IBuffer, ICharMeasure, ITerminal, ITerminalOptions } from '../Interfaces'; import { CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX } from '../Buffer'; -import { COLORS, COLOR_CODES } from './Color'; import { GridCache } from './GridCache'; import { FLAGS } from './Types'; import { BaseRenderLayer } from './BaseRenderLayer'; import { CharData } from '../Types'; +import { COLOR_CODES } from './ColorManager'; /** * The time between cursor blinks. @@ -17,8 +17,8 @@ export class CursorRenderLayer extends BaseRenderLayer implements IDataRenderLay private _cursorRenderers: {[key: string]: (terminal: ITerminal, x: number, y: number, charData: CharData) => void}; private _cursorBlinkStateManager: CursorBlinkStateManager; - constructor(container: HTMLElement, zIndex: number) { - super(container, 'cursor', zIndex); + constructor(container: HTMLElement, zIndex: number, colors: IColorSet) { + super(container, 'cursor', zIndex, colors); this._state = null; this._cursorRenderers = { 'bar': this._renderBarCursor.bind(this), @@ -98,7 +98,7 @@ export class CursorRenderLayer extends BaseRenderLayer implements IDataRenderLay const charData = terminal.buffer.lines.get(cursorY)[terminal.buffer.x]; this._ctx.save(); - this._ctx.fillStyle = COLORS[COLOR_CODES.WHITE]; + this._ctx.fillStyle = this.colors.ansi[COLOR_CODES.WHITE]; this._cursorRenderers[terminal.options.cursorStyle || 'block'](terminal, terminal.buffer.x, viewportRelativeCursorY, charData); this._ctx.restore(); this._state = [terminal.buffer.x, viewportRelativeCursorY]; @@ -157,7 +157,6 @@ class CursorBlinkStateManager { } public restartBlinkAnimation(terminal: ITerminal): void { - console.log('restartBlinkAnimation'); // Save a timestamp so that the restart can be done on the next interval this._animationTimeRestarted = Date.now(); // Force a cursor render to ensure it's visible and in the correct position @@ -176,7 +175,6 @@ class CursorBlinkStateManager { window.clearInterval(this._blinkInterval); } - console.log('restartInterval'); // Setup the initial timeout which will hide the cursor, this is done before // the regular interval is setup in order to support restarting the blink // animation in a lightweight way (without thrashing clearInterval and @@ -191,7 +189,6 @@ class CursorBlinkStateManager { return; } - console.log('timeout'); // Hide the cursor this.isCursorVisible = false; this._animationFrame = window.requestAnimationFrame(() => { @@ -201,14 +198,12 @@ class CursorBlinkStateManager { // Setup the blink interval this._blinkInterval = setInterval(() => { - console.log('interval'); // Adjust the animation time if it was restarted if (this._animationTimeRestarted) { // calc time diff // Make restart interval do a setTimeout initially? const time = BLINK_INTERVAL - (Date.now() - this._animationTimeRestarted); this._animationTimeRestarted = null; - console.log(' restart in ', time); this._restartInterval(time); return; } diff --git a/src/renderer/ForegroundRenderLayer.ts b/src/renderer/ForegroundRenderLayer.ts index 680f1abc..fa4ef800 100644 --- a/src/renderer/ForegroundRenderLayer.ts +++ b/src/renderer/ForegroundRenderLayer.ts @@ -1,7 +1,6 @@ -import { IDataRenderLayer } from './Interfaces'; +import { IDataRenderLayer, IColorSet } from './Interfaces'; import { IBuffer, ICharMeasure, ITerminal } from '../Interfaces'; import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from '../Buffer'; -import { COLORS } from './Color'; import { FLAGS } from './Types'; import { GridCache } from './GridCache'; import { CharData } from '../Types'; @@ -10,8 +9,8 @@ import { BaseRenderLayer } from './BaseRenderLayer'; export class ForegroundRenderLayer extends BaseRenderLayer implements IDataRenderLayer { private _state: GridCache; - constructor(container: HTMLElement, zIndex: number) { - super(container, 'fg', zIndex); + constructor(container: HTMLElement, zIndex: number, colors: IColorSet) { + super(container, 'fg', zIndex, colors); this._state = new GridCache(); } diff --git a/src/renderer/Interfaces.ts b/src/renderer/Interfaces.ts index 5da13109..4b8ce816 100644 --- a/src/renderer/Interfaces.ts +++ b/src/renderer/Interfaces.ts @@ -1,8 +1,9 @@ import { ITerminal, ITerminalOptions } from '../Interfaces'; export interface IRenderLayer { - onCursorMove(options: ITerminal): void; - onOptionsChanged(options: ITerminal): void; + onCursorMove(terminal: ITerminal): void; + onOptionsChanged(terminal: ITerminal): void; + onThemeChanged(terminal: ITerminal, colorSet: IColorSet): void; /** * Resize the render layer. @@ -28,3 +29,10 @@ export interface IDataRenderLayer extends IRenderLayer { export interface ISelectionRenderLayer extends IRenderLayer { render(terminal: ITerminal, start: [number, number], end: [number, number]): void; } + + +export interface IColorSet { + foreground: string; + background: string; + ansi: string[]; +} diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 7c6f6d40..653e2cba 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -2,7 +2,7 @@ * @license MIT */ -import { ITerminal } from '../Interfaces'; +import { ITerminal, ITheme } from '../Interfaces'; import { DomElementObjectPool } from '../utils/DomElementObjectPool'; import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from '../Buffer'; import { createBackgroundFillData } from './Canvas'; @@ -11,6 +11,8 @@ import { BackgroundRenderLayer } from './BackgroundRenderLayer'; import { ForegroundRenderLayer } from './ForegroundRenderLayer'; import { SelectionRenderLayer } from './SelectionRenderLayer'; import { CursorRenderLayer } from './CursorRenderLayer'; +import { ColorManager } from './ColorManager'; +import { BaseRenderLayer } from './BaseRenderLayer'; export class Renderer { /** A queue of the rows to be refreshed */ @@ -20,17 +22,37 @@ export class Renderer { private _dataRenderLayers: IDataRenderLayer[]; private _selectionRenderLayers: ISelectionRenderLayer[]; + private _colorManager: ColorManager; + constructor(private _terminal: ITerminal) { + this._colorManager = new ColorManager(); this._dataRenderLayers = [ - new BackgroundRenderLayer(this._terminal.element, 0), - new ForegroundRenderLayer(this._terminal.element, 2), - new CursorRenderLayer(this._terminal.element, 3) + new BackgroundRenderLayer(this._terminal.element, 0, this._colorManager.colors), + new ForegroundRenderLayer(this._terminal.element, 2, this._colorManager.colors), + new CursorRenderLayer(this._terminal.element, 3, this._colorManager.colors) ]; this._selectionRenderLayers = [ - new SelectionRenderLayer(this._terminal.element, 1) + new SelectionRenderLayer(this._terminal.element, 1, this._colorManager.colors) ]; } + public setTheme(theme: ITheme): void { + console.log('setTheme'); + this._colorManager.setTheme(theme); + // Clear layers and force a full render + for (let i = 0; i < this._dataRenderLayers.length; i++) { + this._dataRenderLayers[i].onThemeChanged(this._terminal, this._colorManager.colors); + this._dataRenderLayers[i].reset(this._terminal); + } + for (let i = 0; i < this._selectionRenderLayers.length; i++) { + this._selectionRenderLayers[i].onThemeChanged(this._terminal, this._colorManager.colors); + this._selectionRenderLayers[i].reset(this._terminal); + } + + // TODO: This is currently done for every single terminal, but it's static so it's wasting time + this._terminal.refresh(0, this._terminal.rows - 1); + } + public onResize(cols: number, rows: number): void { const width = this._terminal.charMeasure.width * this._terminal.cols; const height = this._terminal.charMeasure.height * this._terminal.rows; diff --git a/src/renderer/SelectionRenderLayer.ts b/src/renderer/SelectionRenderLayer.ts index daaf160d..4ec96764 100644 --- a/src/renderer/SelectionRenderLayer.ts +++ b/src/renderer/SelectionRenderLayer.ts @@ -1,4 +1,4 @@ -import { ISelectionRenderLayer } from './Interfaces'; +import { ISelectionRenderLayer, IColorSet } from './Interfaces'; import { IBuffer, ICharMeasure, ITerminal } from '../Interfaces'; import { CHAR_DATA_ATTR_INDEX } from '../Buffer'; import { GridCache } from './GridCache'; @@ -8,8 +8,8 @@ import { BaseRenderLayer } from './BaseRenderLayer'; export class SelectionRenderLayer extends BaseRenderLayer implements ISelectionRenderLayer { private _state: {start: [number, number], end: [number, number]}; - constructor(container: HTMLElement, zIndex: number) { - super(container, 'selection', zIndex); + constructor(container: HTMLElement, zIndex: number, colors: IColorSet) { + super(container, 'selection', zIndex, colors); this._state = { start: null, end: null diff --git a/src/renderer/Types.ts b/src/renderer/Types.ts index 621db0bd..09e74439 100644 --- a/src/renderer/Types.ts +++ b/src/renderer/Types.ts @@ -8,8 +8,3 @@ export enum FLAGS { INVERSE = 8, INVISIBLE = 16 }; - -export type Point = { - x: number, - y: number -}; diff --git a/src/utils/CharAtlas.ts b/src/utils/CharAtlas.ts new file mode 100644 index 00000000..a375d468 --- /dev/null +++ b/src/utils/CharAtlas.ts @@ -0,0 +1,66 @@ +import { ITerminal } from '../Interfaces'; +import { IColorSet } from '../renderer/Interfaces'; + +export function acquireCharAtlas(terminal: ITerminal, colors: IColorSet): Promise { + const scaledCharWidth = terminal.charMeasure.width * window.devicePixelRatio; + const scaledCharHeight = terminal.charMeasure.height * window.devicePixelRatio; + + // TODO: Check to see if the atlas already exists in a cache + + return generator.generate(scaledCharWidth, scaledCharHeight, terminal.options.fontSize, terminal.options.fontFamily, colors.foreground, colors.ansi); +} + +export function releaseCharAtlas(terminal: ITerminal): void { + // TODO: Release the char atlas if it's no longer needed +} + +class CharAtlasGenerator { + private _canvas: HTMLCanvasElement; + private _ctx: CanvasRenderingContext2D; + + constructor() { + this._canvas = document.createElement('canvas'); + this._ctx = this._canvas.getContext('2d'); + this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); + } + + public generate(scaledCharWidth: number, scaledCharHeight: number, fontSize: number, fontFamily: string, foreground: string, ansiColors: string[]): Promise { + this._canvas.width = 255 * scaledCharWidth; + this._canvas.height = (/*default*/1 + /*0-15*/16) * scaledCharHeight; + + this._ctx.save(); + this._ctx.fillStyle = foreground; + this._ctx.font = `${fontSize * window.devicePixelRatio}px ${fontFamily}`; + this._ctx.textBaseline = 'top'; + + // Default color + for (let i = 0; i < 256; i++) { + this._ctx.fillText(String.fromCharCode(i), i * scaledCharWidth, 0); + } + + // Colors 0-15 + for (let colorIndex = 0; colorIndex < 16; colorIndex++) { + // colors 8-15 are bold + if (colorIndex === 8) { + this._ctx.font = `bold ${this._ctx.font}`; + } + const y = (colorIndex + 1) * scaledCharHeight; + // Clear rectangle as some fonts seem to draw over the bottom boundary + this._ctx.clearRect(0, y, this._canvas.width, scaledCharHeight); + // Draw ascii characters + for (let i = 0; i < 256; i++) { + this._ctx.fillStyle = ansiColors[colorIndex]; + this._ctx.fillText(String.fromCharCode(i), i * scaledCharWidth, y); + } + } + this._ctx.restore(); + + const charAtlasImageData = this._ctx.getImageData(0, 0, this._canvas.width, this._canvas.height); + const promise = window.createImageBitmap(charAtlasImageData); + // Clear the rect while the promise is in progress + this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); + return promise; + } +} + +const generator = new CharAtlasGenerator(); From bc3bdacdaa3b220e400c9a597e5f59303e902196 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 1 Sep 2017 16:43:45 -0700 Subject: [PATCH 143/378] Add a char atlas cache to allow different styles+reuse across terminals --- src/renderer/BaseRenderLayer.ts | 8 +--- src/utils/CharAtlas.ts | 85 +++++++++++++++++++++++++++++++-- src/utils/TestUtils.test.ts | 3 ++ 3 files changed, 85 insertions(+), 11 deletions(-) diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index d131bee0..e996a2a8 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -32,9 +32,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { public onThemeChanged(terminal: ITerminal, colorSet: IColorSet): void { this._charAtlas = null; - acquireCharAtlas(terminal, this.colors).then(bitmap => { - this._charAtlas = bitmap; - }); + acquireCharAtlas(terminal, this.colors).then(bitmap => this._charAtlas = bitmap); } public resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void { @@ -46,9 +44,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._canvas.style.height = `${canvasHeight}px`; if (charSizeChanged) { - acquireCharAtlas(terminal, this.colors).then(bitmap => { - this._charAtlas = bitmap; - }); + acquireCharAtlas(terminal, this.colors).then(bitmap => this._charAtlas = bitmap); } } diff --git a/src/utils/CharAtlas.ts b/src/utils/CharAtlas.ts index a375d468..18398efc 100644 --- a/src/utils/CharAtlas.ts +++ b/src/utils/CharAtlas.ts @@ -1,17 +1,92 @@ -import { ITerminal } from '../Interfaces'; +import { ITerminal, ITheme } from '../Interfaces'; import { IColorSet } from '../renderer/Interfaces'; +interface ICharAtlasConfig { + fontSize: number; + fontFamily: string; + scaledCharWidth: number; + scaledCharHeight: number; + colors: IColorSet; +} + +interface ICharAtlasCacheEntry { + bitmap: Promise; + config: ICharAtlasConfig; + ownedBy: ITerminal[]; +} + +let charAtlasCache: ICharAtlasCacheEntry[] = []; + export function acquireCharAtlas(terminal: ITerminal, colors: IColorSet): Promise { const scaledCharWidth = terminal.charMeasure.width * window.devicePixelRatio; const scaledCharHeight = terminal.charMeasure.height * window.devicePixelRatio; + const newConfig = generateConfig(scaledCharWidth, scaledCharHeight, terminal, colors); - // TODO: Check to see if the atlas already exists in a cache + // Check to see if the terminal already owns this config + for (let i = 0; i < charAtlasCache.length; i++) { + const entry = charAtlasCache[i]; + const ownedByIndex = entry.ownedBy.indexOf(terminal); + if (ownedByIndex >= 0) { + if (configEquals(entry.config, newConfig)) { + return entry.bitmap; + } else { + // The configs differ, release the terminal from the entry + if (entry.ownedBy.length === 1) { + charAtlasCache.splice(i, 1); + } else { + entry.ownedBy.splice(ownedByIndex, 1); + } + break; + } + } + } - return generator.generate(scaledCharWidth, scaledCharHeight, terminal.options.fontSize, terminal.options.fontFamily, colors.foreground, colors.ansi); + // Try match a char atlas from the cache + for (let i = 0; i < charAtlasCache.length; i++) { + const entry = charAtlasCache[i]; + if (configEquals(entry.config, newConfig)) { + // Add the terminal to the cache entry and return + entry.ownedBy.push(terminal); + return entry.bitmap; + } + } + + const newEntry: ICharAtlasCacheEntry = { + bitmap: generator.generate(scaledCharWidth, scaledCharHeight, terminal.options.fontSize, terminal.options.fontFamily, colors.foreground, colors.ansi), + config: newConfig, + ownedBy: [terminal] + }; + charAtlasCache.push(newEntry); + return newEntry.bitmap; } -export function releaseCharAtlas(terminal: ITerminal): void { - // TODO: Release the char atlas if it's no longer needed +function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: ITerminal, colors: IColorSet): ICharAtlasConfig { + const clonedColors = { + foreground: colors.foreground, + background: colors.background, + ansi: colors.ansi.slice(0, 16) + }; + return { + scaledCharWidth, + scaledCharHeight, + fontFamily: terminal.options.fontFamily, + fontSize: terminal.options.fontSize, + colors: clonedColors + }; +} + +function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean { + for (let i = 0; i < a.colors.ansi.length; i++) { + if (a.colors.ansi[i] !== b.colors.ansi[i]) { + return false; + } + } + return a.fontFamily === b.fontFamily && + a.fontSize === b.fontSize && + a.scaledCharWidth === b.scaledCharWidth && + a.scaledCharHeight === b.scaledCharHeight && + a.colors.foreground === b.colors.foreground && + a.colors.background === b.colors.background; } class CharAtlasGenerator { diff --git a/src/utils/TestUtils.test.ts b/src/utils/TestUtils.test.ts index ae83aed4..3a66d49d 100644 --- a/src/utils/TestUtils.test.ts +++ b/src/utils/TestUtils.test.ts @@ -52,6 +52,9 @@ export class MockTerminal implements ITerminal { showCursor(): void { throw new Error('Method not implemented.'); } + refresh(start: number, end: number): void { + throw new Error('Method not implemented.'); + } blankLine(cur?: boolean, isWrapped?: boolean, cols?: number): LineData { const line: LineData = []; cols = cols || this.cols; From de4fcb9241e23b31d2a7bcc2f0cd6d8277d509c5 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 1 Sep 2017 16:54:26 -0700 Subject: [PATCH 144/378] Merge render layer interface types --- src/Terminal.ts | 5 +-- src/renderer/BackgroundRenderLayer.ts | 6 +-- src/renderer/BaseRenderLayer.ts | 2 + src/renderer/CursorRenderLayer.ts | 6 +-- src/renderer/ForegroundRenderLayer.ts | 6 +-- src/renderer/Interfaces.ts | 16 +------ src/renderer/Renderer.ts | 63 +++++++-------------------- src/renderer/SelectionRenderLayer.ts | 6 +-- 8 files changed, 32 insertions(+), 78 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index a6633235..13f38860 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -761,10 +761,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.viewport = new Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasure); this.renderer = new Renderer(this); - this.on('cursormove', () => { - console.log('cursormove fired'); - this.renderer.onCursorMove(); - }); + this.on('cursormove', () => this.renderer.onCursorMove()); this.on('resize', () => this.renderer.onResize(this.cols, this.rows)); this.charMeasure.on('charsizechanged', () => { this.renderer.onCharSizeChanged(this.charMeasure.width, this.charMeasure.height); diff --git a/src/renderer/BackgroundRenderLayer.ts b/src/renderer/BackgroundRenderLayer.ts index cf122cc2..271dc2ac 100644 --- a/src/renderer/BackgroundRenderLayer.ts +++ b/src/renderer/BackgroundRenderLayer.ts @@ -1,11 +1,11 @@ -import { IDataRenderLayer, IColorSet } from './Interfaces'; +import { IColorSet } from './Interfaces'; import { IBuffer, ICharMeasure, ITerminal } from '../Interfaces'; import { CHAR_DATA_ATTR_INDEX } from '../Buffer'; import { GridCache } from './GridCache'; import { FLAGS } from './Types'; import { BaseRenderLayer } from './BaseRenderLayer'; -export class BackgroundRenderLayer extends BaseRenderLayer implements IDataRenderLayer { +export class BackgroundRenderLayer extends BaseRenderLayer { private _state: GridCache; constructor(container: HTMLElement, zIndex: number, colors: IColorSet) { @@ -23,7 +23,7 @@ export class BackgroundRenderLayer extends BaseRenderLayer implements IDataRende this.clearAll(); } - public render(terminal: ITerminal, startRow: number, endRow: number): void { + public onGridChanged(terminal: ITerminal, startRow: number, endRow: number): void { for (let y = startRow; y <= endRow; y++) { let row = y + terminal.buffer.ydisp; let line = terminal.buffer.lines.get(row); diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index e996a2a8..20425e41 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -29,6 +29,8 @@ export abstract class BaseRenderLayer implements IRenderLayer { // TODO: Should this do anything? public onOptionsChanged(terminal: ITerminal): void {} public onCursorMove(terminal: ITerminal): void {} + public onGridChanged(terminal: ITerminal, startRow: number, endRow: number): void {} + public onSelectionChanged(terminal: ITerminal, start: [number, number], end: [number, number]): void {} public onThemeChanged(terminal: ITerminal, colorSet: IColorSet): void { this._charAtlas = null; diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index bfec1d8e..1e5cf647 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -1,4 +1,4 @@ -import { IDataRenderLayer, IColorSet } from './Interfaces'; +import { IColorSet } from './Interfaces'; import { IBuffer, ICharMeasure, ITerminal, ITerminalOptions } from '../Interfaces'; import { CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX } from '../Buffer'; import { GridCache } from './GridCache'; @@ -12,7 +12,7 @@ import { COLOR_CODES } from './ColorManager'; */ const BLINK_INTERVAL = 600; -export class CursorRenderLayer extends BaseRenderLayer implements IDataRenderLayer { +export class CursorRenderLayer extends BaseRenderLayer { private _state: [number, number]; private _cursorRenderers: {[key: string]: (terminal: ITerminal, x: number, y: number, charData: CharData) => void}; private _cursorBlinkStateManager: CursorBlinkStateManager; @@ -62,7 +62,7 @@ export class CursorRenderLayer extends BaseRenderLayer implements IDataRenderLay } } - public render(terminal: ITerminal, startRow: number, endRow: number): void { + public onGridChanged(terminal: ITerminal, startRow: number, endRow: number): void { // Only render if the animation frame is not active if (!this._cursorBlinkStateManager) { this._render(terminal, false); diff --git a/src/renderer/ForegroundRenderLayer.ts b/src/renderer/ForegroundRenderLayer.ts index fa4ef800..5a1d41f6 100644 --- a/src/renderer/ForegroundRenderLayer.ts +++ b/src/renderer/ForegroundRenderLayer.ts @@ -1,4 +1,4 @@ -import { IDataRenderLayer, IColorSet } from './Interfaces'; +import { IColorSet } from './Interfaces'; import { IBuffer, ICharMeasure, ITerminal } from '../Interfaces'; import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from '../Buffer'; import { FLAGS } from './Types'; @@ -6,7 +6,7 @@ import { GridCache } from './GridCache'; import { CharData } from '../Types'; import { BaseRenderLayer } from './BaseRenderLayer'; -export class ForegroundRenderLayer extends BaseRenderLayer implements IDataRenderLayer { +export class ForegroundRenderLayer extends BaseRenderLayer { private _state: GridCache; constructor(container: HTMLElement, zIndex: number, colors: IColorSet) { @@ -24,7 +24,7 @@ export class ForegroundRenderLayer extends BaseRenderLayer implements IDataRende this.clearAll(); } - public render(terminal: ITerminal, startRow: number, endRow: number): void { + public onGridChanged(terminal: ITerminal, startRow: number, endRow: number): void { // TODO: Ensure that the render is eventually performed // Don't bother render until the atlas bitmap is ready // TODO: Move this to BaseRenderLayer? diff --git a/src/renderer/Interfaces.ts b/src/renderer/Interfaces.ts index 4b8ce816..5626f13d 100644 --- a/src/renderer/Interfaces.ts +++ b/src/renderer/Interfaces.ts @@ -4,6 +4,8 @@ export interface IRenderLayer { onCursorMove(terminal: ITerminal): void; onOptionsChanged(terminal: ITerminal): void; onThemeChanged(terminal: ITerminal, colorSet: IColorSet): void; + onGridChanged(terminal: ITerminal, startRow: number, endRow: number): void; + onSelectionChanged(terminal: ITerminal, start: [number, number], end: [number, number]): void; /** * Resize the render layer. @@ -16,20 +18,6 @@ export interface IRenderLayer { reset(terminal: ITerminal): void; } -/** - * A render layer that renders when there is a data change. - */ -export interface IDataRenderLayer extends IRenderLayer { - render(terminal: ITerminal, startRow: number, endRow: number): void; -} - -/** - * A render layer that renders when there is a selection change. - */ -export interface ISelectionRenderLayer extends IRenderLayer { - render(terminal: ITerminal, start: [number, number], end: [number, number]): void; -} - export interface IColorSet { foreground: string; diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 653e2cba..96e899f6 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -6,48 +6,41 @@ import { ITerminal, ITheme } from '../Interfaces'; import { DomElementObjectPool } from '../utils/DomElementObjectPool'; import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from '../Buffer'; import { createBackgroundFillData } from './Canvas'; -import { IDataRenderLayer, ISelectionRenderLayer } from './Interfaces'; import { BackgroundRenderLayer } from './BackgroundRenderLayer'; import { ForegroundRenderLayer } from './ForegroundRenderLayer'; import { SelectionRenderLayer } from './SelectionRenderLayer'; import { CursorRenderLayer } from './CursorRenderLayer'; import { ColorManager } from './ColorManager'; import { BaseRenderLayer } from './BaseRenderLayer'; +import { IRenderLayer } from './Interfaces'; export class Renderer { /** A queue of the rows to be refreshed */ private _refreshRowsQueue: {start: number, end: number}[] = []; private _refreshAnimationFrame = null; - private _dataRenderLayers: IDataRenderLayer[]; - private _selectionRenderLayers: ISelectionRenderLayer[]; + private _renderLayers: IRenderLayer[]; private _colorManager: ColorManager; constructor(private _terminal: ITerminal) { this._colorManager = new ColorManager(); - this._dataRenderLayers = [ + this._renderLayers = [ new BackgroundRenderLayer(this._terminal.element, 0, this._colorManager.colors), + new SelectionRenderLayer(this._terminal.element, 1, this._colorManager.colors), new ForegroundRenderLayer(this._terminal.element, 2, this._colorManager.colors), new CursorRenderLayer(this._terminal.element, 3, this._colorManager.colors) ]; - this._selectionRenderLayers = [ - new SelectionRenderLayer(this._terminal.element, 1, this._colorManager.colors) - ]; } public setTheme(theme: ITheme): void { console.log('setTheme'); this._colorManager.setTheme(theme); // Clear layers and force a full render - for (let i = 0; i < this._dataRenderLayers.length; i++) { - this._dataRenderLayers[i].onThemeChanged(this._terminal, this._colorManager.colors); - this._dataRenderLayers[i].reset(this._terminal); - } - for (let i = 0; i < this._selectionRenderLayers.length; i++) { - this._selectionRenderLayers[i].onThemeChanged(this._terminal, this._colorManager.colors); - this._selectionRenderLayers[i].reset(this._terminal); - } + this._renderLayers.forEach(l => { + l.onThemeChanged(this._terminal, this._colorManager.colors); + l.reset(this._terminal); + }); // TODO: This is currently done for every single terminal, but it's static so it's wasting time this._terminal.refresh(0, this._terminal.rows - 1); @@ -56,53 +49,29 @@ export class Renderer { public onResize(cols: number, rows: number): void { const width = this._terminal.charMeasure.width * this._terminal.cols; const height = this._terminal.charMeasure.height * this._terminal.rows; - for (let i = 0; i < this._dataRenderLayers.length; i++) { - this._dataRenderLayers[i].resize(this._terminal, width, height, false); - } + this._renderLayers.forEach(l => l.resize(this._terminal, width, height, false)); } public onCharSizeChanged(charWidth: number, charHeight: number): void { const width = charWidth * this._terminal.cols; const height = charHeight * this._terminal.rows; - for (let i = 0; i < this._dataRenderLayers.length; i++) { - this._dataRenderLayers[i].resize(this._terminal, width, height, true); - } - for (let i = 0; i < this._selectionRenderLayers.length; i++) { - this._selectionRenderLayers[i].resize(this._terminal, width, height, true); - } + this._renderLayers.forEach(l => l.resize(this._terminal, width, height, true)); } public onSelectionChanged(start: [number, number], end: [number, number]): void { - for (let i = 0; i < this._selectionRenderLayers.length; i++) { - this._selectionRenderLayers[i].render(this._terminal, start, end); - } + this._renderLayers.forEach(l => l.onSelectionChanged(this._terminal, start, end)); } public onCursorMove(): void { - for (let i = 0; i < this._dataRenderLayers.length; i++) { - this._dataRenderLayers[i].onCursorMove(this._terminal); - } - for (let i = 0; i < this._selectionRenderLayers.length; i++) { - this._selectionRenderLayers[i].onCursorMove(this._terminal); - } + this._renderLayers.forEach(l => l.onCursorMove(this._terminal)); } public onOptionsChanged(): void { - for (let i = 0; i < this._dataRenderLayers.length; i++) { - this._dataRenderLayers[i].onOptionsChanged(this._terminal); - } - for (let i = 0; i < this._selectionRenderLayers.length; i++) { - this._selectionRenderLayers[i].onOptionsChanged(this._terminal); - } + this._renderLayers.forEach(l => l.onOptionsChanged(this._terminal)); } public clear(): void { - for (let i = 0; i < this._dataRenderLayers.length; i++) { - this._dataRenderLayers[i].reset(this._terminal); - } - for (let i = 0; i < this._selectionRenderLayers.length; i++) { - this._selectionRenderLayers[i].reset(this._terminal); - } + this._renderLayers.forEach(l => l.reset(this._terminal)); } /** @@ -146,9 +115,7 @@ export class Renderer { this._refreshAnimationFrame = null; // Render - for (let i = 0; i < this._dataRenderLayers.length; i++) { - this._dataRenderLayers[i].render(this._terminal, start, end); - } + this._renderLayers.forEach(l => l.onGridChanged(this._terminal, start, end)); this._terminal.emit('refresh', {start, end}); } } diff --git a/src/renderer/SelectionRenderLayer.ts b/src/renderer/SelectionRenderLayer.ts index 4ec96764..1dcb8ab6 100644 --- a/src/renderer/SelectionRenderLayer.ts +++ b/src/renderer/SelectionRenderLayer.ts @@ -1,11 +1,11 @@ -import { ISelectionRenderLayer, IColorSet } from './Interfaces'; +import { IColorSet } from './Interfaces'; import { IBuffer, ICharMeasure, ITerminal } from '../Interfaces'; import { CHAR_DATA_ATTR_INDEX } from '../Buffer'; import { GridCache } from './GridCache'; import { FLAGS } from './Types'; import { BaseRenderLayer } from './BaseRenderLayer'; -export class SelectionRenderLayer extends BaseRenderLayer implements ISelectionRenderLayer { +export class SelectionRenderLayer extends BaseRenderLayer { private _state: {start: [number, number], end: [number, number]}; constructor(container: HTMLElement, zIndex: number, colors: IColorSet) { @@ -26,7 +26,7 @@ export class SelectionRenderLayer extends BaseRenderLayer implements ISelectionR } } - public render(terminal: ITerminal, start: [number, number], end: [number, number]): void { + public onSelectionChanged(terminal: ITerminal, start: [number, number], end: [number, number]): void { // Selection has not changed if (this._state.start === start || this._state.end === end) { return; From 2c9dbc6191158e09e46f0633acf1683a7b501433 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 1 Sep 2017 16:58:02 -0700 Subject: [PATCH 145/378] Add some jsdoc --- src/renderer/CursorRenderLayer.ts | 1 - src/renderer/Interfaces.ts | 20 ++++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index 1e5cf647..1533c4f2 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -38,7 +38,6 @@ export class CursorRenderLayer extends BaseRenderLayer { } public onOptionsChanged(terminal: ITerminal): void { - super.onOptionsChanged(terminal); if (terminal.options.cursorBlink) { if (!this._cursorBlinkStateManager) { this._cursorBlinkStateManager = new CursorBlinkStateManager(terminal, () => { diff --git a/src/renderer/Interfaces.ts b/src/renderer/Interfaces.ts index 5626f13d..45822e65 100644 --- a/src/renderer/Interfaces.ts +++ b/src/renderer/Interfaces.ts @@ -1,10 +1,30 @@ import { ITerminal, ITerminalOptions } from '../Interfaces'; export interface IRenderLayer { + /** + * Called when the cursor is moved. + */ onCursorMove(terminal: ITerminal): void; + + /** + * Called when options change. + */ onOptionsChanged(terminal: ITerminal): void; + + /** + * Called when the theme changes. + */ onThemeChanged(terminal: ITerminal, colorSet: IColorSet): void; + + /** + * Called when the data in the grid has changed (or needs to be rendered + * again). + */ onGridChanged(terminal: ITerminal, startRow: number, endRow: number): void; + + /** + * Calls when the selection changes. + */ onSelectionChanged(terminal: ITerminal, start: [number, number], end: [number, number]): void; /** From bf9533327518bf162da192c82ef8963db247bdac Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 1 Sep 2017 20:44:41 -0700 Subject: [PATCH 146/378] Fix the fit addon --- demo/main.js | 33 +++++++++++++-------------- src/addons/fit/fit.js | 33 +++++++++++---------------- src/renderer/BackgroundRenderLayer.ts | 4 ++++ src/renderer/ForegroundRenderLayer.ts | 5 ++++ src/renderer/GridCache.ts | 2 +- 5 files changed, 39 insertions(+), 38 deletions(-) diff --git a/demo/main.js b/demo/main.js index cb346de2..28840709 100644 --- a/demo/main.js +++ b/demo/main.js @@ -92,27 +92,26 @@ function createTerminal() { term.open(terminalContainer); term.fit(); - var initialGeometry = term.proposeGeometry(), - cols = initialGeometry.cols, - rows = initialGeometry.rows; + // fit is called within a setTimeout, cols and rows need this. + setTimeout(() => { + colsElement.value = term.cols; + rowsElement.value = term.rows; - colsElement.value = cols; - rowsElement.value = rows; + fetch('/terminals?cols=' + cols + '&rows=' + rows, {method: 'POST'}).then(function (res) { - fetch('/terminals?cols=' + cols + '&rows=' + rows, {method: 'POST'}).then(function (res) { + charWidth = Math.ceil(term.element.offsetWidth / cols); + charHeight = Math.ceil(term.element.offsetHeight / rows); - charWidth = Math.ceil(term.element.offsetWidth / cols); - charHeight = Math.ceil(term.element.offsetHeight / rows); - - res.text().then(function (pid) { - window.pid = pid; - socketURL += pid; - socket = new WebSocket(socketURL); - socket.onopen = runRealTerminal; - socket.onclose = runFakeTerminal; - socket.onerror = runFakeTerminal; + res.text().then(function (pid) { + window.pid = pid; + socketURL += pid; + socket = new WebSocket(socketURL); + socket.onopen = runRealTerminal; + socket.onclose = runFakeTerminal; + socket.onerror = runFakeTerminal; + }); }); - }); + }, 0); } function runRealTerminal() { diff --git a/src/addons/fit/fit.js b/src/addons/fit/fit.js index da66802d..1e46932c 100644 --- a/src/addons/fit/fit.js +++ b/src/addons/fit/fit.js @@ -45,33 +45,26 @@ availableWidth = parentElementWidth - elementPaddingHor, container = term.rowContainer, subjectRow = term.rowContainer.firstElementChild, - contentBuffer = subjectRow.innerHTML, - characterHeight, - rows, - characterWidth, - cols, - geometry; + contentBuffer = subjectRow.innerHTML; - subjectRow.style.display = 'inline'; - subjectRow.innerHTML = 'W'; // Common character for measuring width, although on monospace - characterWidth = subjectRow.getBoundingClientRect().width; - subjectRow.style.display = ''; // Revert style before calculating height, since they differ. - characterHeight = subjectRow.getBoundingClientRect().height; - subjectRow.innerHTML = contentBuffer; + var geometry = { + cols: parseInt(availableWidth / term.charMeasure.width, 10), + rows: parseInt(availableHeight / term.charMeasure.height, 10) + }; - rows = parseInt(availableHeight / characterHeight); - cols = parseInt(availableWidth / characterWidth); - - geometry = {cols: cols, rows: rows}; return geometry; }; exports.fit = function (term) { - var geometry = exports.proposeGeometry(term); + // Wrap fit in a setTimeout as charMeasure needs time to get initialized + // after calling Terminal.open + setTimeout(() => { + var geometry = exports.proposeGeometry(term); - if (geometry) { - term.resize(geometry.cols, geometry.rows); - } + if (geometry) { + term.resize(geometry.cols, geometry.rows); + } + }, 0); }; Terminal.prototype.proposeGeometry = function () { diff --git a/src/renderer/BackgroundRenderLayer.ts b/src/renderer/BackgroundRenderLayer.ts index 271dc2ac..33550af0 100644 --- a/src/renderer/BackgroundRenderLayer.ts +++ b/src/renderer/BackgroundRenderLayer.ts @@ -24,6 +24,10 @@ export class BackgroundRenderLayer extends BaseRenderLayer { } public onGridChanged(terminal: ITerminal, startRow: number, endRow: number): void { + // Resize has not been called yet + if (this._state.cache.length === 0) { + return; + } for (let y = startRow; y <= endRow; y++) { let row = y + terminal.buffer.ydisp; let line = terminal.buffer.lines.get(row); diff --git a/src/renderer/ForegroundRenderLayer.ts b/src/renderer/ForegroundRenderLayer.ts index 5a1d41f6..9df1f49b 100644 --- a/src/renderer/ForegroundRenderLayer.ts +++ b/src/renderer/ForegroundRenderLayer.ts @@ -32,6 +32,11 @@ export class ForegroundRenderLayer extends BaseRenderLayer { // return; // } + // Resize has not been called yet + if (this._state.cache.length === 0) { + return; + } + for (let y = startRow; y <= endRow; y++) { const row = y + terminal.buffer.ydisp; const line = terminal.buffer.lines.get(row); diff --git a/src/renderer/GridCache.ts b/src/renderer/GridCache.ts index a4657fcb..56294ddc 100644 --- a/src/renderer/GridCache.ts +++ b/src/renderer/GridCache.ts @@ -18,7 +18,7 @@ export class GridCache { this.cache.length = width; } - public clear() { + public clear(): void { for (let x = 0; x < this.cache.length; x++) { for (let y = 0; y < this.cache[x].length; y++) { this.cache[x][y] = null; From 1f444b19130410677d1a92f7fa19ea4efac5ae5f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 1 Sep 2017 20:51:18 -0700 Subject: [PATCH 147/378] Support invisible attr --- src/renderer/BaseRenderLayer.ts | 2 +- src/renderer/CursorRenderLayer.ts | 2 +- src/renderer/ForegroundRenderLayer.ts | 7 ++++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 20425e41..207f87ee 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -80,7 +80,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._ctx.clearRect(startCol * this.scaledCharWidth, startRow * this.scaledCharHeight, colWidth * this.scaledCharWidth, colHeight * this.scaledCharHeight); } - protected drawChar(terminal: ITerminal, char: string, code: number, fg: number, x: number, y: number): void { + protected drawChar(terminal: ITerminal, char: string, code: number, x: number, y: number, fg: number): void { let colorIndex = 0; if (fg < 256) { colorIndex = fg + 1; diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index 1533c4f2..cd11f1dc 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -116,7 +116,7 @@ export class CursorRenderLayer extends BaseRenderLayer { private _renderBlockCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void { this.fillCells(x, y, 1, 1); - this.drawChar(terminal, charData[CHAR_DATA_CHAR_INDEX], charData[CHAR_DATA_CODE_INDEX], COLOR_CODES.BLACK, x, y); + this.drawChar(terminal, charData[CHAR_DATA_CHAR_INDEX], charData[CHAR_DATA_CODE_INDEX], x, y, COLOR_CODES.BLACK); } private _renderUnderlineCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void { diff --git a/src/renderer/ForegroundRenderLayer.ts b/src/renderer/ForegroundRenderLayer.ts index 9df1f49b..53660fa0 100644 --- a/src/renderer/ForegroundRenderLayer.ts +++ b/src/renderer/ForegroundRenderLayer.ts @@ -61,13 +61,14 @@ export class ForegroundRenderLayer extends BaseRenderLayer { } this._state.cache[x][y] = charData; + const flags = attr >> 18; + // Skip rendering if the character is invisible - if (!code || code === 32 /*' '*/) { + if (!code || code === 32 /*' '*/ || (flags & FLAGS.INVISIBLE)) { continue; } let fg = (attr >> 9) & 0x1ff; - const flags = attr >> 18; // If inverse flag is on, the foreground should become the background. if (flags & FLAGS.INVERSE) { @@ -87,7 +88,7 @@ export class ForegroundRenderLayer extends BaseRenderLayer { } } - this.drawChar(terminal, char, code, fg, x, y); + this.drawChar(terminal, char, code, x, y, fg); this._ctx.restore(); } } From 70764a74afa893e6cc91bb5cd813c38e6a79437f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 1 Sep 2017 21:09:07 -0700 Subject: [PATCH 148/378] Use the proper bg/fg colors for inverse attr --- src/renderer/BackgroundRenderLayer.ts | 7 +++---- src/renderer/BaseRenderLayer.ts | 10 +++++++--- src/renderer/ForegroundRenderLayer.ts | 6 +++--- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/renderer/BackgroundRenderLayer.ts b/src/renderer/BackgroundRenderLayer.ts index 33550af0..729a8ec5 100644 --- a/src/renderer/BackgroundRenderLayer.ts +++ b/src/renderer/BackgroundRenderLayer.ts @@ -3,7 +3,7 @@ import { IBuffer, ICharMeasure, ITerminal } from '../Interfaces'; import { CHAR_DATA_ATTR_INDEX } from '../Buffer'; import { GridCache } from './GridCache'; import { FLAGS } from './Types'; -import { BaseRenderLayer } from './BaseRenderLayer'; +import { BaseRenderLayer, INVERTED_DEFAULT_COLOR } from './BaseRenderLayer'; export class BackgroundRenderLayer extends BaseRenderLayer { private _state: GridCache; @@ -39,9 +39,8 @@ export class BackgroundRenderLayer extends BaseRenderLayer { // If inverse flag is on, the background should become the foreground. if (flags & FLAGS.INVERSE) { bg = (attr >> 9) & 0x1ff; - // TODO: Is this case still needed if (bg === 257) { - bg = 15; + bg = INVERTED_DEFAULT_COLOR; } } @@ -50,7 +49,7 @@ export class BackgroundRenderLayer extends BaseRenderLayer { if (needsRefresh) { if (bg < 256) { this._ctx.save(); - this._ctx.fillStyle = this.colors.ansi[bg]; + this._ctx.fillStyle = (bg === INVERTED_DEFAULT_COLOR ? this.colors.foreground : this.colors.ansi[bg]); this.fillCells(x, y, 1, 1); this._ctx.restore(); this._state.cache[x][y] = bg; diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 207f87ee..858f15b8 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -2,6 +2,8 @@ import { IRenderLayer, IColorSet } from './Interfaces'; import { ITerminal, ITerminalOptions } from '../Interfaces'; import { acquireCharAtlas } from '../utils/CharAtlas'; +export const INVERTED_DEFAULT_COLOR = -1; + export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; protected _ctx: CanvasRenderingContext2D; @@ -102,11 +104,13 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._ctx.font = `${terminal.options.fontSize * window.devicePixelRatio}px ${terminal.options.fontFamily}`; this._ctx.textBaseline = 'top'; - // 256 color support - if (fg < 256) { + if (fg === INVERTED_DEFAULT_COLOR) { + this._ctx.fillStyle = this.colors.background; + } else if (fg < 256) { + // 256 color support this._ctx.fillStyle = this.colors.ansi[fg]; } else { - this._ctx.fillStyle = '#ffffff'; + this._ctx.fillStyle = this.colors.foreground; } // TODO: Do we care about width for rendering wide chars? diff --git a/src/renderer/ForegroundRenderLayer.ts b/src/renderer/ForegroundRenderLayer.ts index 53660fa0..7d1cec1f 100644 --- a/src/renderer/ForegroundRenderLayer.ts +++ b/src/renderer/ForegroundRenderLayer.ts @@ -4,7 +4,7 @@ import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_ import { FLAGS } from './Types'; import { GridCache } from './GridCache'; import { CharData } from '../Types'; -import { BaseRenderLayer } from './BaseRenderLayer'; +import { BaseRenderLayer, INVERTED_DEFAULT_COLOR } from './BaseRenderLayer'; export class ForegroundRenderLayer extends BaseRenderLayer { private _state: GridCache; @@ -74,8 +74,8 @@ export class ForegroundRenderLayer extends BaseRenderLayer { if (flags & FLAGS.INVERSE) { fg = attr & 0x1ff; // TODO: Is this case still needed - if (fg === 257) { - fg = 0; + if (fg === 256) { + fg = INVERTED_DEFAULT_COLOR; } } From b0653fec6823d7a425c40a4c3a86b6f5a0aa7061 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 1 Sep 2017 21:24:17 -0700 Subject: [PATCH 149/378] Support underline --- src/renderer/BaseRenderLayer.ts | 3 +-- src/renderer/ForegroundRenderLayer.ts | 13 +++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 858f15b8..523edbda 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -82,7 +82,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._ctx.clearRect(startCol * this.scaledCharWidth, startRow * this.scaledCharHeight, colWidth * this.scaledCharWidth, colHeight * this.scaledCharHeight); } - protected drawChar(terminal: ITerminal, char: string, code: number, x: number, y: number, fg: number): void { + protected drawChar(terminal: ITerminal, char: string, code: number, x: number, y: number, fg: number, underline: boolean = false): void { let colorIndex = 0; if (fg < 256) { colorIndex = fg + 1; @@ -113,7 +113,6 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._ctx.fillStyle = this.colors.foreground; } - // TODO: Do we care about width for rendering wide chars? this._ctx.fillText(char, x * scaledCharWidth, y * scaledCharHeight); this._ctx.restore(); } diff --git a/src/renderer/ForegroundRenderLayer.ts b/src/renderer/ForegroundRenderLayer.ts index 7d1cec1f..30f68f4c 100644 --- a/src/renderer/ForegroundRenderLayer.ts +++ b/src/renderer/ForegroundRenderLayer.ts @@ -88,7 +88,20 @@ export class ForegroundRenderLayer extends BaseRenderLayer { } } + if (flags & FLAGS.UNDERLINE) { + if (fg === INVERTED_DEFAULT_COLOR) { + this._ctx.fillStyle = this.colors.background; + } else if (fg < 256) { + // 256 color support + this._ctx.fillStyle = this.colors.ansi[fg]; + } else { + this._ctx.fillStyle = this.colors.foreground; + } + this.fillBottomLineAtCell(x, y); + } + this.drawChar(terminal, char, code, x, y, fg); + this._ctx.restore(); } } From 5cd189e2e072ea45f41058dfc8e7235d45645d93 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 1 Sep 2017 21:36:20 -0700 Subject: [PATCH 150/378] Fix demo pty size --- demo/main.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/demo/main.js b/demo/main.js index 28840709..f05a8cef 100644 --- a/demo/main.js +++ b/demo/main.js @@ -97,10 +97,10 @@ function createTerminal() { colsElement.value = term.cols; rowsElement.value = term.rows; - fetch('/terminals?cols=' + cols + '&rows=' + rows, {method: 'POST'}).then(function (res) { + fetch('/terminals?cols=' + term.cols + '&rows=' + term.rows, {method: 'POST'}).then(function (res) { - charWidth = Math.ceil(term.element.offsetWidth / cols); - charHeight = Math.ceil(term.element.offsetHeight / rows); + charWidth = Math.ceil(term.element.offsetWidth / term.cols); + charHeight = Math.ceil(term.element.offsetHeight / term.rows); res.text().then(function (pid) { window.pid = pid; From 6ee8e9126a5c1f7fe13de8de90c6d37411ae15a4 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 1 Sep 2017 21:38:35 -0700 Subject: [PATCH 151/378] Remove some old dead code --- src/Renderer.ts | 423 ------------------------- src/renderer/Renderer.ts | 1 - src/utils/DomElementObjectPool.test.ts | 51 --- src/utils/DomElementObjectPool.ts | 75 ----- 4 files changed, 550 deletions(-) delete mode 100644 src/Renderer.ts delete mode 100644 src/utils/DomElementObjectPool.test.ts delete mode 100644 src/utils/DomElementObjectPool.ts diff --git a/src/Renderer.ts b/src/Renderer.ts deleted file mode 100644 index f4a275bb..00000000 --- a/src/Renderer.ts +++ /dev/null @@ -1,423 +0,0 @@ -/** - * @license MIT - */ - -import { ITerminal } from './Interfaces'; -import { DomElementObjectPool } from './utils/DomElementObjectPool'; -import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from './Buffer'; - -/** - * The maximum number of refresh frames to skip when the write buffer is non- - * empty. Note that these frames may be intermingled with frames that are - * skipped via requestAnimationFrame's mechanism. - */ -const MAX_REFRESH_FRAME_SKIP = 5; - -/** - * Flags used to render terminal text properly. - */ -enum FLAGS { - BOLD = 1, - UNDERLINE = 2, - BLINK = 4, - INVERSE = 8, - INVISIBLE = 16 -}; - -let brokenBold: boolean = null; - -export class Renderer { - /** A queue of the rows to be refreshed */ - private _refreshRowsQueue: {start: number, end: number}[] = []; - private _refreshFramesSkipped = 0; - private _refreshAnimationFrame = null; - - private _spanElementObjectPool = new DomElementObjectPool('span'); - - constructor(private _terminal: ITerminal) { - // Figure out whether boldness affects - // the character width of monospace fonts. - if (brokenBold === null) { - brokenBold = checkBoldBroken(this._terminal.element); - } - this._spanElementObjectPool = new DomElementObjectPool('span'); - - // TODO: Pull more DOM interactions into Renderer.constructor, element for - // example should be owned by Renderer (and also exposed by Terminal due to - // to established public API). - } - - /** - * Queues a refresh between two rows (inclusive), to be done on next animation - * frame. - * @param {number} start The start row. - * @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)); - } - } - - /** - * Performs the refresh loop callback, calling refresh only if a refresh is - * necessary before queueing up the next one. - */ - private _refreshLoop(): void { - // Skip MAX_REFRESH_FRAME_SKIP frames if the writeBuffer is non-empty as it - // will need to be immediately refreshed anyway. This saves a lot of - // rendering time as the viewport DOM does not need to be refreshed, no - // scroll events, no layouts, etc. - const skipFrame = this._terminal.writeBuffer.length > 0 && this._refreshFramesSkipped++ <= MAX_REFRESH_FRAME_SKIP; - if (skipFrame) { - this._refreshAnimationFrame = window.requestAnimationFrame(this._refreshLoop.bind(this)); - return; - } - - this._refreshFramesSkipped = 0; - 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; - this._refresh(start, end); - } - - /** - * Refreshes (re-renders) terminal content within two rows (inclusive) - * - * Rendering Engine: - * - * In the screen buffer, each character is stored as a an array with a character - * and a 32-bit integer: - * - First value: a utf-16 character. - * - Second value: - * - Next 9 bits: background color (0-511). - * - Next 9 bits: foreground color (0-511). - * - Next 14 bits: a mask for misc. flags: - * - 1=bold - * - 2=underline - * - 4=blink - * - 8=inverse - * - 16=invisible - * - * @param {number} start The row to start from (between 0 and terminal's height terminal - 1) - * @param {number} end The row to end at (between fromRow and terminal's height terminal - 1) - */ - private _refresh(start: number, end: number): void { - // If this is a big refresh, remove the terminal rows from the DOM for faster calculations - let parent; - if (end - start >= this._terminal.rows / 2) { - parent = this._terminal.element.parentNode; - if (parent) { - this._terminal.element.removeChild(this._terminal.rowContainer); - } - } - - let width = this._terminal.cols; - let y = start; - - if (end >= this._terminal.rows) { - this._terminal.log('`end` is too large. Most likely a bad CSR.'); - end = this._terminal.rows - 1; - } - - for (; y <= end; y++) { - let row = y + this._terminal.buffer.ydisp; - - let line = this._terminal.buffer.lines.get(row); - - let x; - if (this._terminal.buffer.y === y - (this._terminal.buffer.ybase - this._terminal.buffer.ydisp) && - this._terminal.cursorState && - !this._terminal.cursorHidden) { - x = this._terminal.buffer.x; - } else { - x = -1; - } - - let attr = this._terminal.defAttr; - - const documentFragment = document.createDocumentFragment(); - let innerHTML = ''; - let currentElement; - - // Return the row's spans to the pool - while (this._terminal.children[y].children.length) { - const child = this._terminal.children[y].children[0]; - this._terminal.children[y].removeChild(child); - this._spanElementObjectPool.release(child); - } - - for (let i = 0; i < width; i++) { - // TODO: Could data be a more specific type? - let data: any = line[i][0]; - const ch = line[i][CHAR_DATA_CHAR_INDEX]; - const ch_width: any = line[i][CHAR_DATA_WIDTH_INDEX]; - const isCursor: boolean = i === x; - if (!ch_width) { - continue; - } - - if (data !== attr || isCursor) { - if (attr !== this._terminal.defAttr && !isCursor) { - if (innerHTML) { - currentElement.innerHTML = innerHTML; - innerHTML = ''; - } - documentFragment.appendChild(currentElement); - currentElement = null; - } - if (data !== this._terminal.defAttr || isCursor) { - if (innerHTML && !currentElement) { - currentElement = this._spanElementObjectPool.acquire(); - } - if (currentElement) { - if (innerHTML) { - currentElement.innerHTML = innerHTML; - innerHTML = ''; - } - documentFragment.appendChild(currentElement); - } - currentElement = this._spanElementObjectPool.acquire(); - - let bg = data & 0x1ff; - let fg = (data >> 9) & 0x1ff; - let flags = data >> 18; - - if (isCursor) { - currentElement.classList.add('reverse-video'); - currentElement.classList.add('terminal-cursor'); - } - - if (flags & FLAGS.BOLD) { - if (!brokenBold) { - currentElement.classList.add('xterm-bold'); - } - // See: XTerm*boldColors - if (fg < 8) { - fg += 8; - } - } - - if (flags & FLAGS.UNDERLINE) { - currentElement.classList.add('xterm-underline'); - } - - if (flags & FLAGS.BLINK) { - currentElement.classList.add('xterm-blink'); - } - - // If inverse flag is on, then swap the foreground and background variables. - if (flags & FLAGS.INVERSE) { - let temp = bg; - bg = fg; - fg = temp; - // Should inverse just be before the above boldColors effect instead? - if ((flags & 1) && fg < 8) { - fg += 8; - } - } - - if (flags & FLAGS.INVISIBLE && !isCursor) { - currentElement.classList.add('xterm-hidden'); - } - - /** - * Weird situation: Invert flag used black foreground and white background results - * in invalid background color, positioned at the 256 index of the 256 terminal - * color map. Pin the colors manually in such a case. - * - * Source: https://github.com/sourcelair/xterm.js/issues/57 - */ - if (flags & FLAGS.INVERSE) { - if (bg === 257) { - bg = 15; - } - if (fg === 256) { - fg = 0; - } - } - - if (bg < 256) { - currentElement.classList.add(`xterm-bg-color-${bg}`); - } - - if (fg < 256) { - currentElement.classList.add(`xterm-color-${fg}`); - } - - } - } - - if (ch_width === 2) { - // Wrap wide characters so they're sized correctly. It's more difficult to release these - // from the object pool so just create new ones via innerHTML. - innerHTML += `${ch}`; - } else if (ch.charCodeAt(0) > 255) { - // Wrap any non-wide unicode character as some fonts size them badly - innerHTML += `${ch}`; - } else { - switch (ch) { - case '&': - innerHTML += '&'; - break; - case '<': - innerHTML += '<'; - break; - case '>': - innerHTML += '>'; - break; - default: - if (ch <= ' ') { - innerHTML += ' '; - } else { - innerHTML += ch; - } - break; - } - } - - // The cursor needs its own element, therefore we set attr to -1 - // which will cause the next character to be rendered in a new element - attr = isCursor ? -1 : data; - - } - - if (innerHTML && !currentElement) { - currentElement = this._spanElementObjectPool.acquire(); - } - if (currentElement) { - if (innerHTML) { - currentElement.innerHTML = innerHTML; - innerHTML = ''; - } - documentFragment.appendChild(currentElement); - currentElement = null; - } - - this._terminal.children[y].appendChild(documentFragment); - } - - if (parent) { - this._terminal.element.appendChild(this._terminal.rowContainer); - } - - this._terminal.emit('refresh', {start, end}); - }; - - private _imageDataCache = {}; - private _colors = [ - // dark: - '#2e3436', - '#cc0000', - '#4e9a06', - '#c4a000', - '#3465a4', - '#75507b', - '#06989a', - '#d3d7cf', - // bright: - '#555753', - '#ef2929', - '#8ae234', - '#fce94f', - '#729fcf', - '#ad7fa8', - '#34e2e2', - '#eeeeec' - ]; - - /** - * Refreshes the selection in the DOM. - * @param start The selection start. - * @param end The selection end. - */ - public refreshSelection(start: [number, number], end: [number, number]): void { - // Remove all selections - while (this._terminal.selectionContainer.children.length) { - this._terminal.selectionContainer.removeChild(this._terminal.selectionContainer.children[0]); - } - - // Selection does not exist - if (!start || !end) { - return; - } - - // Translate from buffer position to viewport position - const viewportStartRow = start[1] - this._terminal.buffer.ydisp; - const viewportEndRow = end[1] - this._terminal.buffer.ydisp; - const viewportCappedStartRow = Math.max(viewportStartRow, 0); - const viewportCappedEndRow = Math.min(viewportEndRow, this._terminal.rows - 1); - - // No need to draw the selection - if (viewportCappedStartRow >= this._terminal.rows || viewportCappedEndRow < 0) { - return; - } - - // Create the selections - const documentFragment = document.createDocumentFragment(); - // Draw first row - const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0; - const endCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : this._terminal.cols; - documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow, startCol, endCol)); - // Draw middle rows - const middleRowsCount = viewportCappedEndRow - viewportCappedStartRow - 1; - documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow + 1, 0, this._terminal.cols, middleRowsCount)); - // Draw final row - if (viewportCappedStartRow !== viewportCappedEndRow) { - // Only draw viewportEndRow if it's not the same as viewporttartRow - const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : this._terminal.cols; - documentFragment.appendChild(this._createSelectionElement(viewportCappedEndRow, 0, endCol)); - } - this._terminal.selectionContainer.appendChild(documentFragment); - } - - /** - * Creates a selection element at the specified position. - * @param row The row of the selection. - * @param colStart The start column. - * @param colEnd The end columns. - */ - private _createSelectionElement(row: number, colStart: number, colEnd: number, rowCount: number = 1): HTMLElement { - const element = document.createElement('div'); - element.style.height = `${rowCount * this._terminal.charMeasure.height}px`; - element.style.top = `${row * this._terminal.charMeasure.height}px`; - element.style.left = `${colStart * this._terminal.charMeasure.width}px`; - element.style.width = `${this._terminal.charMeasure.width * (colEnd - colStart)}px`; - return element; - } -} - - -// If bold is broken, we can't use it in the terminal. -function checkBoldBroken(terminalElement: HTMLElement): boolean { - const document = terminalElement.ownerDocument; - const el = document.createElement('span'); - el.innerHTML = 'hello world'; - terminalElement.appendChild(el); - const w1 = el.offsetWidth; - const h1 = el.offsetHeight; - el.style.fontWeight = 'bold'; - const w2 = el.offsetWidth; - const h2 = el.offsetHeight; - terminalElement.removeChild(el); - return w1 !== w2 || h1 !== h2; -} diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 96e899f6..119399b0 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -3,7 +3,6 @@ */ import { ITerminal, ITheme } from '../Interfaces'; -import { DomElementObjectPool } from '../utils/DomElementObjectPool'; import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from '../Buffer'; import { createBackgroundFillData } from './Canvas'; import { BackgroundRenderLayer } from './BackgroundRenderLayer'; diff --git a/src/utils/DomElementObjectPool.test.ts b/src/utils/DomElementObjectPool.test.ts deleted file mode 100644 index 5a5d8a78..00000000 --- a/src/utils/DomElementObjectPool.test.ts +++ /dev/null @@ -1,51 +0,0 @@ -/** - * @license MIT - */ - -import { assert } from 'chai'; -import { DomElementObjectPool } from './DomElementObjectPool'; - -class MockDocument { - private _attr: {[key: string]: string} = {}; - constructor() {} - public getAttribute(key: string): string { return this._attr[key]; }; - public setAttribute(key: string, value: string): void { this._attr[key] = value; } -} - -describe('DomElementObjectPool', () => { - let pool: DomElementObjectPool; - - beforeEach(() => { - pool = new DomElementObjectPool('span'); - (global).document = { - createElement: () => new MockDocument() - }; - }); - - it('should acquire distinct elements', () => { - const element1 = pool.acquire(); - const element2 = pool.acquire(); - assert.notEqual(element1, element2); - }); - - it('should acquire released elements', () => { - const element = pool.acquire(); - pool.release(element); - assert.equal(pool.acquire(), element); - }); - - it('should handle a series of acquisitions and releases', () => { - const element1 = pool.acquire(); - const element2 = pool.acquire(); - pool.release(element1); - assert.equal(pool.acquire(), element1); - pool.release(element1); - pool.release(element2); - assert.equal(pool.acquire(), element2); - assert.equal(pool.acquire(), element1); - }); - - it('should throw when releasing an element that was not acquired', () => { - assert.throws(() => pool.release(document.createElement('span'))); - }); -}); diff --git a/src/utils/DomElementObjectPool.ts b/src/utils/DomElementObjectPool.ts deleted file mode 100644 index 7707ad9a..00000000 --- a/src/utils/DomElementObjectPool.ts +++ /dev/null @@ -1,75 +0,0 @@ -/** - * @module xterm/utils/DomElementObjectPool - * @license MIT - */ - -/** - * An object pool that manages acquisition and releasing of DOM elements for - * when reuse is desirable. - */ -export class DomElementObjectPool { - private static readonly OBJECT_ID_ATTRIBUTE = 'data-obj-id'; - - private static _objectCount = 0; - - private _type: string; - private _pool: HTMLElement[]; - private _inUse: {[key: string]: HTMLElement}; - - /** - * @param type The DOM element type (div, span, etc.). - */ - constructor(private type: string) { - this._type = type; - this._pool = []; - this._inUse = {}; - } - - /** - * Acquire an element from the pool, creating it if the pool is empty. - */ - public acquire(): HTMLElement { - let element: HTMLElement; - if (this._pool.length === 0) { - element = this._createNew(); - } else { - element = this._pool.pop(); - } - this._inUse[element.getAttribute(DomElementObjectPool.OBJECT_ID_ATTRIBUTE)] = element; - return element; - } - - /** - * Release an element back into the pool. It's up to the caller of this - * function to ensure that all external references to the element have been - * removed. - * @param element The element being released. - */ - public release(element: HTMLElement): void { - if (!this._inUse[element.getAttribute(DomElementObjectPool.OBJECT_ID_ATTRIBUTE)]) { - throw new Error('Could not release an element not yet acquired'); - } - delete this._inUse[element.getAttribute(DomElementObjectPool.OBJECT_ID_ATTRIBUTE)]; - this._cleanElement(element); - this._pool.push(element); - } - - /** - * Creates a new element for the pool. - */ - private _createNew(): HTMLElement { - const element = document.createElement(this._type); - const id = DomElementObjectPool._objectCount++; - element.setAttribute(DomElementObjectPool.OBJECT_ID_ATTRIBUTE, id.toString(10)); - return element; - } - - /** - * Resets an element back to a "clean state". - * @param element The element to be cleaned. - */ - private _cleanElement(element: HTMLElement): void { - element.className = ''; - element.innerHTML = ''; - } -} From b57b94ad0bf839f9e24d3699cd285f44cc26fb47 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 1 Sep 2017 22:02:12 -0700 Subject: [PATCH 152/378] Improve focus/blur state, fire only once Fixes #681 --- src/Terminal.ts | 46 ++++++++++++++++++++-------------------------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 13f38860..616c2945 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -532,17 +532,14 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT /** * Binds the desired focus behavior on a given terminal object. */ - private bindFocus(): void { - globalOn(this.textarea, 'focus', (ev) => { - if (this.sendFocus) { - this.send(C0.ESC + '[I'); - } - this.element.classList.add('focus'); - this.showCursor(); - this.restartCursorBlinking.apply(this); - // TODO: Why pass terminal here? - this.emit('focus'); - }); + private _onTextAreaFocus(): void { + if (this.sendFocus) { + this.send(C0.ESC + '[I'); + } + this.element.classList.add('focus'); + this.showCursor(); + this.restartCursorBlinking.apply(this); + this.emit('focus'); }; /** @@ -556,17 +553,14 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT /** * Binds the desired blur behavior on a given terminal object. */ - private bindBlur(): void { - on(this.textarea, 'blur', (ev) => { - this.refresh(this.buffer.y, this.buffer.y); - if (this.sendFocus) { - this.send(C0.ESC + '[O'); - } - this.element.classList.remove('focus'); - this.clearCursorBlinkingInterval.apply(this); - // TODO: Why pass terminal here? - this.emit('blur'); - }); + private _onTextAreaBlur(): void { + this.refresh(this.buffer.y, this.buffer.y); + if (this.sendFocus) { + this.send(C0.ESC + '[O'); + } + this.element.classList.remove('focus'); + this.clearCursorBlinkingInterval.apply(this); + this.emit('blur'); } /** @@ -574,8 +568,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT */ private initGlobal(): void { this.bindKeys(); - this.bindFocus(); - this.bindBlur(); // Bind clipboard functionality on(this.element, 'copy', (event: ClipboardEvent) => { @@ -740,8 +732,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.textarea.setAttribute('autocapitalize', 'off'); this.textarea.setAttribute('spellcheck', 'false'); this.textarea.tabIndex = 0; - this.textarea.addEventListener('focus', () => this.emit('focus')); - this.textarea.addEventListener('blur', () => this.emit('blur')); + this.textarea.addEventListener('focus', () => this._onTextAreaFocus()); + this.textarea.addEventListener('blur', () => this._onTextAreaBlur()); this.helperContainer.appendChild(this.textarea); this.compositionView = document.createElement('div'); @@ -763,6 +755,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.renderer = new Renderer(this); this.on('cursormove', () => this.renderer.onCursorMove()); this.on('resize', () => this.renderer.onResize(this.cols, this.rows)); + this.on('blur', () => this.renderer.onBlur()); + this.on('focus', () => this.renderer.onFocus()); this.charMeasure.on('charsizechanged', () => { this.renderer.onCharSizeChanged(this.charMeasure.width, this.charMeasure.height); // Force a refresh for the char size change From dc1e7276138e7a6d7666ed5a21d1405df29ea0cf Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 1 Sep 2017 22:42:31 -0700 Subject: [PATCH 153/378] Support blurred cursor --- src/Interfaces.ts | 1 + src/Terminal.ts | 4 +++ src/renderer/BaseRenderLayer.ts | 16 +++++++-- src/renderer/CursorRenderLayer.ts | 47 +++++++++++++++++++++++---- src/renderer/ForegroundRenderLayer.ts | 2 +- src/renderer/Interfaces.ts | 10 ++++++ src/renderer/Renderer.ts | 8 +++++ 7 files changed, 78 insertions(+), 10 deletions(-) diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 3a0c9929..745dd87a 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -35,6 +35,7 @@ export interface ITerminal extends IEventEmitter { options: ITerminalOptions; buffers: IBufferSet; buffer: IBuffer; + isFocused: boolean; /** * Emit the 'data' event and populate the given data. diff --git a/src/Terminal.ts b/src/Terminal.ts index 616c2945..fc5af887 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -412,6 +412,10 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.textarea.focus(); } + public get isFocused(): boolean { + return document.activeElement === this.textarea; + } + public setTheme(theme: ITheme): void { // TODO: Allow setting of theme before renderer is ready if (this.renderer) { diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 523edbda..3da7aadc 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -30,6 +30,8 @@ export abstract class BaseRenderLayer implements IRenderLayer { // TODO: Should this do anything? public onOptionsChanged(terminal: ITerminal): void {} + public onBlur(terminal: ITerminal): void {} + public onFocus(terminal: ITerminal): void {} public onCursorMove(terminal: ITerminal): void {} public onGridChanged(terminal: ITerminal, startRow: number, endRow: number): void {} public onSelectionChanged(terminal: ITerminal, start: [number, number], end: [number, number]): void {} @@ -58,7 +60,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._ctx.fillRect(startCol * this.scaledCharWidth, startRow * this.scaledCharHeight, colWidth * this.scaledCharWidth, colHeight * this.scaledCharHeight); } - protected fillBottomLineAtCell(x: number, y: number): void { + protected drawBottomLineAtCell(x: number, y: number): void { this._ctx.fillRect( x * this.scaledCharWidth, (y + 1) * this.scaledCharHeight - window.devicePixelRatio - 1 /* Ensure it's drawn within the cell */, @@ -66,7 +68,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { window.devicePixelRatio); } - protected fillLeftLineAtCell(x: number, y: number): void { + protected drawLeftLineAtCell(x: number, y: number): void { this._ctx.fillRect( x * this.scaledCharWidth, y * this.scaledCharHeight, @@ -74,6 +76,16 @@ export abstract class BaseRenderLayer implements IRenderLayer { this.scaledCharHeight); } + protected drawSquareAtCell(x: number, y: number, color: string): void { + this._ctx.strokeStyle = color; + this._ctx.lineWidth = window.devicePixelRatio; + this._ctx.strokeRect( + x * this.scaledCharWidth + window.devicePixelRatio / 2, + y * this.scaledCharHeight + (window.devicePixelRatio / 2), + this.scaledCharWidth - window.devicePixelRatio, + this.scaledCharHeight - window.devicePixelRatio); + } + protected clearAll(): void { this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); } diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index cd11f1dc..941d6d3b 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -16,6 +16,7 @@ export class CursorRenderLayer extends BaseRenderLayer { private _state: [number, number]; private _cursorRenderers: {[key: string]: (terminal: ITerminal, x: number, y: number, charData: CharData) => void}; private _cursorBlinkStateManager: CursorBlinkStateManager; + private _isFocused: boolean; constructor(container: HTMLElement, zIndex: number, colors: IColorSet) { super(container, 'cursor', zIndex, colors); @@ -37,6 +38,20 @@ export class CursorRenderLayer extends BaseRenderLayer { } } + public onBlur(terminal: ITerminal): void { + if (this._cursorBlinkStateManager) { + // this._cursorBlinkStateManager.pause(); + } + terminal.refresh(terminal.buffer.y, terminal.buffer.y); + } + + public onFocus(terminal: ITerminal): void { + if (this._cursorBlinkStateManager) { + // this._cursorBlinkStateManager.resume(); + } + terminal.refresh(terminal.buffer.y, terminal.buffer.y); + } + public onOptionsChanged(terminal: ITerminal): void { if (terminal.options.cursorBlink) { if (!this._cursorBlinkStateManager) { @@ -69,11 +84,8 @@ export class CursorRenderLayer extends BaseRenderLayer { } private _render(terminal: ITerminal, triggeredByAnimationFrame: boolean): void { - // TODO: Track blur/focus somehow, support unfocused cursor // Don't draw the cursor if it's hidden - if (!terminal.cursorState || - terminal.cursorHidden || - (this._cursorBlinkStateManager && !this._cursorBlinkStateManager.isCursorVisible)) { + if (!terminal.cursorState || terminal.cursorHidden) { this._clearCursor(); return; } @@ -87,6 +99,23 @@ export class CursorRenderLayer extends BaseRenderLayer { return; } + const charData = terminal.buffer.lines.get(cursorY)[terminal.buffer.x]; + + if (!terminal.isFocused) { + this._clearCursor(); + this._ctx.save(); + this._ctx.fillStyle = this.colors.ansi[COLOR_CODES.WHITE]; + this._renderBlurCursor(terminal, terminal.buffer.x, viewportRelativeCursorY, charData); + this._ctx.restore(); + return; + } + + // Don't draw the cursor if it's blinking + if (this._cursorBlinkStateManager && !this._cursorBlinkStateManager.isCursorVisible) { + this._clearCursor(); + return; + } + if (this._state) { // The cursor is already in the correct spot, don't redraw if (this._state[0] === terminal.buffer.x && this._state[1] === viewportRelativeCursorY) { @@ -95,7 +124,6 @@ export class CursorRenderLayer extends BaseRenderLayer { this._clearCursor(); } - const charData = terminal.buffer.lines.get(cursorY)[terminal.buffer.x]; this._ctx.save(); this._ctx.fillStyle = this.colors.ansi[COLOR_CODES.WHITE]; this._cursorRenderers[terminal.options.cursorStyle || 'block'](terminal, terminal.buffer.x, viewportRelativeCursorY, charData); @@ -111,7 +139,7 @@ export class CursorRenderLayer extends BaseRenderLayer { } private _renderBarCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void { - this.fillLeftLineAtCell(x, y); + this.drawLeftLineAtCell(x, y); } private _renderBlockCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void { @@ -120,7 +148,12 @@ export class CursorRenderLayer extends BaseRenderLayer { } private _renderUnderlineCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void { - this.fillBottomLineAtCell(x, y); + this.drawBottomLineAtCell(x, y); + } + + private _renderBlurCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void { + // TODO: Support cursor colors + this.drawSquareAtCell(x, y, this.colors.foreground); } } diff --git a/src/renderer/ForegroundRenderLayer.ts b/src/renderer/ForegroundRenderLayer.ts index 30f68f4c..f4bc1e77 100644 --- a/src/renderer/ForegroundRenderLayer.ts +++ b/src/renderer/ForegroundRenderLayer.ts @@ -97,7 +97,7 @@ export class ForegroundRenderLayer extends BaseRenderLayer { } else { this._ctx.fillStyle = this.colors.foreground; } - this.fillBottomLineAtCell(x, y); + this.drawBottomLineAtCell(x, y); } this.drawChar(terminal, char, code, x, y, fg); diff --git a/src/renderer/Interfaces.ts b/src/renderer/Interfaces.ts index 45822e65..b47a75b9 100644 --- a/src/renderer/Interfaces.ts +++ b/src/renderer/Interfaces.ts @@ -1,6 +1,16 @@ import { ITerminal, ITerminalOptions } from '../Interfaces'; export interface IRenderLayer { + /** + * Called when the terminal loses focus. + */ + onBlur(terminal: ITerminal): void; + + /** + * * Called when the terminal gets focus. + */ + onFocus(terminal: ITerminal): void; + /** * Called when the cursor is moved. */ diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 119399b0..474699c5 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -57,6 +57,14 @@ export class Renderer { this._renderLayers.forEach(l => l.resize(this._terminal, width, height, true)); } + public onBlur(): void { + this._renderLayers.forEach(l => l.onBlur(this._terminal)); + } + + public onFocus(): void { + this._renderLayers.forEach(l => l.onFocus(this._terminal)); + } + public onSelectionChanged(start: [number, number], end: [number, number]): void { this._renderLayers.forEach(l => l.onSelectionChanged(this._terminal, start, end)); } From 4919c40b57a3c05515f1b176751831905d121e90 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 1 Sep 2017 22:53:21 -0700 Subject: [PATCH 154/378] Add cursor to ITheme --- src/Interfaces.ts | 2 +- src/renderer/BaseRenderLayer.ts | 17 +++++++++++++---- src/{utils => renderer}/CharAtlas.ts | 6 +++--- src/renderer/ColorManager.ts | 2 ++ src/renderer/CursorRenderLayer.ts | 14 +++++++++++--- src/renderer/Interfaces.ts | 1 + src/utils/TestUtils.test.ts | 1 + 7 files changed, 32 insertions(+), 11 deletions(-) rename src/{utils => renderer}/CharAtlas.ts (97%) diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 745dd87a..b6ba08db 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -299,7 +299,7 @@ export interface IInputHandler { export interface ITheme { foreground?: string; background?: string; - // cursor?: string; + cursor?: string; black?: string; red?: string; green?: string; diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 3da7aadc..fb78e1d2 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -1,6 +1,6 @@ import { IRenderLayer, IColorSet } from './Interfaces'; import { ITerminal, ITerminalOptions } from '../Interfaces'; -import { acquireCharAtlas } from '../utils/CharAtlas'; +import { acquireCharAtlas } from './CharAtlas'; export const INVERTED_DEFAULT_COLOR = -1; @@ -94,6 +94,15 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._ctx.clearRect(startCol * this.scaledCharWidth, startRow * this.scaledCharHeight, colWidth * this.scaledCharWidth, colHeight * this.scaledCharHeight); } + protected drawCharTrueColor(terminal: ITerminal, char: string, code: number, x: number, y: number, color: string): void { + this._ctx.save(); + this._ctx.font = `${terminal.options.fontSize * window.devicePixelRatio}px ${terminal.options.fontFamily}`; + this._ctx.textBaseline = 'top'; + this._ctx.fillStyle = color; + this._ctx.fillText(char, x * this.scaledCharWidth, y * this.scaledCharHeight); + this._ctx.restore(); + } + protected drawChar(terminal: ITerminal, char: string, code: number, x: number, y: number, fg: number, underline: boolean = false): void { let colorIndex = 0; if (fg < 256) { @@ -105,13 +114,13 @@ export abstract class BaseRenderLayer implements IRenderLayer { code * this.scaledCharWidth, colorIndex * this.scaledCharHeight, this.scaledCharWidth, this.scaledCharHeight, x * this.scaledCharWidth, y * this.scaledCharHeight, this.scaledCharWidth, this.scaledCharHeight); } else { - this._drawUncachedChar(terminal, char, fg, x, y, this.scaledCharWidth, this.scaledCharHeight); + this._drawUncachedChar(terminal, char, fg, x, y); } // This draws the atlas (for debugging purposes) // this._ctx.drawImage(BaseRenderLayer._charAtlas, 0, 0); } - private _drawUncachedChar(terminal: ITerminal, char: string, fg: number, x: number, y: number, scaledCharWidth: number, scaledCharHeight: number): void { + private _drawUncachedChar(terminal: ITerminal, char: string, fg: number, x: number, y: number): void { this._ctx.save(); this._ctx.font = `${terminal.options.fontSize * window.devicePixelRatio}px ${terminal.options.fontFamily}`; this._ctx.textBaseline = 'top'; @@ -125,7 +134,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._ctx.fillStyle = this.colors.foreground; } - this._ctx.fillText(char, x * scaledCharWidth, y * scaledCharHeight); + this._ctx.fillText(char, x * this.scaledCharWidth, y * this.scaledCharHeight); this._ctx.restore(); } } diff --git a/src/utils/CharAtlas.ts b/src/renderer/CharAtlas.ts similarity index 97% rename from src/utils/CharAtlas.ts rename to src/renderer/CharAtlas.ts index 18398efc..e71d8d5a 100644 --- a/src/utils/CharAtlas.ts +++ b/src/renderer/CharAtlas.ts @@ -63,7 +63,8 @@ export function acquireCharAtlas(terminal: ITerminal, colors: IColorSet): Promis function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: ITerminal, colors: IColorSet): ICharAtlasConfig { const clonedColors = { foreground: colors.foreground, - background: colors.background, + background: null, + cursor: null, ansi: colors.ansi.slice(0, 16) }; return { @@ -85,8 +86,7 @@ function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean { a.fontSize === b.fontSize && a.scaledCharWidth === b.scaledCharWidth && a.scaledCharHeight === b.scaledCharHeight && - a.colors.foreground === b.colors.foreground && - a.colors.background === b.colors.background; + a.colors.foreground === b.colors.foreground; } class CharAtlasGenerator { diff --git a/src/renderer/ColorManager.ts b/src/renderer/ColorManager.ts index ca8015a2..341e4ca5 100644 --- a/src/renderer/ColorManager.ts +++ b/src/renderer/ColorManager.ts @@ -77,6 +77,7 @@ export class ColorManager { this.colors = { foreground: '#ffffff', background: '#000000', + cursor: '#ffffff', ansi: generate256Colors(DEFAULT_ANSI_COLORS) }; } @@ -84,6 +85,7 @@ export class ColorManager { public setTheme(theme: ITheme): void { if (theme.foreground) this.colors.foreground = theme.foreground; if (theme.background) this.colors.background = theme.background; + if (theme.cursor) this.colors.cursor = theme.cursor; if (theme.black) this.colors.ansi[0] = theme.black; if (theme.red) this.colors.ansi[1] = theme.red; if (theme.green) this.colors.ansi[2] = theme.green; diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index 941d6d3b..10f3ad8c 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -139,21 +139,29 @@ export class CursorRenderLayer extends BaseRenderLayer { } private _renderBarCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void { + this._ctx.save(); + this._ctx.fillStyle = this.colors.cursor; this.drawLeftLineAtCell(x, y); + this._ctx.restore(); } private _renderBlockCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void { + this._ctx.save(); + this._ctx.fillStyle = this.colors.cursor; this.fillCells(x, y, 1, 1); - this.drawChar(terminal, charData[CHAR_DATA_CHAR_INDEX], charData[CHAR_DATA_CODE_INDEX], x, y, COLOR_CODES.BLACK); + this._ctx.restore(); + this.drawCharTrueColor(terminal, charData[CHAR_DATA_CHAR_INDEX], charData[CHAR_DATA_CODE_INDEX], x, y, this.colors.background); } private _renderUnderlineCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void { + this._ctx.save(); + this._ctx.fillStyle = this.colors.cursor; this.drawBottomLineAtCell(x, y); + this._ctx.restore(); } private _renderBlurCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void { - // TODO: Support cursor colors - this.drawSquareAtCell(x, y, this.colors.foreground); + this.drawSquareAtCell(x, y, this.colors.cursor); } } diff --git a/src/renderer/Interfaces.ts b/src/renderer/Interfaces.ts index b47a75b9..fd94d59d 100644 --- a/src/renderer/Interfaces.ts +++ b/src/renderer/Interfaces.ts @@ -52,5 +52,6 @@ export interface IRenderLayer { export interface IColorSet { foreground: string; background: string; + cursor: string; ansi: string[]; } diff --git a/src/utils/TestUtils.test.ts b/src/utils/TestUtils.test.ts index 3a66d49d..f4eae64d 100644 --- a/src/utils/TestUtils.test.ts +++ b/src/utils/TestUtils.test.ts @@ -7,6 +7,7 @@ import { LineData } from '../Types'; import * as Browser from './Browser'; export class MockTerminal implements ITerminal { + isFocused: boolean; options: ITerminalOptions = {}; element: HTMLElement; rowContainer: HTMLElement; From f8ece108c6f6d6ff7dd11c9f4eff434368ea0cdc Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 1 Sep 2017 23:00:29 -0700 Subject: [PATCH 155/378] Rerender cursor if state changes --- src/Terminal.ts | 48 ------------------------------- src/renderer/CursorRenderLayer.ts | 9 ++++-- 2 files changed, 6 insertions(+), 51 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index fc5af887..7d2e8fd9 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -61,13 +61,6 @@ const WRITE_BUFFER_PAUSE_THRESHOLD = 5; */ const WRITE_BATCH_SIZE = 300; -/** - * The time between cursor blinks. This is driven by JS rather than a CSS - * animation due to a bug in Chromium that causes it to use excessive CPU time. - * See https://github.com/Microsoft/vscode/issues/22900 - */ -const CURSOR_BLINK_INTERVAL = 600; - // TODO: Most of the color code should be removed after truecolor is implemented // Colors 0-15 const tangoColors: string[] = [ @@ -197,10 +190,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT private sendDataQueue: string; private customKeyEventHandler: CustomKeyEventHandler; - // The ID from a setInterval that tracks the blink animation. This animation - // is done in JS due to a Chromium bug with CSS animations that thrashed the - // CPU. - private cursorBlinkInterval: NodeJS.Timer; // modes public applicationKeypad: boolean; @@ -341,7 +330,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.cursorHidden = false; this.sendDataQueue = ''; this.customKeyEventHandler = null; - this.cursorBlinkInterval = null; // modes this.applicationKeypad = false; @@ -488,12 +476,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this[key] = value; this.options[key] = value; switch (key) { - case 'cursorBlink': this.setCursorBlinking(value); break; - case 'cursorStyle': - this.element.classList.toggle(`xterm-cursor-style-block`, value === 'block'); - this.element.classList.toggle(`xterm-cursor-style-underline`, value === 'underline'); - this.element.classList.toggle(`xterm-cursor-style-bar`, value === 'bar'); - break; case 'fontFamily': case 'fontSize': // When the font changes the size of the cells may change which requires a renderer clear @@ -511,28 +493,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.renderer.onOptionsChanged(); } - private restartCursorBlinking(): void { - this.setCursorBlinking(this.options.cursorBlink); - } - - private setCursorBlinking(enabled: boolean): void { - this.element.classList.toggle('xterm-cursor-blink', enabled); - this.clearCursorBlinkingInterval(); - if (enabled) { - this.cursorBlinkInterval = setInterval(() => { - this.element.classList.toggle('xterm-cursor-blink-on'); - }, CURSOR_BLINK_INTERVAL); - } - } - - private clearCursorBlinkingInterval(): void { - this.element.classList.remove('xterm-cursor-blink-on'); - if (this.cursorBlinkInterval) { - clearInterval(this.cursorBlinkInterval); - this.cursorBlinkInterval = null; - } - } - /** * Binds the desired focus behavior on a given terminal object. */ @@ -542,7 +502,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT } this.element.classList.add('focus'); this.showCursor(); - this.restartCursorBlinking.apply(this); this.emit('focus'); }; @@ -563,7 +522,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.send(C0.ESC + '[O'); } this.element.classList.remove('focus'); - this.clearCursorBlinkingInterval.apply(this); this.emit('blur'); } @@ -696,8 +654,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.element = this.document.createElement('div'); this.element.classList.add('terminal'); this.element.classList.add('xterm'); - this.element.classList.add(`xterm-cursor-style-${this.options.cursorStyle}`); - this.setCursorBlinking(this.options.cursorBlink); this.element.setAttribute('tabindex', '0'); @@ -1462,8 +1418,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT return false; } - this.restartCursorBlinking(); - if (!this.compositionHelper.keydown(ev)) { if (this.buffer.ybase !== this.buffer.ydisp) { this.scrollToBottom(); @@ -2171,12 +2125,10 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.options.rows = this.rows; this.options.cols = this.cols; const customKeyEventHandler = this.customKeyEventHandler; - const cursorBlinkInterval = this.cursorBlinkInterval; const inputHandler = this.inputHandler; const buffers = this.buffers; this.setup(); this.customKeyEventHandler = customKeyEventHandler; - this.cursorBlinkInterval = cursorBlinkInterval; this.inputHandler = inputHandler; this.buffers = buffers; this.refresh(0, this.rows - 1); diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index 10f3ad8c..752b42dd 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -13,7 +13,7 @@ import { COLOR_CODES } from './ColorManager'; const BLINK_INTERVAL = 600; export class CursorRenderLayer extends BaseRenderLayer { - private _state: [number, number]; + private _state: [number, number, string]; private _cursorRenderers: {[key: string]: (terminal: ITerminal, x: number, y: number, charData: CharData) => void}; private _cursorBlinkStateManager: CursorBlinkStateManager; private _isFocused: boolean; @@ -118,7 +118,10 @@ export class CursorRenderLayer extends BaseRenderLayer { if (this._state) { // The cursor is already in the correct spot, don't redraw - if (this._state[0] === terminal.buffer.x && this._state[1] === viewportRelativeCursorY) { + if (this._state[0] === terminal.buffer.x && + this._state[1] === viewportRelativeCursorY && + this._state[2] === terminal.options.cursorStyle) { + // TODO: Ideally cursorStyle would be stored as a number here to prevent the string compare return; } this._clearCursor(); @@ -128,7 +131,7 @@ export class CursorRenderLayer extends BaseRenderLayer { this._ctx.fillStyle = this.colors.ansi[COLOR_CODES.WHITE]; this._cursorRenderers[terminal.options.cursorStyle || 'block'](terminal, terminal.buffer.x, viewportRelativeCursorY, charData); this._ctx.restore(); - this._state = [terminal.buffer.x, viewportRelativeCursorY]; + this._state = [terminal.buffer.x, viewportRelativeCursorY, terminal.options.cursorStyle]; } private _clearCursor(): void { From 06b54585a18ed0941829c538a2a843f4ddd2a41a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 1 Sep 2017 23:11:39 -0700 Subject: [PATCH 156/378] Fix cursor render when after focusing underline/bar --- src/renderer/CursorRenderLayer.ts | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index 752b42dd..9a2ce3fd 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -13,7 +13,7 @@ import { COLOR_CODES } from './ColorManager'; const BLINK_INTERVAL = 600; export class CursorRenderLayer extends BaseRenderLayer { - private _state: [number, number, string]; + private _state: [number, number, boolean, string]; private _cursorRenderers: {[key: string]: (terminal: ITerminal, x: number, y: number, charData: CharData) => void}; private _cursorBlinkStateManager: CursorBlinkStateManager; private _isFocused: boolean; @@ -40,14 +40,14 @@ export class CursorRenderLayer extends BaseRenderLayer { public onBlur(terminal: ITerminal): void { if (this._cursorBlinkStateManager) { - // this._cursorBlinkStateManager.pause(); + this._cursorBlinkStateManager.pause(); } terminal.refresh(terminal.buffer.y, terminal.buffer.y); } public onFocus(terminal: ITerminal): void { if (this._cursorBlinkStateManager) { - // this._cursorBlinkStateManager.resume(); + this._cursorBlinkStateManager.resume(); } terminal.refresh(terminal.buffer.y, terminal.buffer.y); } @@ -107,6 +107,7 @@ export class CursorRenderLayer extends BaseRenderLayer { this._ctx.fillStyle = this.colors.ansi[COLOR_CODES.WHITE]; this._renderBlurCursor(terminal, terminal.buffer.x, viewportRelativeCursorY, charData); this._ctx.restore(); + this._state = [terminal.buffer.x, viewportRelativeCursorY, terminal.isFocused, terminal.options.cursorStyle]; return; } @@ -120,7 +121,8 @@ export class CursorRenderLayer extends BaseRenderLayer { // The cursor is already in the correct spot, don't redraw if (this._state[0] === terminal.buffer.x && this._state[1] === viewportRelativeCursorY && - this._state[2] === terminal.options.cursorStyle) { + this._state[2] === terminal.isFocused && + this._state[3] === terminal.options.cursorStyle) { // TODO: Ideally cursorStyle would be stored as a number here to prevent the string compare return; } @@ -131,7 +133,7 @@ export class CursorRenderLayer extends BaseRenderLayer { this._ctx.fillStyle = this.colors.ansi[COLOR_CODES.WHITE]; this._cursorRenderers[terminal.options.cursorStyle || 'block'](terminal, terminal.buffer.x, viewportRelativeCursorY, charData); this._ctx.restore(); - this._state = [terminal.buffer.x, viewportRelativeCursorY, terminal.options.cursorStyle]; + this._state = [terminal.buffer.x, viewportRelativeCursorY, terminal.isFocused, terminal.options.cursorStyle]; } private _clearCursor(): void { @@ -261,11 +263,18 @@ class CursorBlinkStateManager { }, timeToStart); } - private _pauseBlinkAnimation(): void { - // TODO: Pause the blink animation on blur + public pause(): void { + this.isCursorVisible = true; + window.clearInterval(this._blinkInterval); + this._blinkInterval = null; + if (this._animationFrame) { + window.cancelAnimationFrame(this._animationFrame); + this._animationFrame = null; + } } - private _resumeBlinkAnimation(): void { - // TODO: Resume the blink animation on focus + public resume(): void { + console.log('resume'); + this._restartInterval(); } } From cf7237d2a4f0885f9ac8167a79cf5105d9325b5a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 1 Sep 2017 23:47:17 -0700 Subject: [PATCH 157/378] Fix remaining cursor animation state issues --- src/renderer/CursorRenderLayer.ts | 48 ++++++++++++++++++++++--------- src/renderer/Renderer.ts | 1 - 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index 9a2ce3fd..0894498f 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -42,14 +42,14 @@ export class CursorRenderLayer extends BaseRenderLayer { if (this._cursorBlinkStateManager) { this._cursorBlinkStateManager.pause(); } - terminal.refresh(terminal.buffer.y, terminal.buffer.y); + terminal.emit('cursormove'); } public onFocus(terminal: ITerminal): void { if (this._cursorBlinkStateManager) { this._cursorBlinkStateManager.resume(); } - terminal.refresh(terminal.buffer.y, terminal.buffer.y); + terminal.emit('cursormove'); } public onOptionsChanged(terminal: ITerminal): void { @@ -78,7 +78,7 @@ export class CursorRenderLayer extends BaseRenderLayer { public onGridChanged(terminal: ITerminal, startRow: number, endRow: number): void { // Only render if the animation frame is not active - if (!this._cursorBlinkStateManager) { + if (!this._cursorBlinkStateManager || this._cursorBlinkStateManager.isPaused) { this._render(terminal, false); } } @@ -107,7 +107,7 @@ export class CursorRenderLayer extends BaseRenderLayer { this._ctx.fillStyle = this.colors.ansi[COLOR_CODES.WHITE]; this._renderBlurCursor(terminal, terminal.buffer.x, viewportRelativeCursorY, charData); this._ctx.restore(); - this._state = [terminal.buffer.x, viewportRelativeCursorY, terminal.isFocused, terminal.options.cursorStyle]; + this._state = [terminal.buffer.x, viewportRelativeCursorY, false, terminal.options.cursorStyle]; return; } @@ -123,7 +123,6 @@ export class CursorRenderLayer extends BaseRenderLayer { this._state[1] === viewportRelativeCursorY && this._state[2] === terminal.isFocused && this._state[3] === terminal.options.cursorStyle) { - // TODO: Ideally cursorStyle would be stored as a number here to prevent the string compare return; } this._clearCursor(); @@ -133,7 +132,7 @@ export class CursorRenderLayer extends BaseRenderLayer { this._ctx.fillStyle = this.colors.ansi[COLOR_CODES.WHITE]; this._cursorRenderers[terminal.options.cursorStyle || 'block'](terminal, terminal.buffer.x, viewportRelativeCursorY, charData); this._ctx.restore(); - this._state = [terminal.buffer.x, viewportRelativeCursorY, terminal.isFocused, terminal.options.cursorStyle]; + this._state = [terminal.buffer.x, viewportRelativeCursorY, true, terminal.options.cursorStyle]; } private _clearCursor(): void { @@ -189,12 +188,22 @@ class CursorBlinkStateManager { private renderCallback: () => void ) { this.isCursorVisible = true; - this._restartInterval(); + if (terminal.isFocused) { + this._restartInterval(); + } } + public get isPaused(): boolean { return !(this._blinkStartTimeout || this._blinkInterval); } + public dispose(): void { - window.clearInterval(this._blinkInterval); - this._blinkInterval = null; + if (this._blinkInterval) { + window.clearInterval(this._blinkInterval); + this._blinkInterval = null; + } + if (this._blinkStartTimeout) { + window.clearTimeout(this._blinkStartTimeout); + this._blinkStartTimeout = null; + } if (this._animationFrame) { window.cancelAnimationFrame(this._animationFrame); this._animationFrame = null; @@ -202,6 +211,9 @@ class CursorBlinkStateManager { } public restartBlinkAnimation(terminal: ITerminal): void { + if (this.isPaused) { + return; + } // Save a timestamp so that the restart can be done on the next interval this._animationTimeRestarted = Date.now(); // Force a cursor render to ensure it's visible and in the correct position @@ -230,8 +242,10 @@ class CursorBlinkStateManager { if (this._animationTimeRestarted) { const time = BLINK_INTERVAL - (Date.now() - this._animationTimeRestarted); this._animationTimeRestarted = null; - this._restartInterval(time); - return; + if (time > 0) { + this._restartInterval(time); + return; + } } // Hide the cursor @@ -265,8 +279,14 @@ class CursorBlinkStateManager { public pause(): void { this.isCursorVisible = true; - window.clearInterval(this._blinkInterval); - this._blinkInterval = null; + if (this._blinkInterval) { + window.clearInterval(this._blinkInterval); + this._blinkInterval = null; + } + if (this._blinkStartTimeout) { + window.clearTimeout(this._blinkStartTimeout); + this._blinkStartTimeout = null; + } if (this._animationFrame) { window.cancelAnimationFrame(this._animationFrame); this._animationFrame = null; @@ -274,7 +294,7 @@ class CursorBlinkStateManager { } public resume(): void { - console.log('resume'); + this._animationTimeRestarted = null; this._restartInterval(); } } diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 474699c5..cc85c7e7 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -33,7 +33,6 @@ export class Renderer { } public setTheme(theme: ITheme): void { - console.log('setTheme'); this._colorManager.setTheme(theme); // Clear layers and force a full render this._renderLayers.forEach(l => { From 36a723c4ccc51fa49b294e239170f33a9612c5fb Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 1 Sep 2017 23:58:34 -0700 Subject: [PATCH 158/378] Fix 256 FG chars --- src/renderer/BaseRenderLayer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index fb78e1d2..ecd064f3 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -108,7 +108,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { if (fg < 256) { colorIndex = fg + 1; } - if (code < 256 && (colorIndex > 0 || fg > 255)) { + if (code < 256 && colorIndex > 0 && fg < 16) { // ImageBitmap's draw about twice as fast as from a canvas this._ctx.drawImage(this._charAtlas, code * this.scaledCharWidth, colorIndex * this.scaledCharHeight, this.scaledCharWidth, this.scaledCharHeight, From 6dd6d0391f9d8bc86e68413f34516331050aeb11 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 2 Sep 2017 00:05:31 -0700 Subject: [PATCH 159/378] Add cell spacing to char atlas --- src/renderer/BaseRenderLayer.ts | 8 +++++--- src/renderer/CharAtlas.ts | 16 +++++++++------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index ecd064f3..bd8e4896 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -1,6 +1,6 @@ import { IRenderLayer, IColorSet } from './Interfaces'; import { ITerminal, ITerminalOptions } from '../Interfaces'; -import { acquireCharAtlas } from './CharAtlas'; +import { acquireCharAtlas, CHAR_ATLAS_CELL_SPACING } from './CharAtlas'; export const INVERTED_DEFAULT_COLOR = -1; @@ -110,14 +110,16 @@ export abstract class BaseRenderLayer implements IRenderLayer { } if (code < 256 && colorIndex > 0 && fg < 16) { // ImageBitmap's draw about twice as fast as from a canvas + const charAtlasCellWidth = this.scaledCharWidth + CHAR_ATLAS_CELL_SPACING; + const charAtlasCellHeight = this.scaledCharHeight + CHAR_ATLAS_CELL_SPACING; this._ctx.drawImage(this._charAtlas, - code * this.scaledCharWidth, colorIndex * this.scaledCharHeight, this.scaledCharWidth, this.scaledCharHeight, + code * charAtlasCellWidth, colorIndex * charAtlasCellHeight, this.scaledCharWidth, this.scaledCharHeight, x * this.scaledCharWidth, y * this.scaledCharHeight, this.scaledCharWidth, this.scaledCharHeight); } else { this._drawUncachedChar(terminal, char, fg, x, y); } // This draws the atlas (for debugging purposes) - // this._ctx.drawImage(BaseRenderLayer._charAtlas, 0, 0); + // this._ctx.drawImage(this._charAtlas, 0, 0); } private _drawUncachedChar(terminal: ITerminal, char: string, fg: number, x: number, y: number): void { diff --git a/src/renderer/CharAtlas.ts b/src/renderer/CharAtlas.ts index e71d8d5a..a9fe8bd3 100644 --- a/src/renderer/CharAtlas.ts +++ b/src/renderer/CharAtlas.ts @@ -1,6 +1,8 @@ import { ITerminal, ITheme } from '../Interfaces'; import { IColorSet } from '../renderer/Interfaces'; +export const CHAR_ATLAS_CELL_SPACING = 1; + interface ICharAtlasConfig { fontSize: number; fontFamily: string; @@ -100,8 +102,10 @@ class CharAtlasGenerator { } public generate(scaledCharWidth: number, scaledCharHeight: number, fontSize: number, fontFamily: string, foreground: string, ansiColors: string[]): Promise { - this._canvas.width = 255 * scaledCharWidth; - this._canvas.height = (/*default*/1 + /*0-15*/16) * scaledCharHeight; + const cellWidth = scaledCharWidth + CHAR_ATLAS_CELL_SPACING; + const cellHeight = scaledCharHeight + CHAR_ATLAS_CELL_SPACING; + this._canvas.width = 255 * cellWidth; + this._canvas.height = (/*default*/1 + /*0-15*/16) * cellHeight; this._ctx.save(); this._ctx.fillStyle = foreground; @@ -110,7 +114,7 @@ class CharAtlasGenerator { // Default color for (let i = 0; i < 256; i++) { - this._ctx.fillText(String.fromCharCode(i), i * scaledCharWidth, 0); + this._ctx.fillText(String.fromCharCode(i), i * cellWidth, 0); } // Colors 0-15 @@ -119,13 +123,11 @@ class CharAtlasGenerator { if (colorIndex === 8) { this._ctx.font = `bold ${this._ctx.font}`; } - const y = (colorIndex + 1) * scaledCharHeight; - // Clear rectangle as some fonts seem to draw over the bottom boundary - this._ctx.clearRect(0, y, this._canvas.width, scaledCharHeight); + const y = (colorIndex + 1) * cellHeight; // Draw ascii characters for (let i = 0; i < 256; i++) { this._ctx.fillStyle = ansiColors[colorIndex]; - this._ctx.fillText(String.fromCharCode(i), i * scaledCharWidth, y); + this._ctx.fillText(String.fromCharCode(i), i * cellWidth, y); } } this._ctx.restore(); From ae72e1c51ed31bb151101b4e3ec0c028c96cc394 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 2 Sep 2017 09:49:05 -0700 Subject: [PATCH 160/378] Fix uncached chars bleeding into other cells --- src/renderer/BaseRenderLayer.ts | 11 ++++++++++- src/renderer/CursorRenderLayer.ts | 2 ++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index bd8e4896..7e883598 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -108,7 +108,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { if (fg < 256) { colorIndex = fg + 1; } - if (code < 256 && colorIndex > 0 && fg < 16) { + if (code < 256 && (colorIndex > 0 || fg >= 256)) { // ImageBitmap's draw about twice as fast as from a canvas const charAtlasCellWidth = this.scaledCharWidth + CHAR_ATLAS_CELL_SPACING; const charAtlasCellHeight = this.scaledCharHeight + CHAR_ATLAS_CELL_SPACING; @@ -136,6 +136,15 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._ctx.fillStyle = this.colors.foreground; } + // Since uncached characters are not coming off the char atlas with source + // coordinates, it means that text drawn to the canvas (particularly '_') + // can bleed into other cells. This code will clip the following fillText, + // ensuring that its contents don't go beyond the cell bounds. + this._ctx.beginPath(); + this._ctx.rect(x * this.scaledCharWidth, y * this.scaledCharHeight, this.scaledCharWidth, this.scaledCharHeight); + this._ctx.clip(); + + // Draw the character this._ctx.fillText(char, x * this.scaledCharWidth, y * this.scaledCharHeight); this._ctx.restore(); } diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index 0894498f..96e4b8fb 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -165,7 +165,9 @@ export class CursorRenderLayer extends BaseRenderLayer { } private _renderBlurCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void { + this._ctx.save(); this.drawSquareAtCell(x, y, this.colors.cursor); + this._ctx.restore(); } } From 32a3c9812d517684521db9a0f246e5e0b01c6e45 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 2 Sep 2017 10:12:30 -0700 Subject: [PATCH 161/378] Support wide character drawing and cursors --- src/renderer/BaseRenderLayer.ts | 29 ++++++++---- src/renderer/CursorRenderLayer.ts | 68 ++++++++++++++++++++------- src/renderer/ForegroundRenderLayer.ts | 4 +- 3 files changed, 73 insertions(+), 28 deletions(-) diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 7e883598..2650af3a 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -1,6 +1,8 @@ import { IRenderLayer, IColorSet } from './Interfaces'; import { ITerminal, ITerminalOptions } from '../Interfaces'; import { acquireCharAtlas, CHAR_ATLAS_CELL_SPACING } from './CharAtlas'; +import { CharData } from '../Types'; +import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from '../Buffer'; export const INVERTED_DEFAULT_COLOR = -1; @@ -76,14 +78,14 @@ export abstract class BaseRenderLayer implements IRenderLayer { this.scaledCharHeight); } - protected drawSquareAtCell(x: number, y: number, color: string): void { + protected drawRectAtCell(x: number, y: number, width: number, height: number, color: string): void { this._ctx.strokeStyle = color; this._ctx.lineWidth = window.devicePixelRatio; this._ctx.strokeRect( x * this.scaledCharWidth + window.devicePixelRatio / 2, y * this.scaledCharHeight + (window.devicePixelRatio / 2), - this.scaledCharWidth - window.devicePixelRatio, - this.scaledCharHeight - window.devicePixelRatio); + (width * this.scaledCharWidth) - window.devicePixelRatio, + (height * this.scaledCharHeight) - window.devicePixelRatio); } protected clearAll(): void { @@ -94,16 +96,25 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._ctx.clearRect(startCol * this.scaledCharWidth, startRow * this.scaledCharHeight, colWidth * this.scaledCharWidth, colHeight * this.scaledCharHeight); } - protected drawCharTrueColor(terminal: ITerminal, char: string, code: number, x: number, y: number, color: string): void { + protected drawCharTrueColor(terminal: ITerminal, charData: CharData, x: number, y: number, color: string): void { this._ctx.save(); this._ctx.font = `${terminal.options.fontSize * window.devicePixelRatio}px ${terminal.options.fontFamily}`; this._ctx.textBaseline = 'top'; this._ctx.fillStyle = color; - this._ctx.fillText(char, x * this.scaledCharWidth, y * this.scaledCharHeight); + + // Since uncached characters are not coming off the char atlas with source + // coordinates, it means that text drawn to the canvas (particularly '_') + // can bleed into other cells. This code will clip the following fillText, + // ensuring that its contents don't go beyond the cell bounds. + this._ctx.beginPath(); + this._ctx.rect(x * this.scaledCharWidth, y * this.scaledCharHeight, charData[CHAR_DATA_WIDTH_INDEX] * this.scaledCharWidth, this.scaledCharHeight); + this._ctx.clip(); + + this._ctx.fillText(charData[CHAR_DATA_CHAR_INDEX], x * this.scaledCharWidth, y * this.scaledCharHeight); this._ctx.restore(); } - protected drawChar(terminal: ITerminal, char: string, code: number, x: number, y: number, fg: number, underline: boolean = false): void { + protected drawChar(terminal: ITerminal, char: string, code: number, width: number, x: number, y: number, fg: number, underline: boolean = false): void { let colorIndex = 0; if (fg < 256) { colorIndex = fg + 1; @@ -116,13 +127,13 @@ export abstract class BaseRenderLayer implements IRenderLayer { code * charAtlasCellWidth, colorIndex * charAtlasCellHeight, this.scaledCharWidth, this.scaledCharHeight, x * this.scaledCharWidth, y * this.scaledCharHeight, this.scaledCharWidth, this.scaledCharHeight); } else { - this._drawUncachedChar(terminal, char, fg, x, y); + this._drawUncachedChar(terminal, char, width, fg, x, y); } // This draws the atlas (for debugging purposes) // this._ctx.drawImage(this._charAtlas, 0, 0); } - private _drawUncachedChar(terminal: ITerminal, char: string, fg: number, x: number, y: number): void { + private _drawUncachedChar(terminal: ITerminal, char: string, width: number, fg: number, x: number, y: number): void { this._ctx.save(); this._ctx.font = `${terminal.options.fontSize * window.devicePixelRatio}px ${terminal.options.fontFamily}`; this._ctx.textBaseline = 'top'; @@ -141,7 +152,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { // can bleed into other cells. This code will clip the following fillText, // ensuring that its contents don't go beyond the cell bounds. this._ctx.beginPath(); - this._ctx.rect(x * this.scaledCharWidth, y * this.scaledCharHeight, this.scaledCharWidth, this.scaledCharHeight); + this._ctx.rect(x * this.scaledCharWidth, y * this.scaledCharHeight, width * this.scaledCharWidth, this.scaledCharHeight); this._ctx.clip(); // Draw the character diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index 96e4b8fb..cc443b49 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -1,26 +1,40 @@ import { IColorSet } from './Interfaces'; import { IBuffer, ICharMeasure, ITerminal, ITerminalOptions } from '../Interfaces'; -import { CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX } from '../Buffer'; +import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX } from '../Buffer'; import { GridCache } from './GridCache'; import { FLAGS } from './Types'; import { BaseRenderLayer } from './BaseRenderLayer'; import { CharData } from '../Types'; import { COLOR_CODES } from './ColorManager'; +interface CursorState { + x: number; + y: number; + isFocused: boolean; + style: string; + width: number; +} + /** * The time between cursor blinks. */ const BLINK_INTERVAL = 600; export class CursorRenderLayer extends BaseRenderLayer { - private _state: [number, number, boolean, string]; + private _state: CursorState; private _cursorRenderers: {[key: string]: (terminal: ITerminal, x: number, y: number, charData: CharData) => void}; private _cursorBlinkStateManager: CursorBlinkStateManager; private _isFocused: boolean; constructor(container: HTMLElement, zIndex: number, colors: IColorSet) { super(container, 'cursor', zIndex, colors); - this._state = null; + this._state = { + x: null, + y: null, + isFocused: null, + style: null, + width: null, + }; this._cursorRenderers = { 'bar': this._renderBarCursor.bind(this), 'block': this._renderBlockCursor.bind(this), @@ -42,14 +56,15 @@ export class CursorRenderLayer extends BaseRenderLayer { if (this._cursorBlinkStateManager) { this._cursorBlinkStateManager.pause(); } - terminal.emit('cursormove'); + terminal.refresh(terminal.buffer.y, terminal.buffer.y); } public onFocus(terminal: ITerminal): void { if (this._cursorBlinkStateManager) { - this._cursorBlinkStateManager.resume(); + this._cursorBlinkStateManager.resume(terminal); + } else { + terminal.refresh(terminal.buffer.y, terminal.buffer.y); } - terminal.emit('cursormove'); } public onOptionsChanged(terminal: ITerminal): void { @@ -107,7 +122,11 @@ export class CursorRenderLayer extends BaseRenderLayer { this._ctx.fillStyle = this.colors.ansi[COLOR_CODES.WHITE]; this._renderBlurCursor(terminal, terminal.buffer.x, viewportRelativeCursorY, charData); this._ctx.restore(); - this._state = [terminal.buffer.x, viewportRelativeCursorY, false, terminal.options.cursorStyle]; + this._state.x = terminal.buffer.x; + this._state.y = viewportRelativeCursorY; + this._state.isFocused = false; + this._state.style = terminal.options.cursorStyle; + this._state.width = charData[CHAR_DATA_WIDTH_INDEX]; return; } @@ -119,10 +138,11 @@ export class CursorRenderLayer extends BaseRenderLayer { if (this._state) { // The cursor is already in the correct spot, don't redraw - if (this._state[0] === terminal.buffer.x && - this._state[1] === viewportRelativeCursorY && - this._state[2] === terminal.isFocused && - this._state[3] === terminal.options.cursorStyle) { + if (this._state.x === terminal.buffer.x && + this._state.y === viewportRelativeCursorY && + this._state.isFocused === terminal.isFocused && + this._state.style === terminal.options.cursorStyle && + this._state.width === charData[CHAR_DATA_WIDTH_INDEX]) { return; } this._clearCursor(); @@ -132,13 +152,24 @@ export class CursorRenderLayer extends BaseRenderLayer { this._ctx.fillStyle = this.colors.ansi[COLOR_CODES.WHITE]; this._cursorRenderers[terminal.options.cursorStyle || 'block'](terminal, terminal.buffer.x, viewportRelativeCursorY, charData); this._ctx.restore(); - this._state = [terminal.buffer.x, viewportRelativeCursorY, true, terminal.options.cursorStyle]; + + this._state.x = terminal.buffer.x; + this._state.y = viewportRelativeCursorY; + this._state.isFocused = false; + this._state.style = terminal.options.cursorStyle; + this._state.width = charData[CHAR_DATA_WIDTH_INDEX]; } private _clearCursor(): void { if (this._state) { - this.clearCells(this._state[0], this._state[1], 1, 1); - this._state = null; + this.clearCells(this._state.x, this._state.y, this._state.width, 1); + this._state = { + x: null, + y: null, + isFocused: null, + style: null, + width: null, + }; } } @@ -152,9 +183,9 @@ export class CursorRenderLayer extends BaseRenderLayer { private _renderBlockCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void { this._ctx.save(); this._ctx.fillStyle = this.colors.cursor; - this.fillCells(x, y, 1, 1); + this.fillCells(x, y, charData[CHAR_DATA_WIDTH_INDEX], 1); this._ctx.restore(); - this.drawCharTrueColor(terminal, charData[CHAR_DATA_CHAR_INDEX], charData[CHAR_DATA_CODE_INDEX], x, y, this.colors.background); + this.drawCharTrueColor(terminal, charData, x, y, this.colors.background); } private _renderUnderlineCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void { @@ -166,7 +197,7 @@ export class CursorRenderLayer extends BaseRenderLayer { private _renderBlurCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void { this._ctx.save(); - this.drawSquareAtCell(x, y, this.colors.cursor); + this.drawRectAtCell(x, y, charData[CHAR_DATA_WIDTH_INDEX], 1, this.colors.cursor); this._ctx.restore(); } } @@ -295,8 +326,9 @@ class CursorBlinkStateManager { } } - public resume(): void { + public resume(terminal: ITerminal): void { this._animationTimeRestarted = null; this._restartInterval(); + this.restartBlinkAnimation(terminal); } } diff --git a/src/renderer/ForegroundRenderLayer.ts b/src/renderer/ForegroundRenderLayer.ts index f4bc1e77..182c9ead 100644 --- a/src/renderer/ForegroundRenderLayer.ts +++ b/src/renderer/ForegroundRenderLayer.ts @@ -100,7 +100,9 @@ export class ForegroundRenderLayer extends BaseRenderLayer { this.drawBottomLineAtCell(x, y); } - this.drawChar(terminal, char, code, x, y, fg); + const width: number = charData[CHAR_DATA_WIDTH_INDEX]; + + this.drawChar(terminal, char, code, width, x, y, fg); this._ctx.restore(); } From b8732a25a3a9b3560454ded301bdd2f92009fea5 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 2 Sep 2017 10:37:51 -0700 Subject: [PATCH 162/378] Support Safari Safari lacks support for window.createImageBitmap, fallback to HTMLCanvasElement in thi case. --- src/renderer/BaseRenderLayer.ts | 15 ++++++++++++--- src/renderer/CharAtlas.ts | 18 +++++++++++++++--- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 2650af3a..20768c8a 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -14,7 +14,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { // TODO: This should be shared between terminals, but not for static as some // terminals may have different styles - private _charAtlas: ImageBitmap; + private _charAtlas: HTMLCanvasElement | ImageBitmap; constructor( container: HTMLElement, @@ -39,8 +39,17 @@ export abstract class BaseRenderLayer implements IRenderLayer { public onSelectionChanged(terminal: ITerminal, start: [number, number], end: [number, number]): void {} public onThemeChanged(terminal: ITerminal, colorSet: IColorSet): void { + this._refreshCharAtlas(terminal, colorSet); + } + + private _refreshCharAtlas(terminal: ITerminal, colorSet: IColorSet): void { this._charAtlas = null; - acquireCharAtlas(terminal, this.colors).then(bitmap => this._charAtlas = bitmap); + const result = acquireCharAtlas(terminal, this.colors); + if (result instanceof HTMLCanvasElement) { + this._charAtlas = result; + } else { + result.then(bitmap => this._charAtlas = bitmap); + } } public resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void { @@ -52,7 +61,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._canvas.style.height = `${canvasHeight}px`; if (charSizeChanged) { - acquireCharAtlas(terminal, this.colors).then(bitmap => this._charAtlas = bitmap); + this._refreshCharAtlas(terminal, this.colors); } } diff --git a/src/renderer/CharAtlas.ts b/src/renderer/CharAtlas.ts index a9fe8bd3..d25520d1 100644 --- a/src/renderer/CharAtlas.ts +++ b/src/renderer/CharAtlas.ts @@ -12,14 +12,14 @@ interface ICharAtlasConfig { } interface ICharAtlasCacheEntry { - bitmap: Promise; + bitmap: HTMLCanvasElement | Promise; config: ICharAtlasConfig; ownedBy: ITerminal[]; } let charAtlasCache: ICharAtlasCacheEntry[] = []; -export function acquireCharAtlas(terminal: ITerminal, colors: IColorSet): Promise { +export function acquireCharAtlas(terminal: ITerminal, colors: IColorSet): HTMLCanvasElement | Promise { const scaledCharWidth = terminal.charMeasure.width * window.devicePixelRatio; const scaledCharHeight = terminal.charMeasure.height * window.devicePixelRatio; const newConfig = generateConfig(scaledCharWidth, scaledCharHeight, terminal, colors); @@ -101,7 +101,7 @@ class CharAtlasGenerator { this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); } - public generate(scaledCharWidth: number, scaledCharHeight: number, fontSize: number, fontFamily: string, foreground: string, ansiColors: string[]): Promise { + public generate(scaledCharWidth: number, scaledCharHeight: number, fontSize: number, fontFamily: string, foreground: string, ansiColors: string[]): HTMLCanvasElement | Promise { const cellWidth = scaledCharWidth + CHAR_ATLAS_CELL_SPACING; const cellHeight = scaledCharHeight + CHAR_ATLAS_CELL_SPACING; this._canvas.width = 255 * cellWidth; @@ -133,6 +133,18 @@ class CharAtlasGenerator { this._ctx.restore(); const charAtlasImageData = this._ctx.getImageData(0, 0, this._canvas.width, this._canvas.height); + + // Support is patchy for createImageBitmap at the moment, pass a canvas back + // if support is lacking as drawImage works there too. + if (!('createImageBitmap' in window)) { + // Regenerate canvas and context as they are now owned by the char atlas + const result = this._canvas; + this._canvas = document.createElement('canvas'); + this._ctx = this._canvas.getContext('2d'); + this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); + return result; + } + const promise = window.createImageBitmap(charAtlasImageData); // Clear the rect while the promise is in progress this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); From ee4b71c8e76a0eb09bb8c4480bfe7ab8a310ccf7 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 2 Sep 2017 10:51:08 -0700 Subject: [PATCH 163/378] Remove Terminal.options.colors It wasn't being used --- src/Interfaces.ts | 1 - src/Terminal.ts | 14 -------------- 2 files changed, 15 deletions(-) diff --git a/src/Interfaces.ts b/src/Interfaces.ts index b6ba08db..0b50161c 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -117,7 +117,6 @@ export interface ITerminalOptions { bellSound?: string; bellStyle?: string; cancelEvents?: boolean; - colors?: string[]; cols?: number; convertEol?: boolean; cursorBlink?: boolean; diff --git a/src/Terminal.ts b/src/Terminal.ts index 7d2e8fd9..c572fefb 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -136,7 +136,6 @@ const vcolors: number[][] = (function(): number[][] { })(); const DEFAULT_OPTIONS: ITerminalOptions = { - colors: defaultColors, convertEol: false, termName: 'xterm', geometry: [80, 24], @@ -300,19 +299,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this[key] = this.options[key]; }); - if (this.options.colors.length === 8) { - this.options.colors = this.options.colors.concat(_colors.slice(8)); - } else if (this.options.colors.length === 16) { - this.options.colors = this.options.colors.concat(_colors.slice(16)); - } else if (this.options.colors.length === 10) { - this.options.colors = this.options.colors.slice(0, -2).concat( - _colors.slice(8, -2), this.options.colors.slice(-2)); - } else if (this.options.colors.length === 18) { - this.options.colors = this.options.colors.concat( - _colors.slice(16, -2), this.options.colors.slice(-2)); - } - this.colors = this.options.colors; - // this.context = options.context || window; // this.document = options.document || document; // TODO: WHy not document.body? From 9cabb93b8b11e41f6eeac45943b597cbd0d0f691 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 2 Sep 2017 11:08:43 -0700 Subject: [PATCH 164/378] Organize temporary color handling code --- src/Terminal.ts | 210 +++++++++++--------------- src/renderer/BaseRenderLayer.ts | 5 +- src/renderer/ColorManager.ts | 2 +- src/renderer/ForegroundRenderLayer.ts | 1 - 4 files changed, 95 insertions(+), 123 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index c572fefb..59d39e5a 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -41,6 +41,7 @@ import { getRawByteCoords } from './utils/Mouse'; import { CustomKeyEventHandler, Charset, LinkMatcherHandler, LinkMatcherValidationCallback, CharData, LineData } from './Types'; import { ITerminal, IBrowser, ITerminalOptions, IInputHandlingTerminal, ILinkMatcherOptions, IViewport, ICompositionHelper, ITheme } from './Interfaces'; import { BellSound } from './utils/Sounds'; +import { DEFAULT_ANSI_COLORS } from './renderer/ColorManager'; // Declare for RequireJS in loadAddon declare var define: any; @@ -61,80 +62,6 @@ const WRITE_BUFFER_PAUSE_THRESHOLD = 5; */ const WRITE_BATCH_SIZE = 300; -// TODO: Most of the color code should be removed after truecolor is implemented -// Colors 0-15 -const tangoColors: string[] = [ - // dark: - '#2e3436', - '#cc0000', - '#4e9a06', - '#c4a000', - '#3465a4', - '#75507b', - '#06989a', - '#d3d7cf', - // bright: - '#555753', - '#ef2929', - '#8ae234', - '#fce94f', - '#729fcf', - '#ad7fa8', - '#34e2e2', - '#eeeeec' -]; - -// Colors 0-15 + 16-255 -// Much thanks to TooTallNate for writing this. -const defaultColors: string[] = (function(): string[] { - let colors = tangoColors.slice(); - let r = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]; - let i; - - // 16-231 - i = 0; - for (; i < 216; i++) { - out(r[(i / 36) % 6 | 0], r[(i / 6) % 6 | 0], r[i % 6]); - } - - // 232-255 (grey) - i = 0; - let c: number; - for (; i < 24; i++) { - c = 8 + i * 10; - out(c, c, c); - } - - function out(r: number, g: number, b: number): void { - colors.push('#' + hex(r) + hex(g) + hex(b)); - } - - function hex(c: number): string { - let s = c.toString(16); - return s.length < 2 ? '0' + s : s; - } - - return colors; -})(); - -const _colors: string[] = defaultColors.slice(); - -const vcolors: number[][] = (function(): number[][] { - const out: number[][] = []; - let color; - - for (let i = 0; i < 256; i++) { - color = parseInt(defaultColors[i].substring(1), 16); - out.push([ - (color >> 16) & 0xff, - (color >> 8) & 0xff, - color & 0xff - ]); - } - - return out; -})(); - const DEFAULT_OPTIONS: ITerminalOptions = { convertEol: false, termName: 'xterm', @@ -2139,44 +2066,9 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT return false; } - // Expose to InputHandler - // TODO: Revise when truecolor is introduced. + // TODO: Remove when true color is implemented public matchColor(r1: number, g1: number, b1: number): number { - const hash = (r1 << 16) | (g1 << 8) | b1; - - if (matchColorCache[hash] != null) { - return matchColorCache[hash]; - } - - let ldiff = Infinity; - let li = -1; - let i = 0; - let c: number[]; - let r2: number; - let g2: number; - let b2: number; - let diff: number; - - for (; i < vcolors.length; i++) { - c = vcolors[i]; - r2 = c[0]; - g2 = c[1]; - b2 = c[2]; - - diff = matchColorDistance(r1, g1, b1, r2, g2, b2); - - if (diff === 0) { - li = i; - break; - } - - if (diff < ldiff) { - ldiff = diff; - li = i; - } - } - - return matchColorCache[hash] = li; + return matchColor_(r1, g1, b1); } private visualBell(): boolean { @@ -2235,17 +2127,95 @@ function isThirdLevelShift(browser: IBrowser, ev: KeyboardEvent): boolean { return thirdLevelKey && (!ev.keyCode || ev.keyCode > 47); } -const matchColorCache: {[colorRGBHash: number]: number} = {}; - -// http://stackoverflow.com/questions/1633828 -const matchColorDistance = function(r1: number, g1: number, b1: number, r2: number, g2: number, b2: number): number { - return Math.pow(30 * (r1 - r2), 2) - + Math.pow(59 * (g1 - g2), 2) - + Math.pow(11 * (b1 - b2), 2); -}; - function wasMondifierKeyOnlyEvent(ev: KeyboardEvent): boolean { return ev.keyCode === 16 || // Shift ev.keyCode === 17 || // Ctrl ev.keyCode === 18; // Alt } + +/** + * TODO: + * The below color-related code can be removed when true color is implemented. + * It's only purpose is to match true color requests with the closest matching + * ANSI color code. + */ + +// Colors 0-15 + 16-255 +// Much thanks to TooTallNate for writing this. +const vcolors: number[][] = (function(): number[][] { + const result = DEFAULT_ANSI_COLORS.map(c => { + c = c.substring(1); + return [ + parseInt(c.substring(0, 2), 16), + parseInt(c.substring(2, 4), 16), + parseInt(c.substring(4, 6), 16) + ]; + }); + const r = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]; + + // 16-231 + for (let i = 0; i < 216; i++) { + result.push([ + r[(i / 36) % 6 | 0], + r[(i / 6) % 6 | 0], + r[i % 6] + ]); + } + + // 232-255 (grey) + let c: number; + for (let i = 0; i < 24; i++) { + c = 8 + i * 10; + result.push([c, c, c]); + } + + return result; +})(); + +const matchColorCache: {[colorRGBHash: number]: number} = {}; + +// http://stackoverflow.com/questions/1633828 +function matchColorDistance(r1: number, g1: number, b1: number, r2: number, g2: number, b2: number): number { + return Math.pow(30 * (r1 - r2), 2) + + Math.pow(59 * (g1 - g2), 2) + + Math.pow(11 * (b1 - b2), 2); +}; + + +function matchColor_(r1: number, g1: number, b1: number): number { + const hash = (r1 << 16) | (g1 << 8) | b1; + + if (matchColorCache[hash] != null) { + return matchColorCache[hash]; + } + + let ldiff = Infinity; + let li = -1; + let i = 0; + let c: number[]; + let r2: number; + let g2: number; + let b2: number; + let diff: number; + + for (; i < vcolors.length; i++) { + c = vcolors[i]; + r2 = c[0]; + g2 = c[1]; + b2 = c[2]; + + diff = matchColorDistance(r1, g1, b1, r2, g2, b2); + + if (diff === 0) { + li = i; + break; + } + + if (diff < ldiff) { + ldiff = diff; + li = i; + } + } + + return matchColorCache[hash] = li; +} diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 20768c8a..57427122 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -128,7 +128,10 @@ export abstract class BaseRenderLayer implements IRenderLayer { if (fg < 256) { colorIndex = fg + 1; } - if (code < 256 && (colorIndex > 0 || fg >= 256)) { + const isAscii = code < 256; + const isBasicColor = (colorIndex > 0 && fg < 16); + const isDefaultColor = fg >= 256; + if (isAscii && (isBasicColor || isDefaultColor)) { // ImageBitmap's draw about twice as fast as from a canvas const charAtlasCellWidth = this.scaledCharWidth + CHAR_ATLAS_CELL_SPACING; const charAtlasCellHeight = this.scaledCharHeight + CHAR_ATLAS_CELL_SPACING; diff --git a/src/renderer/ColorManager.ts b/src/renderer/ColorManager.ts index 341e4ca5..6b7f88c1 100644 --- a/src/renderer/ColorManager.ts +++ b/src/renderer/ColorManager.ts @@ -23,7 +23,7 @@ export enum COLOR_CODES { BRIGHT_WHITE = 15 } -const DEFAULT_ANSI_COLORS = [ +export const DEFAULT_ANSI_COLORS = [ // dark: '#2e3436', '#cc0000', diff --git a/src/renderer/ForegroundRenderLayer.ts b/src/renderer/ForegroundRenderLayer.ts index 182c9ead..1f579f91 100644 --- a/src/renderer/ForegroundRenderLayer.ts +++ b/src/renderer/ForegroundRenderLayer.ts @@ -101,7 +101,6 @@ export class ForegroundRenderLayer extends BaseRenderLayer { } const width: number = charData[CHAR_DATA_WIDTH_INDEX]; - this.drawChar(terminal, char, code, width, x, y, fg); this._ctx.restore(); From c92a2f964a403d35e28229f0bacc2019f9cb2510 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 2 Sep 2017 12:27:09 -0700 Subject: [PATCH 165/378] Support lineHeight --- src/Interfaces.ts | 1 + src/SelectionManager.ts | 4 ++-- src/Terminal.ts | 28 ++++++++++++++++++++-------- src/Viewport.ts | 15 ++++++++------- src/addons/fit/fit.js | 4 +++- src/renderer/BaseRenderLayer.ts | 26 +++++++++++++++----------- src/renderer/Renderer.ts | 2 +- src/utils/Mouse.ts | 8 ++++---- typings/xterm.d.ts | 4 ++-- 9 files changed, 56 insertions(+), 36 deletions(-) diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 0b50161c..8af07934 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -127,6 +127,7 @@ export interface ITerminalOptions { fontFamily?: string; geometry?: [number, number]; handler?: (data: string) => void; + lineHeight?: number; rows?: number; screenKeys?: boolean; scrollback?: number; diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index dc58858e..1425aedc 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -273,7 +273,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager * @param event The mouse event. */ private _getMouseBufferCoords(event: MouseEvent): [number, number] { - const coords = Mouse.getCoords(event, this._rowContainer, this._charMeasure, this._terminal.cols, this._terminal.rows, true); + const coords = Mouse.getCoords(event, this._rowContainer, this._charMeasure, this._terminal.options.lineHeight, this._terminal.cols, this._terminal.rows, true); if (!coords) { return null; } @@ -293,7 +293,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager */ private _getMouseEventScrollAmount(event: MouseEvent): number { let offset = Mouse.getCoordsRelativeToElement(event, this._rowContainer)[1]; - const terminalHeight = this._terminal.rows * this._charMeasure.height; + 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 59d39e5a..9dba6701 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -72,6 +72,7 @@ const DEFAULT_OPTIONS: ITerminalOptions = { bellStyle: 'none', fontFamily: 'courier-new, courier, monospace', fontSize: 15, + lineHeight: 1.0, scrollback: 1000, screenKeys: false, debug: false, @@ -356,19 +357,24 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT } break; case 'cursorStyle': - if (!value) { - value = 'block'; - } - break; + if (!value) { + value = 'block'; + } + break; + case 'lineHeight': + if (value < 1) { + console.warn(`${key} cannot be less than 1, value: ${value}`); + return; + } case 'tabStopWidth': if (value < 1) { - console.warn(`tabStopWidth cannot be less than 1, value: ${value}`); + console.warn(`${key} cannot be less than 1, value: ${value}`); return; } break; case 'scrollback': if (value < 0) { - console.warn(`scrollback cannot be less than 0, value: ${value}`); + console.warn(`${key} cannot be less than 0, value: ${value}`); return; } if (this.options[key] !== value) { @@ -395,6 +401,12 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.renderer.clear(); this.charMeasure.measure(this.options); break; + case 'lineHeight': + // When the font changes the size of the cells may change which requires a renderer clear + this.renderer.clear(); + this.renderer.onResize(this.cols, this.rows); + this.refresh(0, this.rows - 1); + // this.charMeasure.measure(this.options); case 'scrollback': this.buffers.resize(this.cols, this.rows); this.viewport.syncScrollArea(); @@ -709,7 +721,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT button = getButton(ev); // get mouse coordinates - pos = getRawByteCoords(ev, self.rowContainer, self.charMeasure, self.cols, self.rows); + pos = getRawByteCoords(ev, self.rowContainer, self.charMeasure, self.options.lineHeight, self.cols, self.rows); if (!pos) return; sendEvent(button, pos); @@ -735,7 +747,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 = getRawByteCoords(ev, self.rowContainer, self.charMeasure, self.cols, self.rows); + let pos = getRawByteCoords(ev, self.rowContainer, self.charMeasure, self.options.lineHeight, self.cols, self.rows); if (!pos) return; // buttons marked as motions diff --git a/src/Viewport.ts b/src/Viewport.ts index 4ad4ddec..b061eeb6 100644 --- a/src/Viewport.ts +++ b/src/Viewport.ts @@ -46,19 +46,20 @@ export class Viewport implements IViewport { */ private refresh(): void { if (this.charMeasure.height > 0) { - const rowHeightChanged = this.charMeasure.height !== this.currentRowHeight; + const lineHeight = Math.ceil(this.charMeasure.height * this.terminal.options.lineHeight); + const rowHeightChanged = lineHeight !== this.currentRowHeight; if (rowHeightChanged) { - this.currentRowHeight = this.charMeasure.height; - this.viewportElement.style.lineHeight = this.charMeasure.height + 'px'; - this.terminal.rowContainer.style.lineHeight = this.charMeasure.height + 'px'; + this.currentRowHeight = lineHeight; + this.viewportElement.style.lineHeight = lineHeight + 'px'; + this.terminal.rowContainer.style.lineHeight = lineHeight + 'px'; } const viewportHeightChanged = this.lastRecordedViewportHeight !== this.terminal.rows; if (rowHeightChanged || viewportHeightChanged) { this.lastRecordedViewportHeight = this.terminal.rows; - this.viewportElement.style.height = this.charMeasure.height * this.terminal.rows + 'px'; + this.viewportElement.style.height = lineHeight * this.terminal.rows + 'px'; this.terminal.selectionContainer.style.height = this.viewportElement.style.height; } - this.scrollArea.style.height = (this.charMeasure.height * this.lastRecordedBufferLength) + 'px'; + this.scrollArea.style.height = (lineHeight * this.lastRecordedBufferLength) + 'px'; } } @@ -75,7 +76,7 @@ export class Viewport implements IViewport { this.refresh(); } else { // If size has changed, refresh viewport - if (this.charMeasure.height !== this.currentRowHeight) { + if (Math.ceil(this.charMeasure.height * this.terminal.options.lineHeight) !== this.currentRowHeight) { this.refresh(); } } diff --git a/src/addons/fit/fit.js b/src/addons/fit/fit.js index 1e46932c..b774cebf 100644 --- a/src/addons/fit/fit.js +++ b/src/addons/fit/fit.js @@ -49,7 +49,7 @@ var geometry = { cols: parseInt(availableWidth / term.charMeasure.width, 10), - rows: parseInt(availableHeight / term.charMeasure.height, 10) + rows: parseInt(availableHeight / (term.charMeasure.height * term.getOption('lineHeight')), 10) }; return geometry; @@ -62,6 +62,8 @@ var geometry = exports.proposeGeometry(term); if (geometry) { + // Force a full render + term.renderer.clear(); term.resize(geometry.cols, geometry.rows); } }, 0); diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 57427122..912d351f 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -11,6 +11,8 @@ export abstract class BaseRenderLayer implements IRenderLayer { protected _ctx: CanvasRenderingContext2D; private scaledCharWidth: number; private scaledCharHeight: number; + private scaledLineHeight: number; + private scaledLineDrawY: number; // TODO: This should be shared between terminals, but not for static as some // terminals may have different styles @@ -55,6 +57,8 @@ export abstract class BaseRenderLayer implements IRenderLayer { public resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void { this.scaledCharWidth = terminal.charMeasure.width * window.devicePixelRatio; this.scaledCharHeight = terminal.charMeasure.height * window.devicePixelRatio; + this.scaledLineHeight = Math.ceil(this.scaledCharHeight * terminal.options.lineHeight); + this.scaledLineDrawY = terminal.options.lineHeight === 1 ? 0 : Math.round((this.scaledLineHeight - this.scaledCharHeight) / 2); this._canvas.width = canvasWidth * window.devicePixelRatio; this._canvas.height = canvasHeight * window.devicePixelRatio; this._canvas.style.width = `${canvasWidth}px`; @@ -68,13 +72,13 @@ export abstract class BaseRenderLayer implements IRenderLayer { public abstract reset(terminal: ITerminal): void; protected fillCells(startCol: number, startRow: number, colWidth: number, colHeight: number): void { - this._ctx.fillRect(startCol * this.scaledCharWidth, startRow * this.scaledCharHeight, colWidth * this.scaledCharWidth, colHeight * this.scaledCharHeight); + this._ctx.fillRect(startCol * this.scaledCharWidth, startRow * this.scaledLineHeight, colWidth * this.scaledCharWidth, colHeight * this.scaledLineHeight); } protected drawBottomLineAtCell(x: number, y: number): void { this._ctx.fillRect( x * this.scaledCharWidth, - (y + 1) * this.scaledCharHeight - window.devicePixelRatio - 1 /* Ensure it's drawn within the cell */, + (y + 1) * this.scaledLineHeight - window.devicePixelRatio - 1 /* Ensure it's drawn within the cell */, this.scaledCharWidth, window.devicePixelRatio); } @@ -82,9 +86,9 @@ export abstract class BaseRenderLayer implements IRenderLayer { protected drawLeftLineAtCell(x: number, y: number): void { this._ctx.fillRect( x * this.scaledCharWidth, - y * this.scaledCharHeight, + y * this.scaledLineHeight, window.devicePixelRatio, - this.scaledCharHeight); + this.scaledLineHeight); } protected drawRectAtCell(x: number, y: number, width: number, height: number, color: string): void { @@ -92,9 +96,9 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._ctx.lineWidth = window.devicePixelRatio; this._ctx.strokeRect( x * this.scaledCharWidth + window.devicePixelRatio / 2, - y * this.scaledCharHeight + (window.devicePixelRatio / 2), + y * this.scaledLineHeight + (window.devicePixelRatio / 2), (width * this.scaledCharWidth) - window.devicePixelRatio, - (height * this.scaledCharHeight) - window.devicePixelRatio); + (height * this.scaledLineHeight) - window.devicePixelRatio); } protected clearAll(): void { @@ -102,7 +106,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { } protected clearCells(startCol: number, startRow: number, colWidth: number, colHeight: number): void { - this._ctx.clearRect(startCol * this.scaledCharWidth, startRow * this.scaledCharHeight, colWidth * this.scaledCharWidth, colHeight * this.scaledCharHeight); + this._ctx.clearRect(startCol * this.scaledCharWidth, startRow * this.scaledLineHeight, colWidth * this.scaledCharWidth, colHeight * this.scaledLineHeight); } protected drawCharTrueColor(terminal: ITerminal, charData: CharData, x: number, y: number, color: string): void { @@ -116,7 +120,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { // can bleed into other cells. This code will clip the following fillText, // ensuring that its contents don't go beyond the cell bounds. this._ctx.beginPath(); - this._ctx.rect(x * this.scaledCharWidth, y * this.scaledCharHeight, charData[CHAR_DATA_WIDTH_INDEX] * this.scaledCharWidth, this.scaledCharHeight); + this._ctx.rect(x * this.scaledCharWidth, y * this.scaledLineHeight + this.scaledLineDrawY, charData[CHAR_DATA_WIDTH_INDEX] * this.scaledCharWidth, this.scaledCharHeight); this._ctx.clip(); this._ctx.fillText(charData[CHAR_DATA_CHAR_INDEX], x * this.scaledCharWidth, y * this.scaledCharHeight); @@ -137,7 +141,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { const charAtlasCellHeight = this.scaledCharHeight + CHAR_ATLAS_CELL_SPACING; this._ctx.drawImage(this._charAtlas, code * charAtlasCellWidth, colorIndex * charAtlasCellHeight, this.scaledCharWidth, this.scaledCharHeight, - x * this.scaledCharWidth, y * this.scaledCharHeight, this.scaledCharWidth, this.scaledCharHeight); + x * this.scaledCharWidth, y * this.scaledLineHeight + this.scaledLineDrawY, this.scaledCharWidth, this.scaledCharHeight); } else { this._drawUncachedChar(terminal, char, width, fg, x, y); } @@ -164,11 +168,11 @@ export abstract class BaseRenderLayer implements IRenderLayer { // can bleed into other cells. This code will clip the following fillText, // ensuring that its contents don't go beyond the cell bounds. this._ctx.beginPath(); - this._ctx.rect(x * this.scaledCharWidth, y * this.scaledCharHeight, width * this.scaledCharWidth, this.scaledCharHeight); + this._ctx.rect(x * this.scaledCharWidth, y * this.scaledLineHeight + this.scaledLineDrawY, width * this.scaledCharWidth, this.scaledCharHeight); this._ctx.clip(); // Draw the character - this._ctx.fillText(char, x * this.scaledCharWidth, y * this.scaledCharHeight); + this._ctx.fillText(char, x * this.scaledCharWidth, y * this.scaledLineHeight + this.scaledLineDrawY); this._ctx.restore(); } } diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index cc85c7e7..b3df2b7a 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -46,7 +46,7 @@ export class Renderer { public onResize(cols: number, rows: number): void { const width = this._terminal.charMeasure.width * this._terminal.cols; - const height = this._terminal.charMeasure.height * this._terminal.rows; + const height = Math.ceil(this._terminal.charMeasure.height * this._terminal.options.lineHeight) * this._terminal.rows; this._renderLayers.forEach(l => l.resize(this._terminal, width, height, false)); } diff --git a/src/utils/Mouse.ts b/src/utils/Mouse.ts index c61624e9..f25a6b2e 100644 --- a/src/utils/Mouse.ts +++ b/src/utils/Mouse.ts @@ -36,7 +36,7 @@ export function getCoordsRelativeToElement(event: MouseEvent, element: HTMLEleme * apply an offset to the x value such that the left half of the cell will * select that cell and the right half will select the next cell. */ -export function getCoords(event: MouseEvent, rowContainer: HTMLElement, charMeasure: CharMeasure, colCount: number, rowCount: number, isSelection?: boolean): [number, number] { +export function getCoords(event: MouseEvent, rowContainer: HTMLElement, charMeasure: CharMeasure, lineHeight: number, colCount: number, rowCount: number, isSelection?: boolean): [number, number] { // Coordinates cannot be measured if charMeasure has not been initialized if (!charMeasure.width || !charMeasure.height) { return null; @@ -49,7 +49,7 @@ export function getCoords(event: MouseEvent, rowContainer: HTMLElement, charMeas // Convert to cols/rows. coords[0] = Math.ceil((coords[0] + (isSelection ? charMeasure.width / 2 : 0)) / charMeasure.width); - coords[1] = Math.ceil(coords[1] / charMeasure.height); + coords[1] = Math.ceil(coords[1] / Math.ceil(charMeasure.height * lineHeight)); // Ensure coordinates are within the terminal viewport. coords[0] = Math.min(Math.max(coords[0], 1), colCount + 1); @@ -68,8 +68,8 @@ export function getCoords(event: MouseEvent, rowContainer: HTMLElement, charMeas * @param colCount The number of columns in the terminal. * @param rowCount The number of rows in the terminal. */ -export function getRawByteCoords(event: MouseEvent, rowContainer: HTMLElement, charMeasure: CharMeasure, colCount: number, rowCount: number): { x: number, y: number } { - const coords = getCoords(event, rowContainer, charMeasure, colCount, rowCount); +export function getRawByteCoords(event: MouseEvent, rowContainer: HTMLElement, charMeasure: CharMeasure, lineHeight: number, colCount: number, rowCount: number): { x: number, y: number } { + const coords = getCoords(event, rowContainer, charMeasure, lineHeight, colCount, rowCount); let x = coords[0]; let y = coords[1]; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index dce15bca..2956c937 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -328,7 +328,7 @@ declare module 'xterm' { * Retrieves an option's value from the terminal. * @param key The option key. */ - getOption(key: 'cols' | 'fontSize' | 'rows' | 'tabStopWidth' | 'scrollback'): number; + getOption(key: 'cols' | 'fontSize' | 'lineHeight' | 'rows' | 'tabStopWidth' | 'scrollback'): number; /** * Retrieves an option's value from the terminal. * @param key The option key. @@ -380,7 +380,7 @@ declare module 'xterm' { * @param key The option key. * @param value The option value. */ - setOption(key: 'cols' | 'fontSize' | 'rows' | 'tabStopWidth' | 'scrollback', value: number): void; + setOption(key: 'cols' | 'fontSize' | 'lineHeight' | 'rows' | 'tabStopWidth' | 'scrollback', value: number): void; /** * Sets an option on the terminal. * @param key The option key. From 66d41feab610cfcfb72b18bd6c52443736d61419 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 2 Sep 2017 12:31:26 -0700 Subject: [PATCH 166/378] Expose setTheme through .d.ts --- typings/xterm.d.ts | 50 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 2956c937..72206c82 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -58,6 +58,50 @@ interface ITerminalOptions { tabStopWidth?: number; } +/** + * Contains colors to theme the terminal with. + */ +interface ITheme { + /** The default foreground color */ + foreground?: string, + /** The default background color */ + background?: string, + /** The cursor color */ + cursor?: string, + /** ANSI black (eg. `\x1b[30m`) */ + black?: string, + /** ANSI red (eg. `\x1b[31m`) */ + red?: string, + /** ANSI green (eg. `\x1b[32m`) */ + green?: string, + /** ANSI yellow (eg. `\x1b[33m`) */ + yellow?: string, + /** ANSI blue (eg. `\x1b[34m`) */ + blue?: string, + /** ANSI magenta (eg. `\x1b[35m`) */ + magenta?: string, + /** ANSI cyan (eg. `\x1b[36m`) */ + cyan?: string, + /** ANSI white (eg. `\x1b[37m`) */ + white?: string, + /** ANSI bright black (eg. `\x1b[1;30m`) */ + brightBlack?: string, + /** ANSI bright red (eg. `\x1b[1;31m`) */ + brightRed?: string, + /** ANSI bright green (eg. `\x1b[1;32m`) */ + brightGreen?: string, + /** ANSI bright yellow (eg. `\x1b[1;33m`) */ + brightYellow?: string, + /** ANSI bright blue (eg. `\x1b[1;34m`) */ + brightBlue?: string, + /** ANSI bright magenta (eg. `\x1b[1;35m`) */ + brightMagenta?: string, + /** ANSI bright cyan (eg. `\x1b[1;36m`) */ + brightCyan?: string, + /** ANSI bright white (eg. `\x1b[1;37m`) */ + brightWhite?: string +} + /** * An object containing options for a link matcher. */ @@ -400,6 +444,12 @@ declare module 'xterm' { */ setOption(key: string, value: any): void; + /** + * Sets the theme of the terminal. + * @param theme The theme to use. + */ + setTheme(theme: ITheme): void; + /** * Tells the renderer to refresh terminal content between two rows * (inclusive) at the next opportunity. From 24a98f2fa860d19b5a91d8cff26b1189c2a876dc Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 2 Sep 2017 13:26:05 -0700 Subject: [PATCH 167/378] Remove rowContainer and children, disable Linkifier --- src/Interfaces.ts | 2 - src/SelectionManager.test.ts | 6 +- src/SelectionManager.ts | 13 ++-- src/Terminal.ts | 113 +++++++++++------------------------ src/Viewport.test.ts | 12 ---- src/Viewport.ts | 2 +- src/addons/fit/fit.js | 20 +++---- src/utils/Mouse.ts | 12 ++-- 8 files changed, 57 insertions(+), 123 deletions(-) diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 8af07934..41fc8bda 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -19,7 +19,6 @@ export interface IBrowser { export interface ITerminal extends IEventEmitter { element: HTMLElement; - rowContainer: HTMLElement; selectionContainer: HTMLElement; selectionManager: ISelectionManager; charMeasure: ICharMeasure; @@ -28,7 +27,6 @@ export interface ITerminal extends IEventEmitter { cols: number; browser: IBrowser; writeBuffer: string[]; - children: HTMLElement[]; cursorHidden: boolean; cursorState: number; defAttr: number; diff --git a/src/SelectionManager.test.ts b/src/SelectionManager.test.ts index 4774b3bb..b5a9d552 100644 --- a/src/SelectionManager.test.ts +++ b/src/SelectionManager.test.ts @@ -17,10 +17,9 @@ class TestSelectionManager extends SelectionManager { constructor( terminal: ITerminal, buffer: IBuffer, - rowContainer: HTMLElement, charMeasure: CharMeasure ) { - super(terminal, buffer, rowContainer, charMeasure); + super(terminal, buffer, charMeasure); } public get model(): SelectionModel { return this._model; } @@ -48,7 +47,6 @@ describe('SelectionManager', () => { dom = new jsdom.JSDOM(''); window = dom.window; document = window.document; - rowContainer = document.createElement('div'); terminal = new MockTerminal(); terminal.cols = 80; terminal.rows = 2; @@ -56,7 +54,7 @@ describe('SelectionManager', () => { terminal.buffers = new BufferSet(terminal); terminal.buffer = terminal.buffers.active; buffer = terminal.buffer; - selectionManager = new TestSelectionManager(terminal, buffer, rowContainer, null); + selectionManager = new TestSelectionManager(terminal, buffer, null); }); function stringToRow(text: string): LineData { diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 1425aedc..e84bde68 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -98,7 +98,6 @@ export class SelectionManager extends EventEmitter implements ISelectionManager constructor( private _terminal: ITerminal, private _buffer: IBuffer, - private _rowContainer: HTMLElement, private _charMeasure: CharMeasure ) { super(); @@ -273,7 +272,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager * @param event The mouse event. */ private _getMouseBufferCoords(event: MouseEvent): [number, number] { - const coords = Mouse.getCoords(event, this._rowContainer, this._charMeasure, this._terminal.options.lineHeight, this._terminal.cols, this._terminal.rows, true); + const coords = Mouse.getCoords(event, this._terminal.element, this._charMeasure, this._terminal.options.lineHeight, this._terminal.cols, this._terminal.rows, true); if (!coords) { return null; } @@ -292,7 +291,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager * @param event The mouse event. */ private _getMouseEventScrollAmount(event: MouseEvent): number { - let offset = Mouse.getCoordsRelativeToElement(event, this._rowContainer)[1]; + let offset = Mouse.getCoordsRelativeToElement(event, this._terminal.element)[1]; const terminalHeight = this._terminal.rows * Math.ceil(this._charMeasure.height * this._terminal.options.lineHeight); if (offset >= 0 && offset <= terminalHeight) { return 0; @@ -361,8 +360,8 @@ export class SelectionManager extends EventEmitter implements ISelectionManager */ private _addMouseDownListeners(): void { // Listen on the document so that dragging outside of viewport works - this._rowContainer.ownerDocument.addEventListener('mousemove', this._mouseMoveListener); - this._rowContainer.ownerDocument.addEventListener('mouseup', this._mouseUpListener); + this._terminal.element.ownerDocument.addEventListener('mousemove', this._mouseMoveListener); + this._terminal.element.ownerDocument.addEventListener('mouseup', this._mouseUpListener); this._dragScrollIntervalTimer = setInterval(() => this._dragScroll(), DRAG_SCROLL_INTERVAL); } @@ -370,8 +369,8 @@ export class SelectionManager extends EventEmitter implements ISelectionManager * Removes the listeners that are registered when mousedown is triggered. */ private _removeMouseDownListeners(): void { - this._rowContainer.ownerDocument.removeEventListener('mousemove', this._mouseMoveListener); - this._rowContainer.ownerDocument.removeEventListener('mouseup', this._mouseUpListener); + this._terminal.element.ownerDocument.removeEventListener('mousemove', this._mouseMoveListener); + this._terminal.element.ownerDocument.removeEventListener('mouseup', this._mouseUpListener); clearInterval(this._dragScrollIntervalTimer); this._dragScrollIntervalTimer = null; } diff --git a/src/Terminal.ts b/src/Terminal.ts index 9dba6701..fec7fdca 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -31,7 +31,7 @@ import { InputHandler } from './InputHandler'; import { Parser } from './Parser'; // import { Renderer } from './Renderer'; import { Renderer } from './renderer/Renderer'; -import { Linkifier } from './Linkifier'; +// import { Linkifier } from './Linkifier'; import { SelectionManager } from './SelectionManager'; import { CharMeasure } from './utils/CharMeasure'; import * as Browser from './utils/Browser'; @@ -87,7 +87,6 @@ const DEFAULT_OPTIONS: ITerminalOptions = { export class Terminal extends EventEmitter implements ITerminal, IInputHandlingTerminal { public textarea: HTMLTextAreaElement; public element: HTMLElement; - public rowContainer: HTMLElement; /** * The HTMLElement that the terminal is created in, set by Terminal.open. @@ -145,7 +144,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT public urxvtMouse: boolean; // misc - public children: HTMLElement[]; private refreshStart: number; private refreshEnd: number; public savedCols: number; @@ -187,7 +185,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT private parser: Parser; private renderer: Renderer; public selectionManager: SelectionManager; - private linkifier: Linkifier; + // private linkifier: Linkifier; public buffers: BufferSet; public buffer: Buffer; public viewport: IViewport; @@ -284,7 +282,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT // Reuse renderer if the Terminal is being recreated via a reset call. this.renderer = this.renderer || null; this.selectionManager = this.selectionManager || null; - this.linkifier = this.linkifier || new Linkifier(); + // this.linkifier = this.linkifier || new Linkifier(); // Create the terminal's buffers and set the current buffer this.buffers = new BufferSet(this); @@ -539,22 +537,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.on('refresh', (data) => this.queueLinkification(data.start, data.end)); } - /** - * Insert the given row to the terminal or produce a new one - * if no row argument is passed. Return the inserted row. - * @param {HTMLElement} row (optional) The row to append to the terminal. - */ - private insertRow(row?: HTMLElement): HTMLElement { - if (typeof row !== 'object') { - row = document.createElement('div'); - } - - this.rowContainer.appendChild(row); - this.children.push(row); - - return row; - }; - /** * Opens the terminal within an element. * @@ -597,13 +579,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.selectionContainer.classList.add('xterm-selection'); this.element.appendChild(this.selectionContainer); - // Create the container that will hold the lines of the terminal and then - // produce the lines the lines. - this.rowContainer = document.createElement('div'); - this.rowContainer.classList.add('xterm-rows'); - this.element.appendChild(this.rowContainer); - this.children = []; - this.linkifier.attachToDom(document, this.children); + // TODO: Re-enable linkifier + // this.linkifier.attachToDom(document, this.children); // Create the container that will hold helpers like the textarea for // capturing DOM Events. Then produce the helpers. @@ -629,9 +606,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.charSizeStyleElement = document.createElement('style'); this.helperContainer.appendChild(this.charSizeStyleElement); - for (; i < this.rows; i++) { - this.insertRow(); - } this.parent.appendChild(this.element); this.charMeasure = new CharMeasure(document, this.helperContainer); @@ -648,7 +622,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.renderer.queueRefresh(0, this.rows - 1); }); - this.selectionManager = new SelectionManager(this, this.buffer, this.rowContainer, this.charMeasure); + this.selectionManager = new SelectionManager(this, this.buffer, this.charMeasure); this.element.addEventListener('mousedown', (e: MouseEvent) => this.selectionManager.onMouseDown(e)); this.selectionManager.on('refresh', data => this.renderer.onSelectionChanged(data.start, data.end)); this.selectionManager.on('newselection', text => { @@ -721,7 +695,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT button = getButton(ev); // get mouse coordinates - pos = getRawByteCoords(ev, self.rowContainer, self.charMeasure, self.options.lineHeight, self.cols, self.rows); + pos = getRawByteCoords(ev, self.element, self.charMeasure, self.options.lineHeight, self.cols, self.rows); if (!pos) return; sendEvent(button, pos); @@ -747,7 +721,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 = getRawByteCoords(ev, self.rowContainer, self.charMeasure, self.options.lineHeight, self.cols, self.rows); + let pos = getRawByteCoords(ev, self.element, self.charMeasure, self.options.lineHeight, self.cols, self.rows); if (!pos) return; // buttons marked as motions @@ -1033,12 +1007,9 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * @param {number} end The row to end at (between start and this.rows - 1). */ private queueLinkification(start: number, end: number): void { - if (this.linkifier) { - this.linkifier.linkifyRows(0, this.rows); - // for (let i = start; i <= end; i++) { - // this.linkifier.linkifyRow(i); - // } - } + // if (this.linkifier) { + // this.linkifier.linkifyRows(0, this.rows); + // } } /** @@ -1246,12 +1217,12 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * @param handler The handler callback function. */ public setHypertextLinkHandler(handler: LinkMatcherHandler): void { - if (!this.linkifier) { - throw new Error('Cannot attach a hypertext link handler before Terminal.open is called'); - } - this.linkifier.setHypertextLinkHandler(handler); - // Refresh to force links to refresh - this.refresh(0, this.rows - 1); + // if (!this.linkifier) { + // throw new Error('Cannot attach a hypertext link handler before Terminal.open is called'); + // } + // this.linkifier.setHypertextLinkHandler(handler); + // // Refresh to force links to refresh + // this.refresh(0, this.rows - 1); } /** @@ -1261,12 +1232,12 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * be cleared with null. */ public setHypertextValidationCallback(callback: LinkMatcherValidationCallback): void { - if (!this.linkifier) { - throw new Error('Cannot attach a hypertext validation callback before Terminal.open is called'); - } - this.linkifier.setHypertextValidationCallback(callback); - // Refresh to force links to refresh - this.refresh(0, this.rows - 1); + // if (!this.linkifier) { + // throw new Error('Cannot attach a hypertext validation callback before Terminal.open is called'); + // } + // this.linkifier.setHypertextValidationCallback(callback); + // // Refresh to force links to refresh + // this.refresh(0, this.rows - 1); } /** @@ -1280,11 +1251,12 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * @return The ID of the new matcher, this can be used to deregister. */ public registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options?: ILinkMatcherOptions): number { - if (this.linkifier) { - const matcherId = this.linkifier.registerLinkMatcher(regex, handler, options); - this.refresh(0, this.rows - 1); - return matcherId; - } + // if (this.linkifier) { + // const matcherId = this.linkifier.registerLinkMatcher(regex, handler, options); + // this.refresh(0, this.rows - 1); + // return matcherId; + // } + return 0; } /** @@ -1292,11 +1264,11 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * @param matcherId The link matcher's ID (returned after register) */ public deregisterLinkMatcher(matcherId: number): void { - if (this.linkifier) { - if (this.linkifier.deregisterLinkMatcher(matcherId)) { - this.refresh(0, this.rows - 1); - } - } + // if (this.linkifier) { + // if (this.linkifier.deregisterLinkMatcher(matcherId)) { + // this.refresh(0, this.rows - 1); + // } + // } } /** @@ -1794,13 +1766,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT return; } - let line; - let el; - let i; - let j; - let ch; - let addToY; - if (x === this.cols && y === this.rows) { // Check if we still need to measure the char size (fixes #785). if (!this.charMeasure.width || !this.charMeasure.height) { @@ -1814,16 +1779,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.buffers.resize(x, y); - // Adjust rows in the DOM to accurately reflect the new dimensions - while (this.children.length < y) { - this.insertRow(); - } - while (this.children.length > y) { - el = this.children.shift(); - if (!el) continue; - el.parentNode.removeChild(el); - } - this.cols = x; this.rows = y; this.buffers.setupTabStops(this.cols); diff --git a/src/Viewport.test.ts b/src/Viewport.test.ts index 428ef098..8a960fc7 100644 --- a/src/Viewport.test.ts +++ b/src/Viewport.test.ts @@ -55,18 +55,6 @@ describe('Viewport', () => { }); describe('refresh', () => { - it('should set the line-height of the terminal', done => { - // Allow CharMeasure to be initialized - setTimeout(() => { - assert.equal(viewportElement.style.lineHeight, CHARACTER_HEIGHT + 'px'); - assert.equal(terminal.rowContainer.style.lineHeight, CHARACTER_HEIGHT + 'px'); - charMeasure.height = 1; - viewport.refresh(); - assert.equal(viewportElement.style.lineHeight, '1px'); - assert.equal(terminal.rowContainer.style.lineHeight, '1px'); - done(); - }, 0); - }); it('should set the height of the viewport when the line-height changed', () => { terminal.buffer.lines.push(''); terminal.buffer.lines.push(''); diff --git a/src/Viewport.ts b/src/Viewport.ts index b061eeb6..10418fa7 100644 --- a/src/Viewport.ts +++ b/src/Viewport.ts @@ -51,7 +51,7 @@ export class Viewport implements IViewport { if (rowHeightChanged) { this.currentRowHeight = lineHeight; this.viewportElement.style.lineHeight = lineHeight + 'px'; - this.terminal.rowContainer.style.lineHeight = lineHeight + 'px'; + this.terminal.element.style.lineHeight = lineHeight + 'px'; } const viewportHeightChanged = this.lastRecordedViewportHeight !== this.terminal.rows; if (rowHeightChanged || viewportHeightChanged) { diff --git a/src/addons/fit/fit.js b/src/addons/fit/fit.js index b774cebf..4f554579 100644 --- a/src/addons/fit/fit.js +++ b/src/addons/fit/fit.js @@ -35,18 +35,14 @@ if (!term.element.parentElement) { return null; } - var parentElementStyle = window.getComputedStyle(term.element.parentElement), - parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height')), - parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')) - 17), - elementStyle = window.getComputedStyle(term.element), - elementPaddingVer = parseInt(elementStyle.getPropertyValue('padding-top')) + parseInt(elementStyle.getPropertyValue('padding-bottom')), - elementPaddingHor = parseInt(elementStyle.getPropertyValue('padding-right')) + parseInt(elementStyle.getPropertyValue('padding-left')), - availableHeight = parentElementHeight - elementPaddingVer, - availableWidth = parentElementWidth - elementPaddingHor, - container = term.rowContainer, - subjectRow = term.rowContainer.firstElementChild, - contentBuffer = subjectRow.innerHTML; - + var parentElementStyle = window.getComputedStyle(term.element.parentElement); + var parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height')); + var parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')) - 17); + var elementStyle = window.getComputedStyle(term.element); + var elementPaddingVer = parseInt(elementStyle.getPropertyValue('padding-top')) + parseInt(elementStyle.getPropertyValue('padding-bottom')); + var elementPaddingHor = parseInt(elementStyle.getPropertyValue('padding-right')) + parseInt(elementStyle.getPropertyValue('padding-left')); + var availableHeight = parentElementHeight - elementPaddingVer; + var availableWidth = parentElementWidth - elementPaddingHor; var geometry = { cols: parseInt(availableWidth / term.charMeasure.width, 10), rows: parseInt(availableHeight / (term.charMeasure.height * term.getOption('lineHeight')), 10) diff --git a/src/utils/Mouse.ts b/src/utils/Mouse.ts index f25a6b2e..77c422d5 100644 --- a/src/utils/Mouse.ts +++ b/src/utils/Mouse.ts @@ -28,7 +28,7 @@ export function getCoordsRelativeToElement(event: MouseEvent, element: HTMLEleme * is returned as an array in the form [x, y] instead of an object as it's a * little faster and this function is used in some low level code. * @param event The mouse event. - * @param rowContainer The terminal's row container. + * @param element The terminal's container element. * @param charMeasure The char measure object used to determine character sizes. * @param colCount The number of columns in the terminal. * @param rowCount The number of rows n the terminal. @@ -36,13 +36,13 @@ export function getCoordsRelativeToElement(event: MouseEvent, element: HTMLEleme * apply an offset to the x value such that the left half of the cell will * select that cell and the right half will select the next cell. */ -export function getCoords(event: MouseEvent, rowContainer: HTMLElement, charMeasure: CharMeasure, lineHeight: number, colCount: number, rowCount: number, isSelection?: boolean): [number, number] { +export function getCoords(event: MouseEvent, element: HTMLElement, charMeasure: CharMeasure, lineHeight: number, colCount: number, rowCount: number, isSelection?: boolean): [number, number] { // Coordinates cannot be measured if charMeasure has not been initialized if (!charMeasure.width || !charMeasure.height) { return null; } - const coords = getCoordsRelativeToElement(event, rowContainer); + const coords = getCoordsRelativeToElement(event, element); if (!coords) { return null; } @@ -63,13 +63,13 @@ export function getCoords(event: MouseEvent, rowContainer: HTMLElement, charMeas * them to the bounds of the terminal and adding 32 to both the x and y values * as expected by xterm. * @param event The mouse event. - * @param rowContainer The terminal's row container. + * @param element The terminal's container element. * @param charMeasure The char measure object used to determine character sizes. * @param colCount The number of columns in the terminal. * @param rowCount The number of rows in the terminal. */ -export function getRawByteCoords(event: MouseEvent, rowContainer: HTMLElement, charMeasure: CharMeasure, lineHeight: number, colCount: number, rowCount: number): { x: number, y: number } { - const coords = getCoords(event, rowContainer, charMeasure, lineHeight, colCount, rowCount); +export function getRawByteCoords(event: MouseEvent, element: HTMLElement, charMeasure: CharMeasure, lineHeight: number, colCount: number, rowCount: number): { x: number, y: number } { + const coords = getCoords(event, element, charMeasure, lineHeight, colCount, rowCount); let x = coords[0]; let y = coords[1]; From 8039fd35e6df96d146528b1043677d8868ce6af6 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 2 Sep 2017 13:34:01 -0700 Subject: [PATCH 168/378] Remove unused CSS, selectionElement --- src/Interfaces.ts | 1 - src/Terminal.ts | 6 - src/Viewport.ts | 1 - src/xterm.css | 2169 +-------------------------------------------- 4 files changed, 5 insertions(+), 2172 deletions(-) diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 41fc8bda..861720a3 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -19,7 +19,6 @@ export interface IBrowser { export interface ITerminal extends IEventEmitter { element: HTMLElement; - selectionContainer: HTMLElement; selectionManager: ISelectionManager; charMeasure: ICharMeasure; textarea: HTMLTextAreaElement; diff --git a/src/Terminal.ts b/src/Terminal.ts index fec7fdca..8003dce3 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -97,7 +97,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT private body: HTMLBodyElement; private viewportScrollArea: HTMLElement; private viewportElement: HTMLElement; - public selectionContainer: HTMLElement; private helperContainer: HTMLElement; private compositionView: HTMLElement; private charSizeStyleElement: HTMLStyleElement; @@ -574,11 +573,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT // preload audio this.syncBellSound(); - // Create the selection container. - this.selectionContainer = document.createElement('div'); - this.selectionContainer.classList.add('xterm-selection'); - this.element.appendChild(this.selectionContainer); - // TODO: Re-enable linkifier // this.linkifier.attachToDom(document, this.children); diff --git a/src/Viewport.ts b/src/Viewport.ts index 10418fa7..cedcef20 100644 --- a/src/Viewport.ts +++ b/src/Viewport.ts @@ -57,7 +57,6 @@ export class Viewport implements IViewport { if (rowHeightChanged || viewportHeightChanged) { this.lastRecordedViewportHeight = this.terminal.rows; this.viewportElement.style.height = lineHeight * this.terminal.rows + 'px'; - this.terminal.selectionContainer.style.height = this.viewportElement.style.height; } this.scrollArea.style.height = (lineHeight * this.lastRecordedBufferLength) + 'px'; } diff --git a/src/xterm.css b/src/xterm.css index 6a7504f3..30bce803 100644 --- a/src/xterm.css +++ b/src/xterm.css @@ -31,11 +31,12 @@ * other features. */ -/* - * Default style for xterm.js +/** + * Default styles for xterm.js */ .terminal { + /** TODO: Remove colors from xterm.css */ background-color: #000; color: #fff; font-family: courier-new, courier, monospace; @@ -74,66 +75,6 @@ resize: none; } -.terminal a { - color: inherit; - text-decoration: none; -} - -.terminal a:hover { - cursor: pointer; - text-decoration: underline; -} - -.terminal a.xterm-invalid-link:hover { - cursor: text; - text-decoration: none; -} - -.terminal .terminal-cursor { - position: relative; - transition: opacity 150ms ease; -} - -.terminal:not(.focus) .terminal-cursor { - outline: 1px solid #fff; - outline-offset: -1px; -} - -.terminal.xterm-cursor-style-block.focus:not(.xterm-cursor-blink-on) .terminal-cursor { - background-color: #fff; - color: #000; -} - -.terminal.focus.xterm-cursor-style-bar:not(.xterm-cursor-blink-on) .terminal-cursor::before, -.terminal.focus.xterm-cursor-style-underline:not(.xterm-cursor-blink-on) .terminal-cursor::before { - content: ''; - position: absolute; - background-color: #fff; - transition: opacity 150ms ease; -} - -.terminal.focus.xterm-cursor-style-bar:not(.xterm-cursor-blink-on) .terminal-cursor::before { - top: 0; - left: 0; - bottom: 0; - width: 1px; -} - -.terminal.focus.xterm-cursor-style-underline:not(.xterm-cursor-blink-on) .terminal-cursor::before { - bottom: 0; - left: 0; - right: 0; - height: 1px; -} - -.terminal.focus.xterm-cursor-style-block.visual-bell-active .terminal-cursor { - opacity: 0.5; -} -.terminal.focus.xterm-cursor-style-bar.visual-bell-active .terminal-cursor::before, -.terminal.focus.xterm-cursor-style-underline.visual-bell-active .terminal-cursor::before { - opacity: 0; -} - .terminal .composition-view { background: #000; color: #FFF; @@ -153,31 +94,12 @@ overflow-y: scroll; } -.terminal .xterm-wide-char, -.terminal .xterm-normal-char { - display: inline-block; -} - .terminal canvas { position: absolute; left: 0; top: 0; } -.terminal .xterm-rows { - position: absolute; - left: 0; - top: 0; - - /* Hide temporarily, this should be removed eventually */ - visibility: hidden; -} - -.terminal .xterm-rows > div { - /* Lines containing spans and text nodes ocassionally wrap despite being the same width (#327) */ - white-space: nowrap; -} - .terminal .xterm-scroll-area { visibility: hidden; } @@ -194,2087 +116,6 @@ cursor: default; } -.terminal .xterm-selection { - position: absolute; - top: 0; - left: 0; - z-index: 1; - opacity: 0.3; - pointer-events: none; -} - -.terminal .xterm-selection div { - position: absolute; - background-color: #fff; -} - -/* - * Determine default colors for xterm.js - */ -.terminal .xterm-bold { - font-weight: bold; -} - -.terminal .xterm-underline { - text-decoration: underline; -} - -.terminal .xterm-blink { - text-decoration: blink; -} - -.terminal .xterm-blink.xterm-underline { - text-decoration: blink underline; -} - -.terminal .xterm-hidden { - visibility: hidden; -} - -.terminal .xterm-color-0 { - color: #2e3436; -} - -.terminal .xterm-bg-color-0 { - background-color: #2e3436; -} - -.terminal .xterm-color-1 { - color: #cc0000; -} - -.terminal .xterm-bg-color-1 { - background-color: #cc0000; -} - -.terminal .xterm-color-2 { - color: #4e9a06; -} - -.terminal .xterm-bg-color-2 { - background-color: #4e9a06; -} - -.terminal .xterm-color-3 { - color: #c4a000; -} - -.terminal .xterm-bg-color-3 { - background-color: #c4a000; -} - -.terminal .xterm-color-4 { - color: #3465a4; -} - -.terminal .xterm-bg-color-4 { - background-color: #3465a4; -} - -.terminal .xterm-color-5 { - color: #75507b; -} - -.terminal .xterm-bg-color-5 { - background-color: #75507b; -} - -.terminal .xterm-color-6 { - color: #06989a; -} - -.terminal .xterm-bg-color-6 { - background-color: #06989a; -} - -.terminal .xterm-color-7 { - color: #d3d7cf; -} - -.terminal .xterm-bg-color-7 { - background-color: #d3d7cf; -} - -.terminal .xterm-color-8 { - color: #555753; -} - -.terminal .xterm-bg-color-8 { - background-color: #555753; -} - -.terminal .xterm-color-9 { - color: #ef2929; -} - -.terminal .xterm-bg-color-9 { - background-color: #ef2929; -} - -.terminal .xterm-color-10 { - color: #8ae234; -} - -.terminal .xterm-bg-color-10 { - background-color: #8ae234; -} - -.terminal .xterm-color-11 { - color: #fce94f; -} - -.terminal .xterm-bg-color-11 { - background-color: #fce94f; -} - -.terminal .xterm-color-12 { - color: #729fcf; -} - -.terminal .xterm-bg-color-12 { - background-color: #729fcf; -} - -.terminal .xterm-color-13 { - color: #ad7fa8; -} - -.terminal .xterm-bg-color-13 { - background-color: #ad7fa8; -} - -.terminal .xterm-color-14 { - color: #34e2e2; -} - -.terminal .xterm-bg-color-14 { - background-color: #34e2e2; -} - -.terminal .xterm-color-15 { - color: #eeeeec; -} - -.terminal .xterm-bg-color-15 { - background-color: #eeeeec; -} - -.terminal .xterm-color-16 { - color: #000000; -} - -.terminal .xterm-bg-color-16 { - background-color: #000000; -} - -.terminal .xterm-color-17 { - color: #00005f; -} - -.terminal .xterm-bg-color-17 { - background-color: #00005f; -} - -.terminal .xterm-color-18 { - color: #000087; -} - -.terminal .xterm-bg-color-18 { - background-color: #000087; -} - -.terminal .xterm-color-19 { - color: #0000af; -} - -.terminal .xterm-bg-color-19 { - background-color: #0000af; -} - -.terminal .xterm-color-20 { - color: #0000d7; -} - -.terminal .xterm-bg-color-20 { - background-color: #0000d7; -} - -.terminal .xterm-color-21 { - color: #0000ff; -} - -.terminal .xterm-bg-color-21 { - background-color: #0000ff; -} - -.terminal .xterm-color-22 { - color: #005f00; -} - -.terminal .xterm-bg-color-22 { - background-color: #005f00; -} - -.terminal .xterm-color-23 { - color: #005f5f; -} - -.terminal .xterm-bg-color-23 { - background-color: #005f5f; -} - -.terminal .xterm-color-24 { - color: #005f87; -} - -.terminal .xterm-bg-color-24 { - background-color: #005f87; -} - -.terminal .xterm-color-25 { - color: #005faf; -} - -.terminal .xterm-bg-color-25 { - background-color: #005faf; -} - -.terminal .xterm-color-26 { - color: #005fd7; -} - -.terminal .xterm-bg-color-26 { - background-color: #005fd7; -} - -.terminal .xterm-color-27 { - color: #005fff; -} - -.terminal .xterm-bg-color-27 { - background-color: #005fff; -} - -.terminal .xterm-color-28 { - color: #008700; -} - -.terminal .xterm-bg-color-28 { - background-color: #008700; -} - -.terminal .xterm-color-29 { - color: #00875f; -} - -.terminal .xterm-bg-color-29 { - background-color: #00875f; -} - -.terminal .xterm-color-30 { - color: #008787; -} - -.terminal .xterm-bg-color-30 { - background-color: #008787; -} - -.terminal .xterm-color-31 { - color: #0087af; -} - -.terminal .xterm-bg-color-31 { - background-color: #0087af; -} - -.terminal .xterm-color-32 { - color: #0087d7; -} - -.terminal .xterm-bg-color-32 { - background-color: #0087d7; -} - -.terminal .xterm-color-33 { - color: #0087ff; -} - -.terminal .xterm-bg-color-33 { - background-color: #0087ff; -} - -.terminal .xterm-color-34 { - color: #00af00; -} - -.terminal .xterm-bg-color-34 { - background-color: #00af00; -} - -.terminal .xterm-color-35 { - color: #00af5f; -} - -.terminal .xterm-bg-color-35 { - background-color: #00af5f; -} - -.terminal .xterm-color-36 { - color: #00af87; -} - -.terminal .xterm-bg-color-36 { - background-color: #00af87; -} - -.terminal .xterm-color-37 { - color: #00afaf; -} - -.terminal .xterm-bg-color-37 { - background-color: #00afaf; -} - -.terminal .xterm-color-38 { - color: #00afd7; -} - -.terminal .xterm-bg-color-38 { - background-color: #00afd7; -} - -.terminal .xterm-color-39 { - color: #00afff; -} - -.terminal .xterm-bg-color-39 { - background-color: #00afff; -} - -.terminal .xterm-color-40 { - color: #00d700; -} - -.terminal .xterm-bg-color-40 { - background-color: #00d700; -} - -.terminal .xterm-color-41 { - color: #00d75f; -} - -.terminal .xterm-bg-color-41 { - background-color: #00d75f; -} - -.terminal .xterm-color-42 { - color: #00d787; -} - -.terminal .xterm-bg-color-42 { - background-color: #00d787; -} - -.terminal .xterm-color-43 { - color: #00d7af; -} - -.terminal .xterm-bg-color-43 { - background-color: #00d7af; -} - -.terminal .xterm-color-44 { - color: #00d7d7; -} - -.terminal .xterm-bg-color-44 { - background-color: #00d7d7; -} - -.terminal .xterm-color-45 { - color: #00d7ff; -} - -.terminal .xterm-bg-color-45 { - background-color: #00d7ff; -} - -.terminal .xterm-color-46 { - color: #00ff00; -} - -.terminal .xterm-bg-color-46 { - background-color: #00ff00; -} - -.terminal .xterm-color-47 { - color: #00ff5f; -} - -.terminal .xterm-bg-color-47 { - background-color: #00ff5f; -} - -.terminal .xterm-color-48 { - color: #00ff87; -} - -.terminal .xterm-bg-color-48 { - background-color: #00ff87; -} - -.terminal .xterm-color-49 { - color: #00ffaf; -} - -.terminal .xterm-bg-color-49 { - background-color: #00ffaf; -} - -.terminal .xterm-color-50 { - color: #00ffd7; -} - -.terminal .xterm-bg-color-50 { - background-color: #00ffd7; -} - -.terminal .xterm-color-51 { - color: #00ffff; -} - -.terminal .xterm-bg-color-51 { - background-color: #00ffff; -} - -.terminal .xterm-color-52 { - color: #5f0000; -} - -.terminal .xterm-bg-color-52 { - background-color: #5f0000; -} - -.terminal .xterm-color-53 { - color: #5f005f; -} - -.terminal .xterm-bg-color-53 { - background-color: #5f005f; -} - -.terminal .xterm-color-54 { - color: #5f0087; -} - -.terminal .xterm-bg-color-54 { - background-color: #5f0087; -} - -.terminal .xterm-color-55 { - color: #5f00af; -} - -.terminal .xterm-bg-color-55 { - background-color: #5f00af; -} - -.terminal .xterm-color-56 { - color: #5f00d7; -} - -.terminal .xterm-bg-color-56 { - background-color: #5f00d7; -} - -.terminal .xterm-color-57 { - color: #5f00ff; -} - -.terminal .xterm-bg-color-57 { - background-color: #5f00ff; -} - -.terminal .xterm-color-58 { - color: #5f5f00; -} - -.terminal .xterm-bg-color-58 { - background-color: #5f5f00; -} - -.terminal .xterm-color-59 { - color: #5f5f5f; -} - -.terminal .xterm-bg-color-59 { - background-color: #5f5f5f; -} - -.terminal .xterm-color-60 { - color: #5f5f87; -} - -.terminal .xterm-bg-color-60 { - background-color: #5f5f87; -} - -.terminal .xterm-color-61 { - color: #5f5faf; -} - -.terminal .xterm-bg-color-61 { - background-color: #5f5faf; -} - -.terminal .xterm-color-62 { - color: #5f5fd7; -} - -.terminal .xterm-bg-color-62 { - background-color: #5f5fd7; -} - -.terminal .xterm-color-63 { - color: #5f5fff; -} - -.terminal .xterm-bg-color-63 { - background-color: #5f5fff; -} - -.terminal .xterm-color-64 { - color: #5f8700; -} - -.terminal .xterm-bg-color-64 { - background-color: #5f8700; -} - -.terminal .xterm-color-65 { - color: #5f875f; -} - -.terminal .xterm-bg-color-65 { - background-color: #5f875f; -} - -.terminal .xterm-color-66 { - color: #5f8787; -} - -.terminal .xterm-bg-color-66 { - background-color: #5f8787; -} - -.terminal .xterm-color-67 { - color: #5f87af; -} - -.terminal .xterm-bg-color-67 { - background-color: #5f87af; -} - -.terminal .xterm-color-68 { - color: #5f87d7; -} - -.terminal .xterm-bg-color-68 { - background-color: #5f87d7; -} - -.terminal .xterm-color-69 { - color: #5f87ff; -} - -.terminal .xterm-bg-color-69 { - background-color: #5f87ff; -} - -.terminal .xterm-color-70 { - color: #5faf00; -} - -.terminal .xterm-bg-color-70 { - background-color: #5faf00; -} - -.terminal .xterm-color-71 { - color: #5faf5f; -} - -.terminal .xterm-bg-color-71 { - background-color: #5faf5f; -} - -.terminal .xterm-color-72 { - color: #5faf87; -} - -.terminal .xterm-bg-color-72 { - background-color: #5faf87; -} - -.terminal .xterm-color-73 { - color: #5fafaf; -} - -.terminal .xterm-bg-color-73 { - background-color: #5fafaf; -} - -.terminal .xterm-color-74 { - color: #5fafd7; -} - -.terminal .xterm-bg-color-74 { - background-color: #5fafd7; -} - -.terminal .xterm-color-75 { - color: #5fafff; -} - -.terminal .xterm-bg-color-75 { - background-color: #5fafff; -} - -.terminal .xterm-color-76 { - color: #5fd700; -} - -.terminal .xterm-bg-color-76 { - background-color: #5fd700; -} - -.terminal .xterm-color-77 { - color: #5fd75f; -} - -.terminal .xterm-bg-color-77 { - background-color: #5fd75f; -} - -.terminal .xterm-color-78 { - color: #5fd787; -} - -.terminal .xterm-bg-color-78 { - background-color: #5fd787; -} - -.terminal .xterm-color-79 { - color: #5fd7af; -} - -.terminal .xterm-bg-color-79 { - background-color: #5fd7af; -} - -.terminal .xterm-color-80 { - color: #5fd7d7; -} - -.terminal .xterm-bg-color-80 { - background-color: #5fd7d7; -} - -.terminal .xterm-color-81 { - color: #5fd7ff; -} - -.terminal .xterm-bg-color-81 { - background-color: #5fd7ff; -} - -.terminal .xterm-color-82 { - color: #5fff00; -} - -.terminal .xterm-bg-color-82 { - background-color: #5fff00; -} - -.terminal .xterm-color-83 { - color: #5fff5f; -} - -.terminal .xterm-bg-color-83 { - background-color: #5fff5f; -} - -.terminal .xterm-color-84 { - color: #5fff87; -} - -.terminal .xterm-bg-color-84 { - background-color: #5fff87; -} - -.terminal .xterm-color-85 { - color: #5fffaf; -} - -.terminal .xterm-bg-color-85 { - background-color: #5fffaf; -} - -.terminal .xterm-color-86 { - color: #5fffd7; -} - -.terminal .xterm-bg-color-86 { - background-color: #5fffd7; -} - -.terminal .xterm-color-87 { - color: #5fffff; -} - -.terminal .xterm-bg-color-87 { - background-color: #5fffff; -} - -.terminal .xterm-color-88 { - color: #870000; -} - -.terminal .xterm-bg-color-88 { - background-color: #870000; -} - -.terminal .xterm-color-89 { - color: #87005f; -} - -.terminal .xterm-bg-color-89 { - background-color: #87005f; -} - -.terminal .xterm-color-90 { - color: #870087; -} - -.terminal .xterm-bg-color-90 { - background-color: #870087; -} - -.terminal .xterm-color-91 { - color: #8700af; -} - -.terminal .xterm-bg-color-91 { - background-color: #8700af; -} - -.terminal .xterm-color-92 { - color: #8700d7; -} - -.terminal .xterm-bg-color-92 { - background-color: #8700d7; -} - -.terminal .xterm-color-93 { - color: #8700ff; -} - -.terminal .xterm-bg-color-93 { - background-color: #8700ff; -} - -.terminal .xterm-color-94 { - color: #875f00; -} - -.terminal .xterm-bg-color-94 { - background-color: #875f00; -} - -.terminal .xterm-color-95 { - color: #875f5f; -} - -.terminal .xterm-bg-color-95 { - background-color: #875f5f; -} - -.terminal .xterm-color-96 { - color: #875f87; -} - -.terminal .xterm-bg-color-96 { - background-color: #875f87; -} - -.terminal .xterm-color-97 { - color: #875faf; -} - -.terminal .xterm-bg-color-97 { - background-color: #875faf; -} - -.terminal .xterm-color-98 { - color: #875fd7; -} - -.terminal .xterm-bg-color-98 { - background-color: #875fd7; -} - -.terminal .xterm-color-99 { - color: #875fff; -} - -.terminal .xterm-bg-color-99 { - background-color: #875fff; -} - -.terminal .xterm-color-100 { - color: #878700; -} - -.terminal .xterm-bg-color-100 { - background-color: #878700; -} - -.terminal .xterm-color-101 { - color: #87875f; -} - -.terminal .xterm-bg-color-101 { - background-color: #87875f; -} - -.terminal .xterm-color-102 { - color: #878787; -} - -.terminal .xterm-bg-color-102 { - background-color: #878787; -} - -.terminal .xterm-color-103 { - color: #8787af; -} - -.terminal .xterm-bg-color-103 { - background-color: #8787af; -} - -.terminal .xterm-color-104 { - color: #8787d7; -} - -.terminal .xterm-bg-color-104 { - background-color: #8787d7; -} - -.terminal .xterm-color-105 { - color: #8787ff; -} - -.terminal .xterm-bg-color-105 { - background-color: #8787ff; -} - -.terminal .xterm-color-106 { - color: #87af00; -} - -.terminal .xterm-bg-color-106 { - background-color: #87af00; -} - -.terminal .xterm-color-107 { - color: #87af5f; -} - -.terminal .xterm-bg-color-107 { - background-color: #87af5f; -} - -.terminal .xterm-color-108 { - color: #87af87; -} - -.terminal .xterm-bg-color-108 { - background-color: #87af87; -} - -.terminal .xterm-color-109 { - color: #87afaf; -} - -.terminal .xterm-bg-color-109 { - background-color: #87afaf; -} - -.terminal .xterm-color-110 { - color: #87afd7; -} - -.terminal .xterm-bg-color-110 { - background-color: #87afd7; -} - -.terminal .xterm-color-111 { - color: #87afff; -} - -.terminal .xterm-bg-color-111 { - background-color: #87afff; -} - -.terminal .xterm-color-112 { - color: #87d700; -} - -.terminal .xterm-bg-color-112 { - background-color: #87d700; -} - -.terminal .xterm-color-113 { - color: #87d75f; -} - -.terminal .xterm-bg-color-113 { - background-color: #87d75f; -} - -.terminal .xterm-color-114 { - color: #87d787; -} - -.terminal .xterm-bg-color-114 { - background-color: #87d787; -} - -.terminal .xterm-color-115 { - color: #87d7af; -} - -.terminal .xterm-bg-color-115 { - background-color: #87d7af; -} - -.terminal .xterm-color-116 { - color: #87d7d7; -} - -.terminal .xterm-bg-color-116 { - background-color: #87d7d7; -} - -.terminal .xterm-color-117 { - color: #87d7ff; -} - -.terminal .xterm-bg-color-117 { - background-color: #87d7ff; -} - -.terminal .xterm-color-118 { - color: #87ff00; -} - -.terminal .xterm-bg-color-118 { - background-color: #87ff00; -} - -.terminal .xterm-color-119 { - color: #87ff5f; -} - -.terminal .xterm-bg-color-119 { - background-color: #87ff5f; -} - -.terminal .xterm-color-120 { - color: #87ff87; -} - -.terminal .xterm-bg-color-120 { - background-color: #87ff87; -} - -.terminal .xterm-color-121 { - color: #87ffaf; -} - -.terminal .xterm-bg-color-121 { - background-color: #87ffaf; -} - -.terminal .xterm-color-122 { - color: #87ffd7; -} - -.terminal .xterm-bg-color-122 { - background-color: #87ffd7; -} - -.terminal .xterm-color-123 { - color: #87ffff; -} - -.terminal .xterm-bg-color-123 { - background-color: #87ffff; -} - -.terminal .xterm-color-124 { - color: #af0000; -} - -.terminal .xterm-bg-color-124 { - background-color: #af0000; -} - -.terminal .xterm-color-125 { - color: #af005f; -} - -.terminal .xterm-bg-color-125 { - background-color: #af005f; -} - -.terminal .xterm-color-126 { - color: #af0087; -} - -.terminal .xterm-bg-color-126 { - background-color: #af0087; -} - -.terminal .xterm-color-127 { - color: #af00af; -} - -.terminal .xterm-bg-color-127 { - background-color: #af00af; -} - -.terminal .xterm-color-128 { - color: #af00d7; -} - -.terminal .xterm-bg-color-128 { - background-color: #af00d7; -} - -.terminal .xterm-color-129 { - color: #af00ff; -} - -.terminal .xterm-bg-color-129 { - background-color: #af00ff; -} - -.terminal .xterm-color-130 { - color: #af5f00; -} - -.terminal .xterm-bg-color-130 { - background-color: #af5f00; -} - -.terminal .xterm-color-131 { - color: #af5f5f; -} - -.terminal .xterm-bg-color-131 { - background-color: #af5f5f; -} - -.terminal .xterm-color-132 { - color: #af5f87; -} - -.terminal .xterm-bg-color-132 { - background-color: #af5f87; -} - -.terminal .xterm-color-133 { - color: #af5faf; -} - -.terminal .xterm-bg-color-133 { - background-color: #af5faf; -} - -.terminal .xterm-color-134 { - color: #af5fd7; -} - -.terminal .xterm-bg-color-134 { - background-color: #af5fd7; -} - -.terminal .xterm-color-135 { - color: #af5fff; -} - -.terminal .xterm-bg-color-135 { - background-color: #af5fff; -} - -.terminal .xterm-color-136 { - color: #af8700; -} - -.terminal .xterm-bg-color-136 { - background-color: #af8700; -} - -.terminal .xterm-color-137 { - color: #af875f; -} - -.terminal .xterm-bg-color-137 { - background-color: #af875f; -} - -.terminal .xterm-color-138 { - color: #af8787; -} - -.terminal .xterm-bg-color-138 { - background-color: #af8787; -} - -.terminal .xterm-color-139 { - color: #af87af; -} - -.terminal .xterm-bg-color-139 { - background-color: #af87af; -} - -.terminal .xterm-color-140 { - color: #af87d7; -} - -.terminal .xterm-bg-color-140 { - background-color: #af87d7; -} - -.terminal .xterm-color-141 { - color: #af87ff; -} - -.terminal .xterm-bg-color-141 { - background-color: #af87ff; -} - -.terminal .xterm-color-142 { - color: #afaf00; -} - -.terminal .xterm-bg-color-142 { - background-color: #afaf00; -} - -.terminal .xterm-color-143 { - color: #afaf5f; -} - -.terminal .xterm-bg-color-143 { - background-color: #afaf5f; -} - -.terminal .xterm-color-144 { - color: #afaf87; -} - -.terminal .xterm-bg-color-144 { - background-color: #afaf87; -} - -.terminal .xterm-color-145 { - color: #afafaf; -} - -.terminal .xterm-bg-color-145 { - background-color: #afafaf; -} - -.terminal .xterm-color-146 { - color: #afafd7; -} - -.terminal .xterm-bg-color-146 { - background-color: #afafd7; -} - -.terminal .xterm-color-147 { - color: #afafff; -} - -.terminal .xterm-bg-color-147 { - background-color: #afafff; -} - -.terminal .xterm-color-148 { - color: #afd700; -} - -.terminal .xterm-bg-color-148 { - background-color: #afd700; -} - -.terminal .xterm-color-149 { - color: #afd75f; -} - -.terminal .xterm-bg-color-149 { - background-color: #afd75f; -} - -.terminal .xterm-color-150 { - color: #afd787; -} - -.terminal .xterm-bg-color-150 { - background-color: #afd787; -} - -.terminal .xterm-color-151 { - color: #afd7af; -} - -.terminal .xterm-bg-color-151 { - background-color: #afd7af; -} - -.terminal .xterm-color-152 { - color: #afd7d7; -} - -.terminal .xterm-bg-color-152 { - background-color: #afd7d7; -} - -.terminal .xterm-color-153 { - color: #afd7ff; -} - -.terminal .xterm-bg-color-153 { - background-color: #afd7ff; -} - -.terminal .xterm-color-154 { - color: #afff00; -} - -.terminal .xterm-bg-color-154 { - background-color: #afff00; -} - -.terminal .xterm-color-155 { - color: #afff5f; -} - -.terminal .xterm-bg-color-155 { - background-color: #afff5f; -} - -.terminal .xterm-color-156 { - color: #afff87; -} - -.terminal .xterm-bg-color-156 { - background-color: #afff87; -} - -.terminal .xterm-color-157 { - color: #afffaf; -} - -.terminal .xterm-bg-color-157 { - background-color: #afffaf; -} - -.terminal .xterm-color-158 { - color: #afffd7; -} - -.terminal .xterm-bg-color-158 { - background-color: #afffd7; -} - -.terminal .xterm-color-159 { - color: #afffff; -} - -.terminal .xterm-bg-color-159 { - background-color: #afffff; -} - -.terminal .xterm-color-160 { - color: #d70000; -} - -.terminal .xterm-bg-color-160 { - background-color: #d70000; -} - -.terminal .xterm-color-161 { - color: #d7005f; -} - -.terminal .xterm-bg-color-161 { - background-color: #d7005f; -} - -.terminal .xterm-color-162 { - color: #d70087; -} - -.terminal .xterm-bg-color-162 { - background-color: #d70087; -} - -.terminal .xterm-color-163 { - color: #d700af; -} - -.terminal .xterm-bg-color-163 { - background-color: #d700af; -} - -.terminal .xterm-color-164 { - color: #d700d7; -} - -.terminal .xterm-bg-color-164 { - background-color: #d700d7; -} - -.terminal .xterm-color-165 { - color: #d700ff; -} - -.terminal .xterm-bg-color-165 { - background-color: #d700ff; -} - -.terminal .xterm-color-166 { - color: #d75f00; -} - -.terminal .xterm-bg-color-166 { - background-color: #d75f00; -} - -.terminal .xterm-color-167 { - color: #d75f5f; -} - -.terminal .xterm-bg-color-167 { - background-color: #d75f5f; -} - -.terminal .xterm-color-168 { - color: #d75f87; -} - -.terminal .xterm-bg-color-168 { - background-color: #d75f87; -} - -.terminal .xterm-color-169 { - color: #d75faf; -} - -.terminal .xterm-bg-color-169 { - background-color: #d75faf; -} - -.terminal .xterm-color-170 { - color: #d75fd7; -} - -.terminal .xterm-bg-color-170 { - background-color: #d75fd7; -} - -.terminal .xterm-color-171 { - color: #d75fff; -} - -.terminal .xterm-bg-color-171 { - background-color: #d75fff; -} - -.terminal .xterm-color-172 { - color: #d78700; -} - -.terminal .xterm-bg-color-172 { - background-color: #d78700; -} - -.terminal .xterm-color-173 { - color: #d7875f; -} - -.terminal .xterm-bg-color-173 { - background-color: #d7875f; -} - -.terminal .xterm-color-174 { - color: #d78787; -} - -.terminal .xterm-bg-color-174 { - background-color: #d78787; -} - -.terminal .xterm-color-175 { - color: #d787af; -} - -.terminal .xterm-bg-color-175 { - background-color: #d787af; -} - -.terminal .xterm-color-176 { - color: #d787d7; -} - -.terminal .xterm-bg-color-176 { - background-color: #d787d7; -} - -.terminal .xterm-color-177 { - color: #d787ff; -} - -.terminal .xterm-bg-color-177 { - background-color: #d787ff; -} - -.terminal .xterm-color-178 { - color: #d7af00; -} - -.terminal .xterm-bg-color-178 { - background-color: #d7af00; -} - -.terminal .xterm-color-179 { - color: #d7af5f; -} - -.terminal .xterm-bg-color-179 { - background-color: #d7af5f; -} - -.terminal .xterm-color-180 { - color: #d7af87; -} - -.terminal .xterm-bg-color-180 { - background-color: #d7af87; -} - -.terminal .xterm-color-181 { - color: #d7afaf; -} - -.terminal .xterm-bg-color-181 { - background-color: #d7afaf; -} - -.terminal .xterm-color-182 { - color: #d7afd7; -} - -.terminal .xterm-bg-color-182 { - background-color: #d7afd7; -} - -.terminal .xterm-color-183 { - color: #d7afff; -} - -.terminal .xterm-bg-color-183 { - background-color: #d7afff; -} - -.terminal .xterm-color-184 { - color: #d7d700; -} - -.terminal .xterm-bg-color-184 { - background-color: #d7d700; -} - -.terminal .xterm-color-185 { - color: #d7d75f; -} - -.terminal .xterm-bg-color-185 { - background-color: #d7d75f; -} - -.terminal .xterm-color-186 { - color: #d7d787; -} - -.terminal .xterm-bg-color-186 { - background-color: #d7d787; -} - -.terminal .xterm-color-187 { - color: #d7d7af; -} - -.terminal .xterm-bg-color-187 { - background-color: #d7d7af; -} - -.terminal .xterm-color-188 { - color: #d7d7d7; -} - -.terminal .xterm-bg-color-188 { - background-color: #d7d7d7; -} - -.terminal .xterm-color-189 { - color: #d7d7ff; -} - -.terminal .xterm-bg-color-189 { - background-color: #d7d7ff; -} - -.terminal .xterm-color-190 { - color: #d7ff00; -} - -.terminal .xterm-bg-color-190 { - background-color: #d7ff00; -} - -.terminal .xterm-color-191 { - color: #d7ff5f; -} - -.terminal .xterm-bg-color-191 { - background-color: #d7ff5f; -} - -.terminal .xterm-color-192 { - color: #d7ff87; -} - -.terminal .xterm-bg-color-192 { - background-color: #d7ff87; -} - -.terminal .xterm-color-193 { - color: #d7ffaf; -} - -.terminal .xterm-bg-color-193 { - background-color: #d7ffaf; -} - -.terminal .xterm-color-194 { - color: #d7ffd7; -} - -.terminal .xterm-bg-color-194 { - background-color: #d7ffd7; -} - -.terminal .xterm-color-195 { - color: #d7ffff; -} - -.terminal .xterm-bg-color-195 { - background-color: #d7ffff; -} - -.terminal .xterm-color-196 { - color: #ff0000; -} - -.terminal .xterm-bg-color-196 { - background-color: #ff0000; -} - -.terminal .xterm-color-197 { - color: #ff005f; -} - -.terminal .xterm-bg-color-197 { - background-color: #ff005f; -} - -.terminal .xterm-color-198 { - color: #ff0087; -} - -.terminal .xterm-bg-color-198 { - background-color: #ff0087; -} - -.terminal .xterm-color-199 { - color: #ff00af; -} - -.terminal .xterm-bg-color-199 { - background-color: #ff00af; -} - -.terminal .xterm-color-200 { - color: #ff00d7; -} - -.terminal .xterm-bg-color-200 { - background-color: #ff00d7; -} - -.terminal .xterm-color-201 { - color: #ff00ff; -} - -.terminal .xterm-bg-color-201 { - background-color: #ff00ff; -} - -.terminal .xterm-color-202 { - color: #ff5f00; -} - -.terminal .xterm-bg-color-202 { - background-color: #ff5f00; -} - -.terminal .xterm-color-203 { - color: #ff5f5f; -} - -.terminal .xterm-bg-color-203 { - background-color: #ff5f5f; -} - -.terminal .xterm-color-204 { - color: #ff5f87; -} - -.terminal .xterm-bg-color-204 { - background-color: #ff5f87; -} - -.terminal .xterm-color-205 { - color: #ff5faf; -} - -.terminal .xterm-bg-color-205 { - background-color: #ff5faf; -} - -.terminal .xterm-color-206 { - color: #ff5fd7; -} - -.terminal .xterm-bg-color-206 { - background-color: #ff5fd7; -} - -.terminal .xterm-color-207 { - color: #ff5fff; -} - -.terminal .xterm-bg-color-207 { - background-color: #ff5fff; -} - -.terminal .xterm-color-208 { - color: #ff8700; -} - -.terminal .xterm-bg-color-208 { - background-color: #ff8700; -} - -.terminal .xterm-color-209 { - color: #ff875f; -} - -.terminal .xterm-bg-color-209 { - background-color: #ff875f; -} - -.terminal .xterm-color-210 { - color: #ff8787; -} - -.terminal .xterm-bg-color-210 { - background-color: #ff8787; -} - -.terminal .xterm-color-211 { - color: #ff87af; -} - -.terminal .xterm-bg-color-211 { - background-color: #ff87af; -} - -.terminal .xterm-color-212 { - color: #ff87d7; -} - -.terminal .xterm-bg-color-212 { - background-color: #ff87d7; -} - -.terminal .xterm-color-213 { - color: #ff87ff; -} - -.terminal .xterm-bg-color-213 { - background-color: #ff87ff; -} - -.terminal .xterm-color-214 { - color: #ffaf00; -} - -.terminal .xterm-bg-color-214 { - background-color: #ffaf00; -} - -.terminal .xterm-color-215 { - color: #ffaf5f; -} - -.terminal .xterm-bg-color-215 { - background-color: #ffaf5f; -} - -.terminal .xterm-color-216 { - color: #ffaf87; -} - -.terminal .xterm-bg-color-216 { - background-color: #ffaf87; -} - -.terminal .xterm-color-217 { - color: #ffafaf; -} - -.terminal .xterm-bg-color-217 { - background-color: #ffafaf; -} - -.terminal .xterm-color-218 { - color: #ffafd7; -} - -.terminal .xterm-bg-color-218 { - background-color: #ffafd7; -} - -.terminal .xterm-color-219 { - color: #ffafff; -} - -.terminal .xterm-bg-color-219 { - background-color: #ffafff; -} - -.terminal .xterm-color-220 { - color: #ffd700; -} - -.terminal .xterm-bg-color-220 { - background-color: #ffd700; -} - -.terminal .xterm-color-221 { - color: #ffd75f; -} - -.terminal .xterm-bg-color-221 { - background-color: #ffd75f; -} - -.terminal .xterm-color-222 { - color: #ffd787; -} - -.terminal .xterm-bg-color-222 { - background-color: #ffd787; -} - -.terminal .xterm-color-223 { - color: #ffd7af; -} - -.terminal .xterm-bg-color-223 { - background-color: #ffd7af; -} - -.terminal .xterm-color-224 { - color: #ffd7d7; -} - -.terminal .xterm-bg-color-224 { - background-color: #ffd7d7; -} - -.terminal .xterm-color-225 { - color: #ffd7ff; -} - -.terminal .xterm-bg-color-225 { - background-color: #ffd7ff; -} - -.terminal .xterm-color-226 { - color: #ffff00; -} - -.terminal .xterm-bg-color-226 { - background-color: #ffff00; -} - -.terminal .xterm-color-227 { - color: #ffff5f; -} - -.terminal .xterm-bg-color-227 { - background-color: #ffff5f; -} - -.terminal .xterm-color-228 { - color: #ffff87; -} - -.terminal .xterm-bg-color-228 { - background-color: #ffff87; -} - -.terminal .xterm-color-229 { - color: #ffffaf; -} - -.terminal .xterm-bg-color-229 { - background-color: #ffffaf; -} - -.terminal .xterm-color-230 { - color: #ffffd7; -} - -.terminal .xterm-bg-color-230 { - background-color: #ffffd7; -} - -.terminal .xterm-color-231 { - color: #ffffff; -} - -.terminal .xterm-bg-color-231 { - background-color: #ffffff; -} - -.terminal .xterm-color-232 { - color: #080808; -} - -.terminal .xterm-bg-color-232 { - background-color: #080808; -} - -.terminal .xterm-color-233 { - color: #121212; -} - -.terminal .xterm-bg-color-233 { - background-color: #121212; -} - -.terminal .xterm-color-234 { - color: #1c1c1c; -} - -.terminal .xterm-bg-color-234 { - background-color: #1c1c1c; -} - -.terminal .xterm-color-235 { - color: #262626; -} - -.terminal .xterm-bg-color-235 { - background-color: #262626; -} - -.terminal .xterm-color-236 { - color: #303030; -} - -.terminal .xterm-bg-color-236 { - background-color: #303030; -} - -.terminal .xterm-color-237 { - color: #3a3a3a; -} - -.terminal .xterm-bg-color-237 { - background-color: #3a3a3a; -} - -.terminal .xterm-color-238 { - color: #444444; -} - -.terminal .xterm-bg-color-238 { - background-color: #444444; -} - -.terminal .xterm-color-239 { - color: #4e4e4e; -} - -.terminal .xterm-bg-color-239 { - background-color: #4e4e4e; -} - -.terminal .xterm-color-240 { - color: #585858; -} - -.terminal .xterm-bg-color-240 { - background-color: #585858; -} - -.terminal .xterm-color-241 { - color: #626262; -} - -.terminal .xterm-bg-color-241 { - background-color: #626262; -} - -.terminal .xterm-color-242 { - color: #6c6c6c; -} - -.terminal .xterm-bg-color-242 { - background-color: #6c6c6c; -} - -.terminal .xterm-color-243 { - color: #767676; -} - -.terminal .xterm-bg-color-243 { - background-color: #767676; -} - -.terminal .xterm-color-244 { - color: #808080; -} - -.terminal .xterm-bg-color-244 { - background-color: #808080; -} - -.terminal .xterm-color-245 { - color: #8a8a8a; -} - -.terminal .xterm-bg-color-245 { - background-color: #8a8a8a; -} - -.terminal .xterm-color-246 { - color: #949494; -} - -.terminal .xterm-bg-color-246 { - background-color: #949494; -} - -.terminal .xterm-color-247 { - color: #9e9e9e; -} - -.terminal .xterm-bg-color-247 { - background-color: #9e9e9e; -} - -.terminal .xterm-color-248 { - color: #a8a8a8; -} - -.terminal .xterm-bg-color-248 { - background-color: #a8a8a8; -} - -.terminal .xterm-color-249 { - color: #b2b2b2; -} - -.terminal .xterm-bg-color-249 { - background-color: #b2b2b2; -} - -.terminal .xterm-color-250 { - color: #bcbcbc; -} - -.terminal .xterm-bg-color-250 { - background-color: #bcbcbc; -} - -.terminal .xterm-color-251 { - color: #c6c6c6; -} - -.terminal .xterm-bg-color-251 { - background-color: #c6c6c6; -} - -.terminal .xterm-color-252 { - color: #d0d0d0; -} - -.terminal .xterm-bg-color-252 { - background-color: #d0d0d0; -} - -.terminal .xterm-color-253 { - color: #dadada; -} - -.terminal .xterm-bg-color-253 { - background-color: #dadada; -} - -.terminal .xterm-color-254 { - color: #e4e4e4; -} - -.terminal .xterm-bg-color-254 { - background-color: #e4e4e4; -} - -.terminal .xterm-color-255 { - color: #eeeeee; -} - -.terminal .xterm-bg-color-255 { - background-color: #eeeeee; +.terminal:not(.enable-mouse-events) { + cursor: text; } From 4f81cdfe8c6da79b0dc6651eef86d6027834b3ff Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 2 Sep 2017 13:58:21 -0700 Subject: [PATCH 169/378] Make the terminal background theme aware --- demo/style.css | 2 -- src/Interfaces.ts | 2 ++ src/Terminal.ts | 5 ++++- src/Viewport.ts | 5 +++++ src/renderer/Renderer.ts | 6 ++++-- src/utils/TestUtils.test.ts | 3 +++ src/xterm.css | 4 +--- 7 files changed, 19 insertions(+), 8 deletions(-) diff --git a/demo/style.css b/demo/style.css index 71389621..ee00eca0 100644 --- a/demo/style.css +++ b/demo/style.css @@ -16,7 +16,5 @@ h1 { } #terminal-container .terminal { - background-color: #111; - color: #fafafa; padding: 2px; } diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 861720a3..2b4909c9 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -4,6 +4,7 @@ import { ILinkMatcherOptions } from './Interfaces'; import { LinkMatcherHandler, LinkMatcherValidationCallback, Charset, LineData } from './Types'; +import { IColorSet } from './renderer/Interfaces'; export interface IBrowser { isNode: boolean; @@ -163,6 +164,7 @@ export interface IViewport { onWheel(ev: WheelEvent): void; onTouchStart(ev: TouchEvent): void; onTouchMove(ev: TouchEvent): void; + onThemeChanged(colors: IColorSet): void; } export interface ISelectionManager { diff --git a/src/Terminal.ts b/src/Terminal.ts index 8003dce3..d47c6bf8 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -318,7 +318,10 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT public setTheme(theme: ITheme): void { // TODO: Allow setting of theme before renderer is ready if (this.renderer) { - this.renderer.setTheme(theme); + const colors = this.renderer.setTheme(theme); + if (this.viewport) { + this.viewport.onThemeChanged(colors); + } } } diff --git a/src/Viewport.ts b/src/Viewport.ts index cedcef20..88433ed5 100644 --- a/src/Viewport.ts +++ b/src/Viewport.ts @@ -4,6 +4,7 @@ import { ITerminal, IViewport } from './Interfaces'; import { CharMeasure } from './utils/CharMeasure'; +import { IColorSet } from './renderer/Interfaces'; /** * Represents the viewport of a terminal, the visible area within the larger buffer of output. @@ -40,6 +41,10 @@ export class Viewport implements IViewport { setTimeout(() => this.syncScrollArea(), 0); } + public onThemeChanged(colors: IColorSet): void { + this.viewportElement.style.backgroundColor = colors.background; + } + /** * Refreshes row height, setting line-height, viewport height and scroll area height if * necessary. diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index b3df2b7a..adf5bbd0 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -11,7 +11,7 @@ import { SelectionRenderLayer } from './SelectionRenderLayer'; import { CursorRenderLayer } from './CursorRenderLayer'; import { ColorManager } from './ColorManager'; import { BaseRenderLayer } from './BaseRenderLayer'; -import { IRenderLayer } from './Interfaces'; +import { IRenderLayer, IColorSet } from './Interfaces'; export class Renderer { /** A queue of the rows to be refreshed */ @@ -32,7 +32,7 @@ export class Renderer { ]; } - public setTheme(theme: ITheme): void { + public setTheme(theme: ITheme): IColorSet { this._colorManager.setTheme(theme); // Clear layers and force a full render this._renderLayers.forEach(l => { @@ -42,6 +42,8 @@ export class Renderer { // TODO: This is currently done for every single terminal, but it's static so it's wasting time this._terminal.refresh(0, this._terminal.rows - 1); + + return this._colorManager.colors; } public onResize(cols: number, rows: number): void { diff --git a/src/utils/TestUtils.test.ts b/src/utils/TestUtils.test.ts index f4eae64d..46a226a2 100644 --- a/src/utils/TestUtils.test.ts +++ b/src/utils/TestUtils.test.ts @@ -202,6 +202,9 @@ export class MockBuffer implements IBuffer { } export class MockViewport implements IViewport { + onThemeChanged(colors: IColorSet): void { + throw new Error('Method not implemented.'); + } onWheel(ev: WheelEvent): void { throw new Error('Method not implemented.'); } diff --git a/src/xterm.css b/src/xterm.css index 30bce803..0ddfe8b5 100644 --- a/src/xterm.css +++ b/src/xterm.css @@ -36,9 +36,6 @@ */ .terminal { - /** TODO: Remove colors from xterm.css */ - background-color: #000; - color: #fff; font-family: courier-new, courier, monospace; font-feature-settings: "liga" 0; position: relative; @@ -76,6 +73,7 @@ } .terminal .composition-view { + /* TODO: Composition position got messed up somewhere */ background: #000; color: #FFF; display: none; From c5a6d96a62c3900f89d7be072c838388f775d995 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 2 Sep 2017 14:11:21 -0700 Subject: [PATCH 170/378] Remove padding from terminal, not support atm --- demo/style.css | 4 ---- 1 file changed, 4 deletions(-) diff --git a/demo/style.css b/demo/style.css index ee00eca0..1ab5f61c 100644 --- a/demo/style.css +++ b/demo/style.css @@ -14,7 +14,3 @@ h1 { margin: 0 auto; padding: 2px; } - -#terminal-container .terminal { - padding: 2px; -} From 7ccac09b6bbac4e93030c50aa5b0d5aaff41e50d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 2 Sep 2017 18:41:53 -0700 Subject: [PATCH 171/378] Fix the composition element's position --- src/Buffer.ts | 6 ++++++ src/CompositionHelper.ts | 21 +++++++++++---------- src/Interfaces.ts | 1 + src/xterm.css | 5 +++++ 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/Buffer.ts b/src/Buffer.ts index 101244f1..6931eea4 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -52,6 +52,12 @@ export class Buffer implements IBuffer { return this._hasScrollback && this.lines.maxLength > this._terminal.rows; } + public get isCursorInViewport(): boolean { + const absoluteY = this.ybase + this.y; + const relativeY = absoluteY - this.ydisp; + return (relativeY >= 0 && relativeY < this._terminal.rows); + } + /** * Gets the correct buffer length based on the rows provided, the terminal's * scrollback and whether this buffer is flagged to have scrollback or not. diff --git a/src/CompositionHelper.ts b/src/CompositionHelper.ts index 223a90e7..345041d6 100644 --- a/src/CompositionHelper.ts +++ b/src/CompositionHelper.ts @@ -63,6 +63,7 @@ export class CompositionHelper { * @param {CompositionEvent} ev The event. */ public compositionupdate(ev: CompositionEvent): void { + console.log('compositionupdate'); this.compositionView.textContent = ev.data; this.updateCompositionElements(); setTimeout(() => { @@ -193,26 +194,26 @@ export class CompositionHelper { if (!this.isComposing) { return; } - const cursor = this.terminal.element.querySelector('.terminal-cursor'); - if (cursor) { - // Take .xterm-rows offsetTop into account as well in case it's positioned absolutely within - // the .xterm element. - const xtermRows = this.terminal.element.querySelector('.xterm-rows'); - const cursorTop = xtermRows.offsetTop + cursor.offsetTop; - this.compositionView.style.left = cursor.offsetLeft + 'px'; + if (this.terminal.buffer.isCursorInViewport) { + const cellHeight = Math.ceil(this.terminal.charMeasure.height * this.terminal.options.lineHeight); + const cursorTop = this.terminal.buffer.y * cellHeight; + const cursorLeft = this.terminal.buffer.x * this.terminal.charMeasure.width; + + this.compositionView.style.left = cursorLeft + 'px'; this.compositionView.style.top = cursorTop + 'px'; - this.compositionView.style.height = cursor.offsetHeight + 'px'; - this.compositionView.style.lineHeight = cursor.offsetHeight + 'px'; + this.compositionView.style.height = cellHeight + 'px'; + this.compositionView.style.lineHeight = cellHeight + 'px'; // Sync the textarea to the exact position of the composition view so the IME knows where the // text is. const compositionViewBounds = this.compositionView.getBoundingClientRect(); - this.textarea.style.left = cursor.offsetLeft + 'px'; + this.textarea.style.left = cursorLeft + 'px'; this.textarea.style.top = cursorTop + 'px'; this.textarea.style.width = compositionViewBounds.width + 'px'; this.textarea.style.height = compositionViewBounds.height + 'px'; this.textarea.style.lineHeight = compositionViewBounds.height + 'px'; } + if (!dontRecurse) { setTimeout(() => this.updateCompositionElements(true), 0); } diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 2b4909c9..7ebf6983 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -145,6 +145,7 @@ export interface IBuffer { 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; diff --git a/src/xterm.css b/src/xterm.css index 0ddfe8b5..df3aff59 100644 --- a/src/xterm.css +++ b/src/xterm.css @@ -52,6 +52,11 @@ .terminal .xterm-helpers { position: absolute; top: 0; + /** + * The z-index of the helpers must be higher than the canvases in order for + * IMEs to appear on top. + */ + z-index: 10; } .terminal .xterm-helper-textarea { From c94e87428deb687dd3739fa3527486d4a6cdc802 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 2 Sep 2017 19:04:07 -0700 Subject: [PATCH 172/378] Fix CharMeasure getting taller on successive calls --- src/Viewport.ts | 1 - src/addons/fit/fit.js | 10 ++++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Viewport.ts b/src/Viewport.ts index 88433ed5..2de698a0 100644 --- a/src/Viewport.ts +++ b/src/Viewport.ts @@ -56,7 +56,6 @@ export class Viewport implements IViewport { if (rowHeightChanged) { this.currentRowHeight = lineHeight; this.viewportElement.style.lineHeight = lineHeight + 'px'; - this.terminal.element.style.lineHeight = lineHeight + 'px'; } const viewportHeightChanged = this.lastRecordedViewportHeight !== this.terminal.rows; if (rowHeightChanged || viewportHeightChanged) { diff --git a/src/addons/fit/fit.js b/src/addons/fit/fit.js index 4f554579..60214dad 100644 --- a/src/addons/fit/fit.js +++ b/src/addons/fit/fit.js @@ -44,8 +44,8 @@ var availableHeight = parentElementHeight - elementPaddingVer; var availableWidth = parentElementWidth - elementPaddingHor; var geometry = { - cols: parseInt(availableWidth / term.charMeasure.width, 10), - rows: parseInt(availableHeight / (term.charMeasure.height * term.getOption('lineHeight')), 10) + cols: Math.floor(availableWidth / term.charMeasure.width), + rows: Math.floor(availableHeight / Math.ceil(term.charMeasure.height * term.getOption('lineHeight'))) }; return geometry; @@ -59,8 +59,10 @@ if (geometry) { // Force a full render - term.renderer.clear(); - term.resize(geometry.cols, geometry.rows); + if (term.rows !== geometry.rows || term.cols !== geometry.cols) { + term.renderer.clear(); + term.resize(geometry.cols, geometry.rows); + } } }, 0); }; From 7973b26e7a956ed6e59bd4e9d80a2b0195ad3cc5 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 2 Sep 2017 19:45:44 -0700 Subject: [PATCH 173/378] Include new options in ITerminalOptions --- typings/xterm.d.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 72206c82..b1cea816 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -41,6 +41,21 @@ interface ITerminalOptions { */ disableStdin?: boolean; + /** + * The font size used to render text. + */ + fontSize?: number; + + /** + * The font family used to render text. + */ + fontFamily?: string; + + /** + * The line height used to render text. + */ + lineHeight?: number; + /** * The number of rows in the terminal. */ From 8427f7490dd7732bdd0e812feaea11a1d98f24f3 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 2 Sep 2017 21:13:44 -0700 Subject: [PATCH 174/378] Clear state when resizing as canvases get cleared --- src/renderer/BackgroundRenderLayer.ts | 2 ++ src/renderer/CursorRenderLayer.ts | 12 ++++++++++++ src/renderer/ForegroundRenderLayer.ts | 2 ++ src/renderer/SelectionRenderLayer.ts | 9 +++++++++ src/utils/TestUtils.test.ts | 2 ++ 5 files changed, 27 insertions(+) diff --git a/src/renderer/BackgroundRenderLayer.ts b/src/renderer/BackgroundRenderLayer.ts index 729a8ec5..292847c2 100644 --- a/src/renderer/BackgroundRenderLayer.ts +++ b/src/renderer/BackgroundRenderLayer.ts @@ -15,6 +15,8 @@ export class BackgroundRenderLayer extends BaseRenderLayer { public resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void { super.resize(terminal, canvasWidth, canvasHeight, charSizeChanged); + // Resizing the canvas discards the contents of the canvas so clear state + this._state.clear(); this._state.resize(terminal.cols, terminal.rows); } diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index cc443b49..482d4e14 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -43,6 +43,18 @@ export class CursorRenderLayer extends BaseRenderLayer { // TODO: Consider initial options? Maybe onOptionsChanged should be called at the end of open? } + public resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void { + super.resize(terminal, canvasWidth, canvasHeight, charSizeChanged); + // Resizing the canvas discards the contents of the canvas so clear state + this._state = { + x: null, + y: null, + isFocused: null, + style: null, + width: null, + }; + } + public reset(terminal: ITerminal): void { this._clearCursor(); if (this._cursorBlinkStateManager) { diff --git a/src/renderer/ForegroundRenderLayer.ts b/src/renderer/ForegroundRenderLayer.ts index 1f579f91..e8835dea 100644 --- a/src/renderer/ForegroundRenderLayer.ts +++ b/src/renderer/ForegroundRenderLayer.ts @@ -16,6 +16,8 @@ export class ForegroundRenderLayer extends BaseRenderLayer { public resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void { super.resize(terminal, canvasWidth, canvasHeight, charSizeChanged); + // Resizing the canvas discards the contents of the canvas so clear state + this._state.clear(); this._state.resize(terminal.cols, terminal.rows); } diff --git a/src/renderer/SelectionRenderLayer.ts b/src/renderer/SelectionRenderLayer.ts index 1dcb8ab6..7f131da8 100644 --- a/src/renderer/SelectionRenderLayer.ts +++ b/src/renderer/SelectionRenderLayer.ts @@ -16,6 +16,15 @@ export class SelectionRenderLayer extends BaseRenderLayer { }; } + public resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void { + super.resize(terminal, canvasWidth, canvasHeight, charSizeChanged); + // Resizing the canvas discards the contents of the canvas so clear state + this._state = { + start: null, + end: null + }; + } + public reset(terminal: ITerminal): void { if (this._state.start && this._state.end) { this._state = { diff --git a/src/utils/TestUtils.test.ts b/src/utils/TestUtils.test.ts index 46a226a2..d4dc16ab 100644 --- a/src/utils/TestUtils.test.ts +++ b/src/utils/TestUtils.test.ts @@ -5,6 +5,7 @@ import { ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager, ITerminalOptions, IListenerType, IInputHandlingTerminal, IViewport, ICircularList, ICompositionHelper } from '../Interfaces'; import { LineData } from '../Types'; import * as Browser from './Browser'; +import { IColorSet } from '../renderer/Interfaces'; export class MockTerminal implements ITerminal { isFocused: boolean; @@ -180,6 +181,7 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal { } export class MockBuffer implements IBuffer { + isCursorInViewport: boolean; lines: ICircularList<[number, string, number][]>; ydisp: number; ybase: number; From fd4e9d8a1bcf2fa4e6b4f275d48725e7670c534e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 2 Sep 2017 23:13:22 -0700 Subject: [PATCH 175/378] Fix graphical glitch wide chars would leave behind Wide chars now 'own' the character next to them and are entirely responsible for drawing and clean up --- src/renderer/BaseRenderLayer.ts | 5 +++++ src/renderer/ForegroundRenderLayer.ts | 21 +++++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 912d351f..686a1d25 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -128,6 +128,11 @@ export abstract class BaseRenderLayer implements IRenderLayer { } protected drawChar(terminal: ITerminal, char: string, code: number, width: number, x: number, y: number, fg: number, underline: boolean = false): void { + // Clear the cell next to this character if it's wide + if (width === 2) { + this.clearCells(x + 1, y, 1, 1); + } + let colorIndex = 0; if (fg < 256) { colorIndex = fg + 1; diff --git a/src/renderer/ForegroundRenderLayer.ts b/src/renderer/ForegroundRenderLayer.ts index e8835dea..8ee81f02 100644 --- a/src/renderer/ForegroundRenderLayer.ts +++ b/src/renderer/ForegroundRenderLayer.ts @@ -48,6 +48,14 @@ export class ForegroundRenderLayer extends BaseRenderLayer { const code: number = charData[CHAR_DATA_CODE_INDEX]; const char: string = charData[CHAR_DATA_CHAR_INDEX]; const attr: number = charData[CHAR_DATA_ATTR_INDEX]; + const width: number = charData[CHAR_DATA_WIDTH_INDEX]; + + // The character to the left is a wide character, drawing is owned by + // the char at x-1 + if (width === 0) { + this._state.cache[x][y] = null; + continue; + } // Skip rendering if the character is identical const state = this._state.cache[x][y]; @@ -59,7 +67,7 @@ export class ForegroundRenderLayer extends BaseRenderLayer { // Clear the old character if present if (state && state[CHAR_DATA_CODE_INDEX] !== 32 /*' '*/) { - this.clearCells(x, y, 1, 1); + this.clearChar(x, y); } this._state.cache[x][y] = charData; @@ -102,11 +110,20 @@ export class ForegroundRenderLayer extends BaseRenderLayer { this.drawBottomLineAtCell(x, y); } - const width: number = charData[CHAR_DATA_WIDTH_INDEX]; this.drawChar(terminal, char, code, width, x, y, fg); this._ctx.restore(); } } } + + private clearChar(x: number, y: number): void { + let colsToClear = 1; + // Clear the adjacent character if it was wide + const state = this._state.cache[x][y]; + if (state && state[CHAR_DATA_WIDTH_INDEX] === 2) { + colsToClear = 2; + } + this.clearCells(x, y, colsToClear, 1); + } } From 5a4851630e527ebbe1d002e109a5c62588d77af3 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 3 Sep 2017 07:27:52 -0700 Subject: [PATCH 176/378] Prevent updating of invalid terminal row ranges --- src/renderer/Renderer.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index adf5bbd0..bc1a7a1b 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -123,6 +123,8 @@ export class Renderer { this._refreshAnimationFrame = null; // Render + start = Math.max(start, 0); + end = Math.min(end, this._terminal.rows - 1); this._renderLayers.forEach(l => l.onGridChanged(this._terminal, start, end)); this._terminal.emit('refresh', {start, end}); } From deb47e29bf884bc7c696e3e135928d3f558dfa75 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 3 Sep 2017 07:41:18 -0700 Subject: [PATCH 177/378] Fix canvas resizing with non-1 lineHeight --- src/renderer/Renderer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index bc1a7a1b..58f31ff7 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -48,13 +48,13 @@ export class Renderer { public onResize(cols: number, rows: number): void { const width = this._terminal.charMeasure.width * this._terminal.cols; - const height = Math.ceil(this._terminal.charMeasure.height * this._terminal.options.lineHeight) * this._terminal.rows; + const height = Math.floor(this._terminal.charMeasure.height * this._terminal.options.lineHeight) * this._terminal.rows; this._renderLayers.forEach(l => l.resize(this._terminal, width, height, false)); } public onCharSizeChanged(charWidth: number, charHeight: number): void { const width = charWidth * this._terminal.cols; - const height = charHeight * this._terminal.rows; + const height = Math.floor(charHeight * this._terminal.options.lineHeight) * this._terminal.rows; this._renderLayers.forEach(l => l.resize(this._terminal, width, height, true)); } From a3297b001773c00d68fafe5309db74fc63397762 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 3 Sep 2017 07:57:16 -0700 Subject: [PATCH 178/378] Fix viewport/background size after char size changed --- src/Terminal.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Terminal.ts b/src/Terminal.ts index d47c6bf8..4373f5ec 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -608,6 +608,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.charMeasure = new CharMeasure(document, this.helperContainer); this.viewport = new Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasure); + this.charMeasure.on('charsizechanged', () => this.viewport.syncScrollArea()); this.renderer = new Renderer(this); this.on('cursormove', () => this.renderer.onCursorMove()); this.on('resize', () => this.renderer.onResize(this.cols, this.rows)); From bc3470c272e4b32f7daec09711d2892828766db4 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 3 Sep 2017 08:06:27 -0700 Subject: [PATCH 179/378] Fix default bold text --- src/renderer/BaseRenderLayer.ts | 10 ++++++++-- src/renderer/CharAtlas.ts | 12 ++++++++++-- src/renderer/ForegroundRenderLayer.ts | 2 +- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 686a1d25..53c48b69 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -127,7 +127,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._ctx.restore(); } - protected drawChar(terminal: ITerminal, char: string, code: number, width: number, x: number, y: number, fg: number, underline: boolean = false): void { + protected drawChar(terminal: ITerminal, char: string, code: number, width: number, x: number, y: number, fg: number, bold: boolean): void { // Clear the cell next to this character if it's wide if (width === 2) { this.clearCells(x + 1, y, 1, 1); @@ -135,7 +135,12 @@ export abstract class BaseRenderLayer implements IRenderLayer { let colorIndex = 0; if (fg < 256) { - colorIndex = fg + 1; + colorIndex = fg + 2; + } else { + // If default color and bold + if (bold) { + colorIndex = 1; + } } const isAscii = code < 256; const isBasicColor = (colorIndex > 0 && fg < 16); @@ -151,6 +156,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._drawUncachedChar(terminal, char, width, fg, x, y); } // This draws the atlas (for debugging purposes) + // this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); // this._ctx.drawImage(this._charAtlas, 0, 0); } diff --git a/src/renderer/CharAtlas.ts b/src/renderer/CharAtlas.ts index d25520d1..a92037a8 100644 --- a/src/renderer/CharAtlas.ts +++ b/src/renderer/CharAtlas.ts @@ -105,7 +105,7 @@ class CharAtlasGenerator { const cellWidth = scaledCharWidth + CHAR_ATLAS_CELL_SPACING; const cellHeight = scaledCharHeight + CHAR_ATLAS_CELL_SPACING; this._canvas.width = 255 * cellWidth; - this._canvas.height = (/*default*/1 + /*0-15*/16) * cellHeight; + this._canvas.height = (/*default+default bold*/2 + /*0-15*/16) * cellHeight; this._ctx.save(); this._ctx.fillStyle = foreground; @@ -116,14 +116,22 @@ class CharAtlasGenerator { for (let i = 0; i < 256; i++) { this._ctx.fillText(String.fromCharCode(i), i * cellWidth, 0); } + // Default color bold + this._ctx.save(); + this._ctx.font = `bold ${this._ctx.font}`; + for (let i = 0; i < 256; i++) { + this._ctx.fillText(String.fromCharCode(i), i * cellWidth, cellHeight); + } + this._ctx.restore(); // Colors 0-15 + this._ctx.font = `${fontSize * window.devicePixelRatio}px ${fontFamily}`; for (let colorIndex = 0; colorIndex < 16; colorIndex++) { // colors 8-15 are bold if (colorIndex === 8) { this._ctx.font = `bold ${this._ctx.font}`; } - const y = (colorIndex + 1) * cellHeight; + const y = (colorIndex + 2) * cellHeight; // Draw ascii characters for (let i = 0; i < 256; i++) { this._ctx.fillStyle = ansiColors[colorIndex]; diff --git a/src/renderer/ForegroundRenderLayer.ts b/src/renderer/ForegroundRenderLayer.ts index 8ee81f02..907f686d 100644 --- a/src/renderer/ForegroundRenderLayer.ts +++ b/src/renderer/ForegroundRenderLayer.ts @@ -110,7 +110,7 @@ export class ForegroundRenderLayer extends BaseRenderLayer { this.drawBottomLineAtCell(x, y); } - this.drawChar(terminal, char, code, width, x, y, fg); + this.drawChar(terminal, char, code, width, x, y, fg, !!(flags & FLAGS.BOLD)); this._ctx.restore(); } From 48b37453ea3796a9dc9de81deccef2a5349cbd49 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 3 Sep 2017 08:40:00 -0700 Subject: [PATCH 180/378] Ensure char measure's line height isn't influenced by outside --- src/utils/CharMeasure.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/CharMeasure.ts b/src/utils/CharMeasure.ts index 1366bb63..37d6697a 100644 --- a/src/utils/CharMeasure.ts +++ b/src/utils/CharMeasure.ts @@ -38,6 +38,7 @@ export class CharMeasure extends EventEmitter implements ICharMeasure { this._measureElement.style.position = 'absolute'; this._measureElement.style.top = '0'; this._measureElement.style.left = '-9999em'; + this._measureElement.style.lineHeight = 'normal'; this._measureElement.textContent = 'W'; this._measureElement.setAttribute('aria-hidden', 'true'); this._parentElement.appendChild(this._measureElement); From 2d1a1bfb76974219e6a9401e709c2acad501dd15 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 3 Sep 2017 09:05:26 -0700 Subject: [PATCH 181/378] Fix inconsistency with lineHeight --- src/renderer/BaseRenderLayer.ts | 4 ++-- src/renderer/Renderer.ts | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 53c48b69..462c4eca 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -57,7 +57,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { public resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void { this.scaledCharWidth = terminal.charMeasure.width * window.devicePixelRatio; this.scaledCharHeight = terminal.charMeasure.height * window.devicePixelRatio; - this.scaledLineHeight = Math.ceil(this.scaledCharHeight * terminal.options.lineHeight); + this.scaledLineHeight = Math.floor(this.scaledCharHeight * terminal.options.lineHeight); this.scaledLineDrawY = terminal.options.lineHeight === 1 ? 0 : Math.round((this.scaledLineHeight - this.scaledCharHeight) / 2); this._canvas.width = canvasWidth * window.devicePixelRatio; this._canvas.height = canvasHeight * window.devicePixelRatio; @@ -143,7 +143,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { } } const isAscii = code < 256; - const isBasicColor = (colorIndex > 0 && fg < 16); + const isBasicColor = (colorIndex > 1 && fg < 16); const isDefaultColor = fg >= 256; if (isAscii && (isBasicColor || isDefaultColor)) { // ImageBitmap's draw about twice as fast as from a canvas diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 58f31ff7..bc0717dc 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -47,6 +47,9 @@ export class Renderer { } public onResize(cols: number, rows: number): void { + if (!this._terminal.charMeasure.width || !this._terminal.charMeasure.height) { + return; + } const width = this._terminal.charMeasure.width * this._terminal.cols; const height = Math.floor(this._terminal.charMeasure.height * this._terminal.options.lineHeight) * this._terminal.rows; this._renderLayers.forEach(l => l.resize(this._terminal, width, height, false)); From 5619abe12dd4cdaf7a496391bd58a3ee3cb75700 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 3 Sep 2017 11:20:29 -0700 Subject: [PATCH 182/378] Basic link support using mouse zone manager --- src/Linkifier.ts | 238 ++++++++-------------------------- src/Terminal.ts | 59 +++++---- src/Types.ts | 3 +- src/input/Interfaces.ts | 11 ++ src/input/MouseZoneManager.ts | 85 ++++++++++++ 5 files changed, 186 insertions(+), 210 deletions(-) create mode 100644 src/input/Interfaces.ts create mode 100644 src/input/MouseZoneManager.ts diff --git a/src/Linkifier.ts b/src/Linkifier.ts index 5a0fc1e1..bfac87ab 100644 --- a/src/Linkifier.ts +++ b/src/Linkifier.ts @@ -2,8 +2,10 @@ * @license MIT */ -import { ILinkMatcherOptions } from './Interfaces'; -import { LinkMatcher, LinkMatcherHandler, LinkMatcherValidationCallback } from './Types'; +import { ILinkMatcherOptions, ITerminal } from './Interfaces'; +import { LinkMatcher, LinkMatcherHandler, LinkMatcherValidationCallback, LineData } from './Types'; +import { IMouseZoneManager } from './input/Interfaces'; +import { MouseZone } from './input/MouseZoneManager'; const INVALID_LINK_CLASS = 'xterm-invalid-link'; @@ -44,13 +46,14 @@ export class Linkifier { protected _linkMatchers: LinkMatcher[]; - private _document: Document; - private _rows: HTMLElement[]; + private _mouseZoneManager: IMouseZoneManager; private _rowTimeoutIds: number[]; private _rowsTimeoutId: number; private _nextLinkMatcherId = HYPERTEXT_LINK_MATCHER_ID; - constructor() { + constructor( + private _terminal: ITerminal + ) { this._rowTimeoutIds = []; this._linkMatchers = []; this.registerLinkMatcher(strictUrlRegex, null, { matchIndex: 1 }); @@ -61,22 +64,21 @@ export class Linkifier { * @param document The document object. * @param rows The array of rows to apply links to. */ - public attachToDom(document: Document, rows: HTMLElement[]): void { - this._document = document; - this._rows = rows; + public attachToDom(mouseZoneManager: IMouseZoneManager): void { + console.log('attachToDom', mouseZoneManager); + this._mouseZoneManager = mouseZoneManager; } public linkifyRows(start: number, end: number): void { - // for (let i = start; i <= end; i++) { - // this.linkifyRow(i); - // } - - // Don't attempt linkify if not yet attached to DOM - if (!this._document) { + if (!this._mouseZoneManager) { return; } + // Clear out any existing links + this._mouseZoneManager.clearAll(); + // TODO: Cancel any validation callbacks + if (this._rowsTimeoutId) { clearTimeout(this._rowsTimeoutId); } @@ -95,7 +97,7 @@ export class Linkifier { */ public linkifyRow(rowIndex: number): void { // Don't attempt linkify if not yet attached to DOM - if (!this._document) { + if (!this._mouseZoneManager) { return; } @@ -194,194 +196,68 @@ export class Linkifier { * @param {number} rowIndex The index of the row to linkify. */ private _linkifyRow(rowIndex: number): void { - const row = this._rows[rowIndex]; - if (!row) { + const absoluteRowIndex = this._terminal.buffer.ydisp + rowIndex; + if (absoluteRowIndex >= this._terminal.buffer.lines.length) { return; } - const text = row.textContent; + const text = this._terminal.buffer.translateBufferLineToString(absoluteRowIndex, false); for (let i = 0; i < this._linkMatchers.length; i++) { - const matcher = this._linkMatchers[i]; - const linkElements = this._doLinkifyRow(row, matcher); - if (linkElements.length > 0) { - // Fire validation callback - if (matcher.validationCallback) { - for (let j = 0; j < linkElements.length; j++) { - const element = linkElements[j]; - matcher.validationCallback(element.textContent, element, isValid => { - if (!isValid) { - element.classList.add(INVALID_LINK_CLASS); - } - }); - } - } - // Only allow a single LinkMatcher to trigger on any given row. - return; - } + this._doLinkifyRow(rowIndex, text, this._linkMatchers[i]); } } /** * Linkifies a row given a specific handler. - * @param {HTMLElement} row The row to linkify. + * @param rowIndex The row index to linkify. + * @param text The text of the row. * @param {LinkMatcher} matcher The link matcher for this line. * @return The link element(s) that were added. */ - private _doLinkifyRow(row: HTMLElement, matcher: LinkMatcher): HTMLElement[] { + private _doLinkifyRow(rowIndex: number, text: string, matcher: LinkMatcher): void { // Iterate over nodes as we want to consider text nodes let result = []; const isHttpLinkMatcher = matcher.id === HYPERTEXT_LINK_MATCHER_ID; - const nodes = row.childNodes; - +console.log('linkifyRow: ', text); // Find the first match - let match = row.textContent.match(matcher.regex); + let match = text.match(matcher.regex); if (!match || match.length === 0) { - return result; + return; } let uri = match[typeof matcher.matchIndex !== 'number' ? 0 : matcher.matchIndex]; + + // TODO: Match more than one link per row // Set the next searches start index - let rowStartIndex = match.index + uri.length; - - for (let i = 0; i < nodes.length; i++) { - const node = nodes[i]; - const searchIndex = node.textContent.indexOf(uri); - if (searchIndex >= 0) { - const linkElement = this._createAnchorElement(uri, matcher.handler, isHttpLinkMatcher); - if (node.textContent.length === uri.length) { - // Matches entire string - if (node.nodeType === 3 /*Node.TEXT_NODE*/) { - this._replaceNode(node, linkElement); - } else { - const element = (node); - if (element.nodeName === 'A') { - // This row has already been linkified - return result; - } - element.innerHTML = ''; - element.appendChild(linkElement); - } - } else if (node.childNodes.length > 1) { - // Matches part of string in an element with multiple child nodes - for (let j = 0; j < node.childNodes.length; j++) { - const childNode = node.childNodes[j]; - const childSearchIndex = childNode.textContent.indexOf(uri); - if (childSearchIndex !== -1) { - // Match found in currentNode - this._replaceNodeSubstringWithNode(childNode, linkElement, uri, childSearchIndex); - // Don't need to count nodesAdded by replacing the node as this - // is a child node, not a top-level node. - break; - } - } - } else { - // Matches part of string in a single text node - const nodesAdded = this._replaceNodeSubstringWithNode(node, linkElement, uri, searchIndex); - // No need to consider the new nodes - i += nodesAdded; - } - result.push(linkElement); - - // Find the next match - match = row.textContent.substring(rowStartIndex).match(matcher.regex); - if (!match || match.length === 0) { - return result; - } - uri = match[typeof matcher.matchIndex !== 'number' ? 0 : matcher.matchIndex]; - rowStartIndex += match.index + uri.length; - } - } - return result; - } - - /** - * Creates a link anchor element. - * @param {string} uri The uri of the link. - * @return {HTMLAnchorElement} The link. - */ - private _createAnchorElement(uri: string, handler: LinkMatcherHandler, isHypertextLinkHandler: boolean): HTMLAnchorElement { - const element = this._document.createElement('a'); - element.textContent = uri; - element.draggable = false; - if (isHypertextLinkHandler) { - element.href = uri; - // Force link on another tab so work is not lost - element.target = '_blank'; - element.addEventListener('click', (event: MouseEvent) => { - if (handler) { - return handler(event, uri); + // let rowStartIndex = match.index + uri.length; +console.log('matcher', matcher); + // Ensure the link is valid before registering + if (matcher.validationCallback) { + matcher.validationCallback(text, isValid => { + if (isValid) { + // TODO: Discard link if the line has already changed? + this._addLink(match.index, match.length, uri, matcher); } }); } else { - element.addEventListener('click', (event: MouseEvent) => { - // Don't execute the handler if the link is flagged as invalid - if (element.classList.contains(INVALID_LINK_CLASS)) { - return; + this._addLink(match.index, match.length, uri, matcher); + } + } + + private _addLink(x: number, length: number, uri: string, matcher: LinkMatcher): void { + this._mouseZoneManager.add(new MouseZone( + x, + x + length, + e => { + if (matcher.hoverCallback) { + return matcher.hoverCallback(e, uri); } - return handler(event, uri); - }); - } - return element; - } - - /** - * Replace a node with 1 or more other nodes. - * @param {Node} oldNode The node to replace. - * @param {Node[]} newNodes The new nodes to insert in order. - */ - private _replaceNode(oldNode: Node, ...newNodes: Node[]): void { - const parent = oldNode.parentNode; - for (let i = 0; i < newNodes.length; i++) { - parent.insertBefore(newNodes[i], oldNode); - } - parent.removeChild(oldNode); - } - - /** - * Replace a substring within a node with a new node. - * @param {Node} targetNode The target node; either a text node or a - * containing a single text node. - * @param {Node} newNode The new node to insert. - * @param {string} substring The substring to replace. - * @param {number} substringIndex The index of the substring within the string. - * @return The number of nodes to skip when searching for the next uri. - */ - private _replaceNodeSubstringWithNode(targetNode: Node, newNode: Node, substring: string, substringIndex: number): number { - // If the targetNode is a non-text node with a single child, make the child - // the new targetNode. - if (targetNode.childNodes.length === 1) { - targetNode = targetNode.childNodes[0]; - } - - // The targetNode will be either a text node or a . The text node - // (targetNode or its only-child) needs to be replaced with newNode plus new - // text nodes potentially on either side. - if (targetNode.nodeType !== 3/*Node.TEXT_NODE*/) { - throw new Error('targetNode must be a text node or only contain a single text node'); - } - - const fullText = targetNode.textContent; - - if (substringIndex === 0) { - // Replace with - const rightText = fullText.substring(substring.length); - const rightTextNode = this._document.createTextNode(rightText); - this._replaceNode(targetNode, newNode, rightTextNode); - return 0; - } - - if (substringIndex === targetNode.textContent.length - substring.length) { - // Replace with - const leftText = fullText.substring(0, substringIndex); - const leftTextNode = this._document.createTextNode(leftText); - this._replaceNode(targetNode, leftTextNode, newNode); - return 0; - } - - // Replace with - const leftText = fullText.substring(0, substringIndex); - const leftTextNode = this._document.createTextNode(leftText); - const rightText = fullText.substring(substringIndex + substring.length); - const rightTextNode = this._document.createTextNode(rightText); - this._replaceNode(targetNode, leftTextNode, newNode, rightTextNode); - return 1; + console.log('hover', uri); + }, + e => { + if (matcher.handler) { + return matcher.handler(e, uri); + } + window.open(uri, '_blink'); + } + )); } } diff --git a/src/Terminal.ts b/src/Terminal.ts index 4373f5ec..6f93ca2d 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -29,9 +29,8 @@ import { CircularList } from './utils/CircularList'; import { C0 } from './EscapeSequences'; import { InputHandler } from './InputHandler'; import { Parser } from './Parser'; -// import { Renderer } from './Renderer'; import { Renderer } from './renderer/Renderer'; -// import { Linkifier } from './Linkifier'; +import { Linkifier } from './Linkifier'; import { SelectionManager } from './SelectionManager'; import { CharMeasure } from './utils/CharMeasure'; import * as Browser from './utils/Browser'; @@ -42,6 +41,8 @@ import { CustomKeyEventHandler, Charset, LinkMatcherHandler, LinkMatcherValidati import { ITerminal, IBrowser, ITerminalOptions, IInputHandlingTerminal, ILinkMatcherOptions, IViewport, ICompositionHelper, ITheme } from './Interfaces'; import { BellSound } from './utils/Sounds'; import { DEFAULT_ANSI_COLORS } from './renderer/ColorManager'; +import { IMouseZoneManager } from './input/Interfaces'; +import { MouseZoneManager } from './input/MouseZoneManager'; // Declare for RequireJS in loadAddon declare var define: any; @@ -184,12 +185,13 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT private parser: Parser; private renderer: Renderer; public selectionManager: SelectionManager; - // private linkifier: Linkifier; + private linkifier: Linkifier; public buffers: BufferSet; public buffer: Buffer; public viewport: IViewport; private compositionHelper: ICompositionHelper; public charMeasure: CharMeasure; + private _mouseZoneManager: IMouseZoneManager; public cols: number; public rows: number; @@ -281,7 +283,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT // Reuse renderer if the Terminal is being recreated via a reset call. this.renderer = this.renderer || null; this.selectionManager = this.selectionManager || null; - // this.linkifier = this.linkifier || new Linkifier(); + this.linkifier = this.linkifier || new Linkifier(this); + this._mouseZoneManager = this._mouseZoneManager || null; // Create the terminal's buffers and set the current buffer this.buffers = new BufferSet(this); @@ -576,8 +579,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT // preload audio this.syncBellSound(); - // TODO: Re-enable linkifier - // this.linkifier.attachToDom(document, this.children); + this._mouseZoneManager = new MouseZoneManager(this); + this.linkifier.attachToDom(this._mouseZoneManager); // Create the container that will hold helpers like the textarea for // capturing DOM Events. Then produce the helpers. @@ -1005,9 +1008,9 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * @param {number} end The row to end at (between start and this.rows - 1). */ private queueLinkification(start: number, end: number): void { - // if (this.linkifier) { - // this.linkifier.linkifyRows(0, this.rows); - // } + if (this.linkifier) { + this.linkifier.linkifyRows(0, this.rows); + } } /** @@ -1215,11 +1218,11 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * @param handler The handler callback function. */ public setHypertextLinkHandler(handler: LinkMatcherHandler): void { - // if (!this.linkifier) { - // throw new Error('Cannot attach a hypertext link handler before Terminal.open is called'); - // } - // this.linkifier.setHypertextLinkHandler(handler); - // // Refresh to force links to refresh + if (!this.linkifier) { + throw new Error('Cannot attach a hypertext link handler before Terminal.open is called'); + } + this.linkifier.setHypertextLinkHandler(handler); + // Refresh to force links to refresh // this.refresh(0, this.rows - 1); } @@ -1230,10 +1233,10 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * be cleared with null. */ public setHypertextValidationCallback(callback: LinkMatcherValidationCallback): void { - // if (!this.linkifier) { - // throw new Error('Cannot attach a hypertext validation callback before Terminal.open is called'); - // } - // this.linkifier.setHypertextValidationCallback(callback); + if (!this.linkifier) { + throw new Error('Cannot attach a hypertext validation callback before Terminal.open is called'); + } + this.linkifier.setHypertextValidationCallback(callback); // // Refresh to force links to refresh // this.refresh(0, this.rows - 1); } @@ -1249,11 +1252,11 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * @return The ID of the new matcher, this can be used to deregister. */ public registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options?: ILinkMatcherOptions): number { - // if (this.linkifier) { - // const matcherId = this.linkifier.registerLinkMatcher(regex, handler, options); - // this.refresh(0, this.rows - 1); - // return matcherId; - // } + if (this.linkifier) { + const matcherId = this.linkifier.registerLinkMatcher(regex, handler, options); + // this.refresh(0, this.rows - 1); + return matcherId; + } return 0; } @@ -1262,11 +1265,11 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * @param matcherId The link matcher's ID (returned after register) */ public deregisterLinkMatcher(matcherId: number): void { - // if (this.linkifier) { - // if (this.linkifier.deregisterLinkMatcher(matcherId)) { - // this.refresh(0, this.rows - 1); - // } - // } + if (this.linkifier) { + if (this.linkifier.deregisterLinkMatcher(matcherId)) { + // this.refresh(0, this.rows - 1); + } + } } /** diff --git a/src/Types.ts b/src/Types.ts index 180dc5d6..378a66ac 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -6,12 +6,13 @@ export type LinkMatcher = { id: number, regex: RegExp, handler: LinkMatcherHandler, + hoverCallback?: LinkMatcherHandler, matchIndex?: number, validationCallback?: LinkMatcherValidationCallback, priority?: number }; export type LinkMatcherHandler = (event: MouseEvent, uri: string) => boolean | void; -export type LinkMatcherValidationCallback = (uri: string, element: HTMLElement, callback: (isValid: boolean) => void) => void; +export type LinkMatcherValidationCallback = (uri: string, callback: (isValid: boolean) => void) => void; export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean; export type Charset = {[key: string]: string}; diff --git a/src/input/Interfaces.ts b/src/input/Interfaces.ts new file mode 100644 index 00000000..9bb90fed --- /dev/null +++ b/src/input/Interfaces.ts @@ -0,0 +1,11 @@ +export interface IMouseZoneManager { + add(zone: IMouseZone): void; + clearAll(): void; +} + +export interface IMouseZone { + x1: number; + x2: number; + hoverCallback: (e: MouseEvent) => any; + clickCallback: (e: MouseEvent) => any; +} diff --git a/src/input/MouseZoneManager.ts b/src/input/MouseZoneManager.ts new file mode 100644 index 00000000..7e63ee0b --- /dev/null +++ b/src/input/MouseZoneManager.ts @@ -0,0 +1,85 @@ +import { IMouseZoneManager, IMouseZone } from './Interfaces'; +import { ITerminal } from '../Interfaces'; + +/** + * The MouseZoneManager allows components to register zones within the terminal + * that trigger hover and click callbacks. + * + * This class was intentionally made not so robust initially as the only case it + * needed to support was single-line links which never overlap. Improvements can + * be made in the future. + */ +export class MouseZoneManager implements IMouseZoneManager { + private _zones: IMouseZone[] = []; + + private _areZonesActive: boolean = false; + private _mouseMoveListener: (e: MouseEvent) => any; + private _mouseDownListener: (e: MouseEvent) => any; + private _clickListener: (e: MouseEvent) => any; + + constructor( + private _terminal: ITerminal + ) { + // These events are expensive, only listen to it when mouse zones are active + this._mouseMoveListener = e => this._onMouseMove(e); + this._clickListener = e => this._onClick(e); + } + + public add(zone: IMouseZone): void { + console.log('add zone', zone); + this._zones.push(zone); + if (this._zones.length === 1) { + this._activate(); + } + } + + public clearAll(): void { + this._zones.length = 0; + this._deactivate(); + } + + private _activate(): void { + if (!this._areZonesActive) { + console.log('_addMoveListener'); + this._areZonesActive = true; + this._terminal.element.addEventListener('mousemove', this._mouseMoveListener); + this._terminal.element.addEventListener('click', this._clickListener); + } + } + + private _deactivate(): void { + if (this._areZonesActive) { + console.log('_removeMoveListener'); + this._areZonesActive = false; + this._terminal.element.removeEventListener('mousemove', this._mouseMoveListener); + this._terminal.element.removeEventListener('click', this._clickListener); + } + } + + private _onMouseMove(e: MouseEvent): void { + console.log('move'); + // TODO: Handle hover + } + + private _onClick(e: MouseEvent): void { + const zone = this._findZoneEventAt(e); + if (zone) { + zone.clickCallback(e); + e.preventDefault(); + } + } + + private _findZoneEventAt(e: MouseEvent): IMouseZone { + return this._zones[0]; + } +} + +export class MouseZone implements IMouseZone { + constructor( + public x1: number, + public x2: number, + public hoverCallback: (e: MouseEvent) => any, + public clickCallback: (e: MouseEvent) => any + ) { + } +} From 923ea7247c1e2a80ad29c9078c27e50356f8a471 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 3 Sep 2017 11:39:17 -0700 Subject: [PATCH 183/378] Get links working on proper coords --- src/Linkifier.ts | 19 +++++++++++-------- src/input/Interfaces.ts | 1 + src/input/MouseZoneManager.ts | 15 ++++++++++----- src/utils/Mouse.ts | 6 +++--- 4 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/Linkifier.ts b/src/Linkifier.ts index bfac87ab..f92badcf 100644 --- a/src/Linkifier.ts +++ b/src/Linkifier.ts @@ -217,7 +217,7 @@ export class Linkifier { // Iterate over nodes as we want to consider text nodes let result = []; const isHttpLinkMatcher = matcher.id === HYPERTEXT_LINK_MATCHER_ID; -console.log('linkifyRow: ', text); + // Find the first match let match = text.match(matcher.regex); if (!match || match.length === 0) { @@ -228,29 +228,32 @@ console.log('linkifyRow: ', text); // TODO: Match more than one link per row // Set the next searches start index // let rowStartIndex = match.index + uri.length; -console.log('matcher', matcher); + + // Get index, match.index is for the outer match which includes negated chars + const index = text.indexOf(uri); + // Ensure the link is valid before registering if (matcher.validationCallback) { matcher.validationCallback(text, isValid => { if (isValid) { // TODO: Discard link if the line has already changed? - this._addLink(match.index, match.length, uri, matcher); + this._addLink(index, rowIndex, uri, matcher); } }); } else { - this._addLink(match.index, match.length, uri, matcher); + this._addLink(index, rowIndex, uri, matcher); } } - private _addLink(x: number, length: number, uri: string, matcher: LinkMatcher): void { + private _addLink(x: number, y: number, uri: string, matcher: LinkMatcher): void { this._mouseZoneManager.add(new MouseZone( - x, - x + length, + x + 1, + x + 1 + uri.length, + y + 1, e => { if (matcher.hoverCallback) { return matcher.hoverCallback(e, uri); } - console.log('hover', uri); }, e => { if (matcher.handler) { diff --git a/src/input/Interfaces.ts b/src/input/Interfaces.ts index 9bb90fed..d0c0e480 100644 --- a/src/input/Interfaces.ts +++ b/src/input/Interfaces.ts @@ -6,6 +6,7 @@ export interface IMouseZoneManager { export interface IMouseZone { x1: number; x2: number; + y: number; hoverCallback: (e: MouseEvent) => any; clickCallback: (e: MouseEvent) => any; } diff --git a/src/input/MouseZoneManager.ts b/src/input/MouseZoneManager.ts index 7e63ee0b..6d8f017a 100644 --- a/src/input/MouseZoneManager.ts +++ b/src/input/MouseZoneManager.ts @@ -1,5 +1,6 @@ import { IMouseZoneManager, IMouseZone } from './Interfaces'; import { ITerminal } from '../Interfaces'; +import { getCoords } from '../utils/Mouse'; /** * The MouseZoneManager allows components to register zones within the terminal @@ -26,7 +27,6 @@ export class MouseZoneManager implements IMouseZoneManager { } public add(zone: IMouseZone): void { - console.log('add zone', zone); this._zones.push(zone); if (this._zones.length === 1) { this._activate(); @@ -40,7 +40,6 @@ export class MouseZoneManager implements IMouseZoneManager { private _activate(): void { if (!this._areZonesActive) { - console.log('_addMoveListener'); this._areZonesActive = true; this._terminal.element.addEventListener('mousemove', this._mouseMoveListener); this._terminal.element.addEventListener('click', this._clickListener); @@ -49,7 +48,6 @@ export class MouseZoneManager implements IMouseZoneManager { private _deactivate(): void { if (this._areZonesActive) { - console.log('_removeMoveListener'); this._areZonesActive = false; this._terminal.element.removeEventListener('mousemove', this._mouseMoveListener); this._terminal.element.removeEventListener('click', this._clickListener); @@ -57,7 +55,6 @@ export class MouseZoneManager implements IMouseZoneManager { } private _onMouseMove(e: MouseEvent): void { - console.log('move'); // TODO: Handle hover } @@ -70,7 +67,14 @@ export class MouseZoneManager implements IMouseZoneManager { } private _findZoneEventAt(e: MouseEvent): IMouseZone { - return this._zones[0]; + const coords = getCoords(e, this._terminal.element, this._terminal.charMeasure,this._terminal.options.lineHeight, this._terminal.cols, this._terminal.rows); + for (let i = 0; i < this._zones.length; i++) { + const zone = this._zones[i]; + if (zone.y === coords[1] && zone.x1 <= coords[0] && zone.x2 > coords[0]) { + return zone; + } + }; + return null; } } @@ -78,6 +82,7 @@ export class MouseZone implements IMouseZone { constructor( public x1: number, public x2: number, + public y: number, public hoverCallback: (e: MouseEvent) => any, public clickCallback: (e: MouseEvent) => any ) { diff --git a/src/utils/Mouse.ts b/src/utils/Mouse.ts index 77c422d5..0d433cd6 100644 --- a/src/utils/Mouse.ts +++ b/src/utils/Mouse.ts @@ -2,7 +2,7 @@ * @license MIT */ -import { CharMeasure } from './CharMeasure'; +import { ICharMeasure } from '../Interfaces'; export function getCoordsRelativeToElement(event: MouseEvent, element: HTMLElement): [number, number] { // Ignore browsers that don't support MouseEvent.pageX @@ -36,7 +36,7 @@ export function getCoordsRelativeToElement(event: MouseEvent, element: HTMLEleme * apply an offset to the x value such that the left half of the cell will * select that cell and the right half will select the next cell. */ -export function getCoords(event: MouseEvent, element: HTMLElement, charMeasure: CharMeasure, lineHeight: number, colCount: number, rowCount: number, isSelection?: boolean): [number, number] { +export function getCoords(event: MouseEvent, element: HTMLElement, charMeasure: ICharMeasure, lineHeight: number, colCount: number, rowCount: number, isSelection?: boolean): [number, number] { // Coordinates cannot be measured if charMeasure has not been initialized if (!charMeasure.width || !charMeasure.height) { return null; @@ -68,7 +68,7 @@ export function getCoords(event: MouseEvent, element: HTMLElement, charMeasure: * @param colCount The number of columns in the terminal. * @param rowCount The number of rows in the terminal. */ -export function getRawByteCoords(event: MouseEvent, element: HTMLElement, charMeasure: CharMeasure, lineHeight: number, colCount: number, rowCount: number): { x: number, y: number } { +export function getRawByteCoords(event: MouseEvent, element: HTMLElement, charMeasure: ICharMeasure, lineHeight: number, colCount: number, rowCount: number): { x: number, y: number } { const coords = getCoords(event, element, charMeasure, lineHeight, colCount, rowCount); let x = coords[0]; let y = coords[1]; From b30cb58ff42419e406c6abb1492101f7fb4e6c21 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 3 Sep 2017 12:47:04 -0700 Subject: [PATCH 184/378] Support hover callback in links --- src/Interfaces.ts | 4 ++++ src/Linkifier.ts | 12 ++++++------ src/Terminal.ts | 8 ++++---- src/input/Interfaces.ts | 2 +- src/input/MouseZoneManager.ts | 27 +++++++++++++++++++++++---- 5 files changed, 38 insertions(+), 15 deletions(-) diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 7ebf6983..ace4012e 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -236,6 +236,10 @@ export interface ILinkMatcherOptions { * false if invalid. */ validationCallback?: LinkMatcherValidationCallback; + /** + * A callback that fired when the mouse hovers over a link. + */ + hoverCallback?: LinkMatcherHandler; /** * 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 diff --git a/src/Linkifier.ts b/src/Linkifier.ts index f92badcf..20c5b713 100644 --- a/src/Linkifier.ts +++ b/src/Linkifier.ts @@ -65,7 +65,6 @@ export class Linkifier { * @param rows The array of rows to apply links to. */ public attachToDom(mouseZoneManager: IMouseZoneManager): void { - console.log('attachToDom', mouseZoneManager); this._mouseZoneManager = mouseZoneManager; } @@ -147,6 +146,7 @@ export class Linkifier { handler, matchIndex: options.matchIndex, validationCallback: options.validationCallback, + hoverCallback: options.hoverCallback, priority: options.priority || 0 }; this._addLinkMatcherToList(matcher); @@ -250,16 +250,16 @@ export class Linkifier { x + 1, x + 1 + uri.length, y + 1, - e => { - if (matcher.hoverCallback) { - return matcher.hoverCallback(e, uri); - } - }, e => { if (matcher.handler) { return matcher.handler(e, uri); } window.open(uri, '_blink'); + }, + e => { + if (matcher.hoverCallback) { + return matcher.hoverCallback(e, uri); + } } )); } diff --git a/src/Terminal.ts b/src/Terminal.ts index 6f93ca2d..c3d360bb 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1223,7 +1223,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT } this.linkifier.setHypertextLinkHandler(handler); // Refresh to force links to refresh - // this.refresh(0, this.rows - 1); + this.refresh(0, this.rows - 1); } /** @@ -1238,7 +1238,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT } this.linkifier.setHypertextValidationCallback(callback); // // Refresh to force links to refresh - // this.refresh(0, this.rows - 1); + this.refresh(0, this.rows - 1); } /** @@ -1254,7 +1254,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT public registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options?: ILinkMatcherOptions): number { if (this.linkifier) { const matcherId = this.linkifier.registerLinkMatcher(regex, handler, options); - // this.refresh(0, this.rows - 1); + this.refresh(0, this.rows - 1); return matcherId; } return 0; @@ -1267,7 +1267,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT public deregisterLinkMatcher(matcherId: number): void { if (this.linkifier) { if (this.linkifier.deregisterLinkMatcher(matcherId)) { - // this.refresh(0, this.rows - 1); + this.refresh(0, this.rows - 1); } } } diff --git a/src/input/Interfaces.ts b/src/input/Interfaces.ts index d0c0e480..15dcbae9 100644 --- a/src/input/Interfaces.ts +++ b/src/input/Interfaces.ts @@ -7,6 +7,6 @@ export interface IMouseZone { x1: number; x2: number; y: number; - hoverCallback: (e: MouseEvent) => any; clickCallback: (e: MouseEvent) => any; + hoverCallback?: (e: MouseEvent) => any; } diff --git a/src/input/MouseZoneManager.ts b/src/input/MouseZoneManager.ts index 6d8f017a..37a6647d 100644 --- a/src/input/MouseZoneManager.ts +++ b/src/input/MouseZoneManager.ts @@ -2,6 +2,8 @@ import { IMouseZoneManager, IMouseZone } from './Interfaces'; import { ITerminal } from '../Interfaces'; import { getCoords } from '../utils/Mouse'; +const HOVER_DURATION = 500; + /** * The MouseZoneManager allows components to register zones within the terminal * that trigger hover and click callbacks. @@ -18,6 +20,9 @@ export class MouseZoneManager implements IMouseZoneManager { private _mouseDownListener: (e: MouseEvent) => any; private _clickListener: (e: MouseEvent) => any; + private _hoverTimeout: number; + private _lastHoverCoords: [number, number] = [null, null]; + constructor( private _terminal: ITerminal ) { @@ -55,7 +60,21 @@ export class MouseZoneManager implements IMouseZoneManager { } private _onMouseMove(e: MouseEvent): void { - // TODO: Handle hover + if (this._lastHoverCoords[0] !== e.pageX && this._lastHoverCoords[1] !== e.pageY) { + if (this._hoverTimeout) { + clearTimeout(this._hoverTimeout); + } + this._hoverTimeout = setTimeout(() => this._onHover(e), HOVER_DURATION); + this._lastHoverCoords = [e.pageX, e.pageY]; + } + } + + private _onHover(e: MouseEvent): void { + const coords = getCoords(e, this._terminal.element, this._terminal.charMeasure, this._terminal.options.lineHeight, this._terminal.cols, this._terminal.rows); + const zone = this._findZoneEventAt(e); + if (zone && zone.hoverCallback) { + zone.hoverCallback(e); + } } private _onClick(e: MouseEvent): void { @@ -67,7 +86,7 @@ export class MouseZoneManager implements IMouseZoneManager { } private _findZoneEventAt(e: MouseEvent): IMouseZone { - const coords = getCoords(e, this._terminal.element, this._terminal.charMeasure,this._terminal.options.lineHeight, this._terminal.cols, this._terminal.rows); + const coords = getCoords(e, this._terminal.element, this._terminal.charMeasure, this._terminal.options.lineHeight, this._terminal.cols, this._terminal.rows); for (let i = 0; i < this._zones.length; i++) { const zone = this._zones[i]; if (zone.y === coords[1] && zone.x1 <= coords[0] && zone.x2 > coords[0]) { @@ -83,8 +102,8 @@ export class MouseZone implements IMouseZone { public x1: number, public x2: number, public y: number, - public hoverCallback: (e: MouseEvent) => any, - public clickCallback: (e: MouseEvent) => any + public clickCallback: (e: MouseEvent) => any, + public hoverCallback?: (e: MouseEvent) => any ) { } } From 17731c3de36037baa27c33c705b632984cd69fe0 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 3 Sep 2017 13:56:53 -0700 Subject: [PATCH 185/378] Support multiple links in a line, fix Linkifier tests --- src/Buffer.ts | 2 +- src/InputHandler.ts | 8 +- src/Interfaces.ts | 7 +- src/Linkifier.test.ts | 163 +++++++++++++++++++---------------- src/Linkifier.ts | 21 +++-- src/SelectionManager.test.ts | 32 +++---- src/Types.ts | 3 +- src/utils/TestUtils.test.ts | 9 +- typings/xterm.d.ts | 5 ++ 9 files changed, 136 insertions(+), 114 deletions(-) diff --git a/src/Buffer.ts b/src/Buffer.ts index 6931eea4..65b7d58e 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -114,7 +114,7 @@ export class Buffer implements IBuffer { if (this._lines.length > 0) { // Deal with columns increasing (we don't do anything when columns reduce) if (this._terminal.cols < newCols) { - const ch: CharData = [this._terminal.defAttr, ' ', 1]; // does xterm use the default attr? + const ch: CharData = [this._terminal.defAttr, ' ', 1, 32]; // does xterm use the default attr? for (let i = 0; i < this._lines.length; i++) { // TODO: This should be removed, with tests setup for the case that was // causing the underlying bug, see https://github.com/sourcelair/xterm.js/issues/824 diff --git a/src/InputHandler.ts b/src/InputHandler.ts index e9346c1a..3bf16a1c 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -189,7 +189,7 @@ export class InputHandler implements IInputHandler { const row = this._terminal.buffer.y + this._terminal.buffer.ybase; let j = this._terminal.buffer.x; - const ch: CharData = [this._terminal.eraseAttr(), ' ', 1]; // xterm + const ch: CharData = [this._terminal.eraseAttr(), ' ', 1, 32]; // xterm while (param-- && j < this._terminal.cols) { this._terminal.buffer.lines.get(row).splice(j++, 0, ch); @@ -488,7 +488,7 @@ export class InputHandler implements IInputHandler { } const row = this._terminal.buffer.y + this._terminal.buffer.ybase; - const ch: CharData = [this._terminal.eraseAttr(), ' ', 1]; // xterm + const ch: CharData = [this._terminal.eraseAttr(), ' ', 1, 32]; // xterm while (param--) { this._terminal.buffer.lines.get(row).splice(this._terminal.buffer.x, 1); @@ -536,7 +536,7 @@ export class InputHandler implements IInputHandler { const row = this._terminal.buffer.y + this._terminal.buffer.ybase; let j = this._terminal.buffer.x; - const ch: CharData = [this._terminal.eraseAttr(), ' ', 1]; // xterm + const ch: CharData = [this._terminal.eraseAttr(), ' ', 1, 32]; // xterm while (param-- && j < this._terminal.cols) { this._terminal.buffer.lines.get(row)[j++] = ch; @@ -590,7 +590,7 @@ export class InputHandler implements IInputHandler { public repeatPrecedingCharacter(params: number[]): void { let param = params[0] || 1; const line = this._terminal.buffer.lines.get(this._terminal.buffer.ybase + this._terminal.buffer.y); - const ch = line[this._terminal.buffer.x - 1] || [this._terminal.defAttr, ' ', 1]; + const ch = line[this._terminal.buffer.x - 1] || [this._terminal.defAttr, ' ', 1, 32]; while (param--) { line[this._terminal.buffer.x++] = ch; diff --git a/src/Interfaces.ts b/src/Interfaces.ts index ace4012e..9ce8ffd4 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -18,7 +18,11 @@ export interface IBrowser { isMSWindows: boolean; } -export interface ITerminal extends IEventEmitter { +export interface IBufferAccessor { + buffer: IBuffer; +} + +export interface ITerminal extends IBufferAccessor, IEventEmitter { element: HTMLElement; selectionManager: ISelectionManager; charMeasure: ICharMeasure; @@ -32,7 +36,6 @@ export interface ITerminal extends IEventEmitter { defAttr: number; options: ITerminalOptions; buffers: IBufferSet; - buffer: IBuffer; isFocused: boolean; /** diff --git a/src/Linkifier.test.ts b/src/Linkifier.test.ts index a4ed70db..6b28acb9 100644 --- a/src/Linkifier.test.ts +++ b/src/Linkifier.test.ts @@ -2,42 +2,84 @@ * @license MIT */ -import jsdom = require('jsdom'); import { assert } from 'chai'; -import { ITerminal, ILinkifier } from './Interfaces'; +import { ITerminal, ILinkifier, IBuffer, IBufferAccessor } from './Interfaces'; import { Linkifier } from './Linkifier'; -import { LinkMatcher } from './Types'; +import { LinkMatcher, LineData } from './Types'; +import { IMouseZoneManager, IMouseZone } from './input/Interfaces'; +import { MockBuffer } from './utils/TestUtils.test'; +import { CircularList } from './utils/CircularList'; class TestLinkifier extends Linkifier { - constructor() { + constructor(bufferAccessor: IBufferAccessor) { Linkifier.TIME_BEFORE_LINKIFY = 0; - super(); + super(bufferAccessor); } public get linkMatchers(): LinkMatcher[] { return this._linkMatchers; } } -describe('Linkifier', () => { - let dom: jsdom.JSDOM; - let window: Window; - let document: Document; +class TestMouseZoneManager implements IMouseZoneManager { + public clears: number = 0; + public zones: IMouseZone[] = []; + add(zone: IMouseZone): void { + this.zones.push(zone); + } + clearAll(): void { + this.clears++; + } +} - let container: HTMLElement; - let rows: HTMLElement[]; +describe('Linkifier', () => { + let bufferAccessor: IBufferAccessor; let linkifier: TestLinkifier; + let mouseZoneManager: TestMouseZoneManager; beforeEach(() => { - dom = new jsdom.JSDOM(''); - window = dom.window; - document = window.document; - linkifier = new TestLinkifier(); + bufferAccessor = { buffer: new MockBuffer() }; + bufferAccessor.buffer.lines = new CircularList(20); + bufferAccessor.buffer.ydisp = 0; + linkifier = new TestLinkifier(bufferAccessor); + mouseZoneManager = new TestMouseZoneManager(); }); - function addRow(html: string): void { - const element = document.createElement('div'); - element.innerHTML = html; - container.appendChild(element); - rows.push(element); + function stringToRow(text: string): LineData { + let result: LineData = []; + for (let i = 0; i < text.length; i++) { + result.push([0, text.charAt(i), 1, text.charCodeAt(i)]); + } + return result; + } + + function addRow(text: string): void { + bufferAccessor.buffer.lines.push(stringToRow(text)); + } + + function assertLinkifiesEntireRow(uri: string, done: MochaDone): void { + addRow(uri); + linkifier.linkifyRow(0); + setTimeout(() => { + assert.equal(mouseZoneManager.zones[0].x1, 1); + assert.equal(mouseZoneManager.zones[0].x2, uri.length + 1); + assert.equal(mouseZoneManager.zones[0].y, bufferAccessor.buffer.lines.length); + done(); + }, 0); + } + + function assertLinkifiesRow(rowText: string, linkMatcherRegex: RegExp, links: {x: number, length: number}[], done: MochaDone): void { + addRow(rowText); + linkifier.registerLinkMatcher(linkMatcherRegex, () => {}); + linkifier.linkifyRow(0); + // Allow linkify to happen + setTimeout(() => { + assert.equal(mouseZoneManager.zones.length, links.length); + links.forEach((l, i) => { + assert.equal(mouseZoneManager.zones[i].x1, l.x + 1); + assert.equal(mouseZoneManager.zones[i].x2, l.x + l.length + 1); + assert.equal(mouseZoneManager.zones[i].y, bufferAccessor.buffer.lines.length); + }); + done(); + }, 0); } describe('before attachToDom', () => { @@ -52,78 +94,42 @@ describe('Linkifier', () => { describe('after attachToDom', () => { beforeEach(() => { - rows = []; - linkifier.attachToDom(document, rows); - container = document.createElement('div'); - document.body.appendChild(container); + linkifier.attachToDom(mouseZoneManager); }); - function clickElement(element: Node): void { - const event = document.createEvent('MouseEvent'); - event.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); - element.dispatchEvent(event); - } - - function assertLinkifiesEntireRow(uri: string, done: MochaDone): void { - addRow(uri); - linkifier.linkifyRow(0); - setTimeout(() => { - assert.equal((rows[0].firstChild).tagName, 'A'); - assert.equal((rows[0].firstChild).textContent, uri); - done(); - }, 0); - } - describe('http links', () => { - function assertLinkifiesEntireRow(uri: string, done: MochaDone): void { - addRow(uri); - linkifier.linkifyRow(0); - setTimeout(() => { - assert.equal((rows[0].firstChild).tagName, 'A'); - assert.equal((rows[0].firstChild).textContent, uri); - done(); - }, 0); - } - it('should allow ~ character in URI path', done => assertLinkifiesEntireRow('http://foo.com/a~b#c~d?e~f', done)); + it('should allow ~ character in URI path', (done) => { + assertLinkifiesEntireRow('http://foo.com/a~b#c~d?e~f', done); + }); }); describe('link matcher', () => { - function assertLinkifiesRow(rowText: string, linkMatcherRegex: RegExp, expectedHtml: string, done: MochaDone): void { - addRow(rowText); - linkifier.registerLinkMatcher(linkMatcherRegex, () => {}); - linkifier.linkifyRow(0); - // Allow linkify to happen - setTimeout(() => { - assert.equal(rows[0].innerHTML, expectedHtml); - done(); - }, 0); - } it('should match a single link', done => { - assertLinkifiesRow('foo', /foo/, '
foo', done); + assertLinkifiesRow('foo', /foo/, [{x: 0, length: 3}], done); }); it('should match a single link at the start of a text node', done => { - assertLinkifiesRow('foo bar', /foo/, 'foo bar', done); + assertLinkifiesRow('foo bar', /foo/, [{x: 0, length: 3}], done); }); it('should match a single link in the middle of a text node', done => { - assertLinkifiesRow('foo bar baz', /bar/, 'foo bar baz', done); + assertLinkifiesRow('foo bar baz', /bar/, [{x: 4, length: 3}], done); }); it('should match a single link at the end of a text node', done => { - assertLinkifiesRow('foo bar', /bar/, 'foo bar', done); + assertLinkifiesRow('foo bar', /bar/, [{x: 4, length: 3}], done); }); it('should match a link after a link at the start of a text node', done => { - assertLinkifiesRow('foo bar', /foo|bar/, 'foo bar', done); + assertLinkifiesRow('foo bar', /foo|bar/, [{x: 0, length: 3}, {x: 4, length: 3}], done); }); it('should match a link after a link in the middle of a text node', done => { - assertLinkifiesRow('foo bar baz', /bar|baz/, 'foo bar baz', done); + assertLinkifiesRow('foo bar baz', /bar|baz/, [{x: 4, length: 3}, {x: 8, length: 3}], done); }); it('should match a link immediately after a link at the end of a text node', done => { - assertLinkifiesRow('foo barbaz', /bar|baz/, 'foo barbaz', done); + assertLinkifiesRow('foo barbaz', /bar|baz/, [{x: 4, length: 3}, {x: 7, length: 3}], done); }); it('should not duplicate text after a unicode character (wrapped in a span)', done => { // This is a regression test for an issue that came about when using // an oh-my-zsh theme that added the large blue diamond unicode // character (U+1F537) which caused the path to be duplicated. See #642. - assertLinkifiesRow('echo \'🔷foo\'', /foo/, 'echo \'🔷foo\'', done); + assertLinkifiesRow('echo \'🔷foo\'', /foo/, [{x: 8, length: 3}], done); }); }); @@ -131,10 +137,15 @@ describe('Linkifier', () => { it('should enable link if true', done => { addRow('test'); linkifier.registerLinkMatcher(/test/, () => done(), { - validationCallback: (url, element, cb) => { + validationCallback: (url, cb) => { + assert.equal(mouseZoneManager.zones.length, 0); cb(true); - assert.equal((rows[0].firstChild).tagName, 'A'); - setTimeout(() => clickElement(rows[0].firstChild), 0); + assert.equal(mouseZoneManager.zones.length, 1); + assert.equal(mouseZoneManager.zones[0].x1, 1); + assert.equal(mouseZoneManager.zones[0].x2, 5); + assert.equal(mouseZoneManager.zones[0].y, 1); + // Fires done() + mouseZoneManager.zones[0].clickCallback({}); } }); linkifier.linkifyRow(0); @@ -143,14 +154,14 @@ describe('Linkifier', () => { it('should disable link if false', done => { addRow('test'); linkifier.registerLinkMatcher(/test/, () => assert.fail(), { - validationCallback: (url, element, cb) => { + validationCallback: (url, cb) => { + assert.equal(mouseZoneManager.zones.length, 0); cb(false); - assert.equal((rows[0].firstChild).tagName, 'A'); - setTimeout(() => clickElement(rows[0].firstChild), 0); + assert.equal(mouseZoneManager.zones.length, 0); } }); linkifier.linkifyRow(0); - // Allow time for the click to be performed + // Allow time for the validation callback to be performed setTimeout(() => done(), 10); }); @@ -158,7 +169,7 @@ describe('Linkifier', () => { addRow('test test'); let count = 0; linkifier.registerLinkMatcher(/test/, () => assert.fail(), { - validationCallback: (url, element, cb) => { + validationCallback: (url, cb) => { count += 1; if (count === 2) { done(); diff --git a/src/Linkifier.ts b/src/Linkifier.ts index 20c5b713..cae0d662 100644 --- a/src/Linkifier.ts +++ b/src/Linkifier.ts @@ -2,7 +2,7 @@ * @license MIT */ -import { ILinkMatcherOptions, ITerminal } from './Interfaces'; +import { ILinkMatcherOptions, ITerminal, IBufferAccessor } from './Interfaces'; import { LinkMatcher, LinkMatcherHandler, LinkMatcherValidationCallback, LineData } from './Types'; import { IMouseZoneManager } from './input/Interfaces'; import { MouseZone } from './input/MouseZoneManager'; @@ -52,7 +52,7 @@ export class Linkifier { private _nextLinkMatcherId = HYPERTEXT_LINK_MATCHER_ID; constructor( - private _terminal: ITerminal + private _terminal: IBufferAccessor ) { this._rowTimeoutIds = []; this._linkMatchers = []; @@ -213,7 +213,7 @@ export class Linkifier { * @param {LinkMatcher} matcher The link matcher for this line. * @return The link element(s) that were added. */ - private _doLinkifyRow(rowIndex: number, text: string, matcher: LinkMatcher): void { + private _doLinkifyRow(rowIndex: number, text: string, matcher: LinkMatcher, offset: number = 0): void { // Iterate over nodes as we want to consider text nodes let result = []; const isHttpLinkMatcher = matcher.id === HYPERTEXT_LINK_MATCHER_ID; @@ -225,10 +225,6 @@ export class Linkifier { } let uri = match[typeof matcher.matchIndex !== 'number' ? 0 : matcher.matchIndex]; - // TODO: Match more than one link per row - // Set the next searches start index - // let rowStartIndex = match.index + uri.length; - // Get index, match.index is for the outer match which includes negated chars const index = text.indexOf(uri); @@ -237,11 +233,18 @@ export class Linkifier { matcher.validationCallback(text, isValid => { if (isValid) { // TODO: Discard link if the line has already changed? - this._addLink(index, rowIndex, uri, matcher); + this._addLink(offset + index, rowIndex, uri, matcher); } }); } else { - this._addLink(index, rowIndex, uri, matcher); + this._addLink(offset + index, rowIndex, uri, matcher); + } + + // Recursively check for links in the rest of the text + const remainingStartIndex = index + uri.length; + const remainingText = text.substr(remainingStartIndex); + if (remainingText.length > 0) { + this._doLinkifyRow(rowIndex, remainingText, matcher, offset + remainingStartIndex); } } diff --git a/src/SelectionManager.test.ts b/src/SelectionManager.test.ts index b5a9d552..8c90693e 100644 --- a/src/SelectionManager.test.ts +++ b/src/SelectionManager.test.ts @@ -60,7 +60,7 @@ describe('SelectionManager', () => { function stringToRow(text: string): LineData { let result: LineData = []; for (let i = 0; i < text.length; i++) { - result.push([0, text.charAt(i), 1]); + result.push([0, text.charAt(i), 1, text.charCodeAt(i)]); } return result; } @@ -99,21 +99,21 @@ describe('SelectionManager', () => { it('should expand selection for wide characters', () => { // Wide characters use a special format buffer.lines.set(0, [ - [null, '中', 2], - [null, '', 0], - [null, '文', 2], - [null, '', 0], - [null, ' ', 1], - [null, 'a', 1], - [null, '中', 2], - [null, '', 0], - [null, '文', 2], - [null, '', 0], - [null, 'b', 1], - [null, ' ', 1], - [null, 'f', 1], - [null, 'o', 1], - [null, 'o', 1] + [null, '中', 2, '中'.charCodeAt(0)], + [null, '', 0, null], + [null, '文', 2, '文'.charCodeAt(0)], + [null, '', 0, null], + [null, ' ', 1, ' '.charCodeAt(0)], + [null, 'a', 1, 'a'.charCodeAt(0)], + [null, '中', 2, '中'.charCodeAt(0)], + [null, '', 0, null], + [null, '文', 2, '文'.charCodeAt(0)], + [null, '', 0, ''.charCodeAt(0)], + [null, 'b', 1, 'b'.charCodeAt(0)], + [null, ' ', 1, ' '.charCodeAt(0)], + [null, 'f', 1, 'f'.charCodeAt(0)], + [null, 'o', 1, 'o'.charCodeAt(0)], + [null, 'o', 1, 'o'.charCodeAt(0)] ]); // Ensure wide characters take up 2 columns selectionManager.selectWordAt([0, 0]); diff --git a/src/Types.ts b/src/Types.ts index 378a66ac..1ced3d73 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -17,6 +17,5 @@ export type LinkMatcherValidationCallback = (uri: string, callback: (isValid: bo export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean; export type Charset = {[key: string]: string}; -// TODO: Add code here? -export type CharData = [number, string, number]; +export type CharData = [number, string, number, number]; export type LineData = CharData[]; diff --git a/src/utils/TestUtils.test.ts b/src/utils/TestUtils.test.ts index d4dc16ab..0388e9da 100644 --- a/src/utils/TestUtils.test.ts +++ b/src/utils/TestUtils.test.ts @@ -4,6 +4,7 @@ import { ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager, ITerminalOptions, IListenerType, IInputHandlingTerminal, IViewport, ICircularList, ICompositionHelper } from '../Interfaces'; import { LineData } from '../Types'; +import { Buffer } from '../Buffer'; import * as Browser from './Browser'; import { IColorSet } from '../renderer/Interfaces'; @@ -61,7 +62,7 @@ export class MockTerminal implements ITerminal { const line: LineData = []; cols = cols || this.cols; for (let i = 0; i < cols; i++) { - line.push([0, ' ', 1]); + line.push([0, ' ', 1, 32]); } return line; } @@ -130,7 +131,7 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal { eraseLeft(x: number, y: number): void { throw new Error('Method not implemented.'); } - blankLine(cur?: boolean, isWrapped?: boolean): [number, string, number][] { + blankLine(cur?: boolean, isWrapped?: boolean): [number, string, number, number][] { throw new Error('Method not implemented.'); } prevStop(x?: number): number { @@ -182,7 +183,7 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal { export class MockBuffer implements IBuffer { isCursorInViewport: boolean; - lines: ICircularList<[number, string, number][]>; + lines: ICircularList<[number, string, number, number][]>; ydisp: number; ybase: number; y: number; @@ -193,7 +194,7 @@ export class MockBuffer implements IBuffer { savedY: number; savedX: number; translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol?: number, endCol?: number): string { - throw new Error('Method not implemented.'); + return Buffer.prototype.translateBufferLineToString.apply(this, arguments); } nextStop(x?: number): number { throw new Error('Method not implemented.'); diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index b1cea816..d6127978 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -133,6 +133,11 @@ interface ILinkMatcherOptions { */ validationCallback?: (uri: string, element: HTMLElement, callback: (isValid: boolean) => void) => void; + /** + * A callback that fired when the mouse hovers over a link. + */ + hoverCallback?: LinkMatcherHandler; + /** * 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 From e518ea04c59c3122ff99dfee427009bba511229b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 3 Sep 2017 14:21:30 -0700 Subject: [PATCH 186/378] Get tests reporting failures again --- src/Terminal.ts | 3 +++ src/renderer/CharAtlas.ts | 16 +++++++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index c3d360bb..62034017 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -43,6 +43,7 @@ import { BellSound } from './utils/Sounds'; import { DEFAULT_ANSI_COLORS } from './renderer/ColorManager'; import { IMouseZoneManager } from './input/Interfaces'; import { MouseZoneManager } from './input/MouseZoneManager'; +import { initialize as initializeCharAtlas } from './renderer/CharAtlas'; // Declare for RequireJS in loadAddon declare var define: any; @@ -562,6 +563,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.document = this.parent.ownerDocument; this.body = this.document.body; + initializeCharAtlas(this.document); + // Create main element container this.element = this.document.createElement('div'); this.element.classList.add('terminal'); diff --git a/src/renderer/CharAtlas.ts b/src/renderer/CharAtlas.ts index a92037a8..4fcedd1a 100644 --- a/src/renderer/CharAtlas.ts +++ b/src/renderer/CharAtlas.ts @@ -91,12 +91,20 @@ function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean { a.colors.foreground === b.colors.foreground; } +let generator: CharAtlasGenerator; + +export function initialize(document: Document): void { + if (!generator) { + generator = new CharAtlasGenerator(document); + } +} + class CharAtlasGenerator { private _canvas: HTMLCanvasElement; private _ctx: CanvasRenderingContext2D; - constructor() { - this._canvas = document.createElement('canvas'); + constructor(private _document: Document) { + this._canvas = this._document.createElement('canvas'); this._ctx = this._canvas.getContext('2d'); this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); } @@ -147,7 +155,7 @@ class CharAtlasGenerator { if (!('createImageBitmap' in window)) { // Regenerate canvas and context as they are now owned by the char atlas const result = this._canvas; - this._canvas = document.createElement('canvas'); + this._canvas = this._document.createElement('canvas'); this._ctx = this._canvas.getContext('2d'); this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); return result; @@ -159,5 +167,3 @@ class CharAtlasGenerator { return promise; } } - -const generator = new CharAtlasGenerator(); From 36778186e1bfafa302523ee8c874865c00169bd4 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 3 Sep 2017 14:34:50 -0700 Subject: [PATCH 187/378] Fix tests --- fixtures/typings-test/typings-test.ts | 4 ++-- src/CompositionHelper.test.ts | 10 ++++++++++ src/Terminal.test.ts | 3 ++- src/Terminal.ts | 3 ++- src/Viewport.test.ts | 3 ++- src/renderer/Interfaces.ts | 15 ++++++++++++++- src/renderer/Renderer.ts | 4 ++-- src/utils/TestUtils.test.ts | 17 +++++++++++++++-- typings/xterm.d.ts | 6 +++--- 9 files changed, 52 insertions(+), 13 deletions(-) diff --git a/fixtures/typings-test/typings-test.ts b/fixtures/typings-test/typings-test.ts index ffe6afd4..90958f83 100644 --- a/fixtures/typings-test/typings-test.ts +++ b/fixtures/typings-test/typings-test.ts @@ -210,8 +210,8 @@ namespace methods_experimental { t.registerLinkMatcher(/foo/, () => true, { matchIndex: 1, priority: 1, - validationCallback: (uri: string, element: HTMLElement, callback: (isValid: boolean) => void) => { - console.log(uri, element, callback); + validationCallback: (uri: string, callback: (isValid: boolean) => void) => { + console.log(uri, callback); } }); t.deregisterLinkMatcher(1); diff --git a/src/CompositionHelper.test.ts b/src/CompositionHelper.test.ts index 0adb1719..7d78a259 100644 --- a/src/CompositionHelper.test.ts +++ b/src/CompositionHelper.test.ts @@ -42,6 +42,16 @@ describe('CompositionHelper', () => { }, handler: (text: string) => { handledText += text; + }, + buffer: { + isCursorInViewport: true + }, + charMeasure: { + height: 10, + width: 10 + }, + options: { + lineHeight: 1 } }; handledText = ''; diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index 0c333741..7a44ee81 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -1,6 +1,6 @@ import { assert, expect } from 'chai'; import { Terminal } from './Terminal'; -import { MockViewport, MockCompositionHelper } from './utils/TestUtils.test'; +import { MockViewport, MockCompositionHelper, MockRenderer } from './utils/TestUtils.test'; import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from './Buffer'; const INIT_COLS = 80; @@ -21,6 +21,7 @@ describe('term.js addons', () => { rows: INIT_ROWS }); term.refresh = () => {}; + (term).renderer = new MockRenderer(); term.viewport = new MockViewport(); (term).compositionHelper = new MockCompositionHelper(); // Force synchronous writes diff --git a/src/Terminal.ts b/src/Terminal.ts index 62034017..cab25b03 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -44,6 +44,7 @@ import { DEFAULT_ANSI_COLORS } from './renderer/ColorManager'; import { IMouseZoneManager } from './input/Interfaces'; import { MouseZoneManager } from './input/MouseZoneManager'; import { initialize as initializeCharAtlas } from './renderer/CharAtlas'; +import { IRenderer } from './renderer/Interfaces'; // Declare for RequireJS in loadAddon declare var define: any; @@ -184,7 +185,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT private inputHandler: InputHandler; private parser: Parser; - private renderer: Renderer; + private renderer: IRenderer; public selectionManager: SelectionManager; private linkifier: Linkifier; public buffers: BufferSet; diff --git a/src/Viewport.test.ts b/src/Viewport.test.ts index 8a960fc7..6d23e19a 100644 --- a/src/Viewport.test.ts +++ b/src/Viewport.test.ts @@ -31,7 +31,8 @@ describe('Viewport', () => { } }, options: { - scrollback: 10 + scrollback: 10, + lineHeight: 1 } }; terminal.buffers = new BufferSet(terminal); diff --git a/src/renderer/Interfaces.ts b/src/renderer/Interfaces.ts index fd94d59d..f7645abf 100644 --- a/src/renderer/Interfaces.ts +++ b/src/renderer/Interfaces.ts @@ -1,4 +1,17 @@ -import { ITerminal, ITerminalOptions } from '../Interfaces'; +import { ITerminal, ITerminalOptions, ITheme } from '../Interfaces'; + +export interface IRenderer { + setTheme(theme: ITheme): IColorSet; + onResize(cols: number, rows: number): void; + onCharSizeChanged(charWidth: number, charHeight: number): void; + onBlur(): void; + onFocus(): void; + onSelectionChanged(start: [number, number], end: [number, number]): void; + onCursorMove(): void; + onOptionsChanged(): void; + clear(): void; + queueRefresh(start: number, end: number): void; +} export interface IRenderLayer { /** diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index bc0717dc..6b12188e 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -11,9 +11,9 @@ import { SelectionRenderLayer } from './SelectionRenderLayer'; import { CursorRenderLayer } from './CursorRenderLayer'; import { ColorManager } from './ColorManager'; import { BaseRenderLayer } from './BaseRenderLayer'; -import { IRenderLayer, IColorSet } from './Interfaces'; +import { IRenderLayer, IColorSet, IRenderer } from './Interfaces'; -export class Renderer { +export class Renderer implements IRenderer { /** A queue of the rows to be refreshed */ private _refreshRowsQueue: {start: number, end: number}[] = []; private _refreshAnimationFrame = null; diff --git a/src/utils/TestUtils.test.ts b/src/utils/TestUtils.test.ts index 0388e9da..51f191a8 100644 --- a/src/utils/TestUtils.test.ts +++ b/src/utils/TestUtils.test.ts @@ -2,11 +2,11 @@ * @license MIT */ -import { ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager, ITerminalOptions, IListenerType, IInputHandlingTerminal, IViewport, ICircularList, ICompositionHelper } from '../Interfaces'; +import { ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager, ITerminalOptions, IListenerType, IInputHandlingTerminal, IViewport, ICircularList, ICompositionHelper, ITheme } from '../Interfaces'; import { LineData } from '../Types'; import { Buffer } from '../Buffer'; import * as Browser from './Browser'; -import { IColorSet } from '../renderer/Interfaces'; +import { IColorSet, IRenderer } from '../renderer/Interfaces'; export class MockTerminal implements ITerminal { isFocused: boolean; @@ -204,6 +204,19 @@ export class MockBuffer implements IBuffer { } } +export class MockRenderer implements IRenderer { + setTheme(theme: ITheme): IColorSet { return {}; } + onResize(cols: number, rows: number): void {} + onCharSizeChanged(charWidth: number, charHeight: number): void {} + onBlur(): void {} + onFocus(): void {} + onSelectionChanged(start: [number, number], end: [number, number]): void {} + onCursorMove(): void {} + onOptionsChanged(): void {} + clear(): void {} + queueRefresh(start: number, end: number): void {} +} + export class MockViewport implements IViewport { onThemeChanged(colors: IColorSet): void { throw new Error('Method not implemented.'); diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index d6127978..866021e8 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -131,12 +131,12 @@ interface ILinkMatcherOptions { * A callback that validates an individual link, returning true if valid and * false if invalid. */ - validationCallback?: (uri: string, element: HTMLElement, callback: (isValid: boolean) => void) => void; + validationCallback?: (uri: string, callback: (isValid: boolean) => void) => void; /** * A callback that fired when the mouse hovers over a link. */ - hoverCallback?: LinkMatcherHandler; + hoverCallback?: (event: MouseEvent, uri: string) => boolean | void; /** * The priority of the link matcher, this defines the order in which the link @@ -290,7 +290,7 @@ declare module 'xterm' { * @param options Options for the link matcher. * @return The ID of the new matcher, this can be used to deregister. */ - registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => boolean | void , options?: ILinkMatcherOptions): number; + registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => boolean | void, options?: ILinkMatcherOptions): number; /** * (EXPERIMENTAL) Deregisters a link matcher if it has been registered. From e123dd1849b63fd15cdead4d7ec89774180e799e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 3 Sep 2017 21:15:14 -0700 Subject: [PATCH 188/378] Null check if clicking on scroll bar --- src/SelectionManager.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index e84bde68..4fe09c8c 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -409,6 +409,11 @@ export class SelectionManager extends EventEmitter implements ISelectionManager return; } + // Return early if the click event is not in the buffer (eg. in scroll bar) + if (line.length >= this._model.selectionStart[0]) { + return; + } + // If the mouse is over the second half of a wide character, adjust the // selection to cover the whole character const char = line[this._model.selectionStart[0]]; From 7934e050836953d967ae2ccad64e5a395178228d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 3 Sep 2017 21:34:48 -0700 Subject: [PATCH 189/378] jsdoc Linkifier --- src/Linkifier.test.ts | 15 +++++---- src/Linkifier.ts | 78 ++++++++++++++++++++----------------------- 2 files changed, 45 insertions(+), 48 deletions(-) diff --git a/src/Linkifier.test.ts b/src/Linkifier.test.ts index 6b28acb9..f165590f 100644 --- a/src/Linkifier.test.ts +++ b/src/Linkifier.test.ts @@ -11,12 +11,13 @@ import { MockBuffer } from './utils/TestUtils.test'; import { CircularList } from './utils/CircularList'; class TestLinkifier extends Linkifier { - constructor(bufferAccessor: IBufferAccessor) { + constructor(private _bufferAccessor: IBufferAccessor) { + super(_bufferAccessor); Linkifier.TIME_BEFORE_LINKIFY = 0; - super(bufferAccessor); } public get linkMatchers(): LinkMatcher[] { return this._linkMatchers; } + public linkifyRows(): void { super.linkifyRows(0, this._bufferAccessor.buffer.lines.length - 1); } } class TestMouseZoneManager implements IMouseZoneManager { @@ -57,7 +58,7 @@ describe('Linkifier', () => { function assertLinkifiesEntireRow(uri: string, done: MochaDone): void { addRow(uri); - linkifier.linkifyRow(0); + linkifier.linkifyRows(); setTimeout(() => { assert.equal(mouseZoneManager.zones[0].x1, 1); assert.equal(mouseZoneManager.zones[0].x2, uri.length + 1); @@ -69,7 +70,7 @@ describe('Linkifier', () => { function assertLinkifiesRow(rowText: string, linkMatcherRegex: RegExp, links: {x: number, length: number}[], done: MochaDone): void { addRow(rowText); linkifier.registerLinkMatcher(linkMatcherRegex, () => {}); - linkifier.linkifyRow(0); + linkifier.linkifyRows(); // Allow linkify to happen setTimeout(() => { assert.equal(mouseZoneManager.zones.length, links.length); @@ -148,7 +149,7 @@ describe('Linkifier', () => { mouseZoneManager.zones[0].clickCallback({}); } }); - linkifier.linkifyRow(0); + linkifier.linkifyRows(); }); it('should disable link if false', done => { @@ -160,7 +161,7 @@ describe('Linkifier', () => { assert.equal(mouseZoneManager.zones.length, 0); } }); - linkifier.linkifyRow(0); + linkifier.linkifyRows(); // Allow time for the validation callback to be performed setTimeout(() => done(), 10); }); @@ -177,7 +178,7 @@ describe('Linkifier', () => { cb(false); } }); - linkifier.linkifyRow(0); + linkifier.linkifyRows(); }); }); diff --git a/src/Linkifier.ts b/src/Linkifier.ts index cae0d662..d3150b4f 100644 --- a/src/Linkifier.ts +++ b/src/Linkifier.ts @@ -44,30 +44,31 @@ export class Linkifier { */ protected static TIME_BEFORE_LINKIFY = 200; - protected _linkMatchers: LinkMatcher[]; + protected _linkMatchers: LinkMatcher[] = []; private _mouseZoneManager: IMouseZoneManager; - private _rowTimeoutIds: number[]; private _rowsTimeoutId: number; private _nextLinkMatcherId = HYPERTEXT_LINK_MATCHER_ID; constructor( private _terminal: IBufferAccessor ) { - this._rowTimeoutIds = []; - this._linkMatchers = []; this.registerLinkMatcher(strictUrlRegex, null, { matchIndex: 1 }); } /** * Attaches the linkifier to the DOM, enabling linkification. - * @param document The document object. - * @param rows The array of rows to apply links to. + * @param mouseZoneManager The mouse zone manager to register link zones with. */ public attachToDom(mouseZoneManager: IMouseZoneManager): void { this._mouseZoneManager = mouseZoneManager; } + /** + * Queue linkification on a set of rows. + * @param start The row to linkify from (inclusive). + * @param end The row to linkify to (inclusive). + */ public linkifyRows(start: number, end: number): void { // Don't attempt linkify if not yet attached to DOM if (!this._mouseZoneManager) { @@ -84,6 +85,11 @@ export class Linkifier { this._rowsTimeoutId = setTimeout(this._linkifyRows.bind(this, start, end), Linkifier.TIME_BEFORE_LINKIFY); } + /** + * Linkifies + * @param start The row to start at. + * @param end The row to end at. + */ private _linkifyRows(start: number, end: number): void { for (let i = start; i <= end; i++) { this._linkifyRow(i); @@ -91,27 +97,9 @@ export class Linkifier { } /** - * Queues a row for linkification. - * @param {number} rowIndex The index of the row to linkify. - */ - public linkifyRow(rowIndex: number): void { - // Don't attempt linkify if not yet attached to DOM - if (!this._mouseZoneManager) { - return; - } - - const timeoutId = this._rowTimeoutIds[rowIndex]; - if (timeoutId) { - clearTimeout(timeoutId); - } - this._rowTimeoutIds[rowIndex] = setTimeout(this._linkifyRow.bind(this, rowIndex), Linkifier.TIME_BEFORE_LINKIFY); - } - - /** - * Attaches a handler for hypertext links, overriding default behavior - * for standard http(s) links. - * @param {LinkHandler} handler The handler to use, this can be cleared with - * null. + * Attaches a handler for hypertext links, overriding default behavior for + * tandard http(s) links. + * @param handler The handler to use, this can be cleared with null. */ public setHypertextLinkHandler(handler: LinkMatcherHandler): void { this._linkMatchers[HYPERTEXT_LINK_MATCHER_ID].handler = handler; @@ -119,8 +107,7 @@ export class Linkifier { /** * Attaches a validation callback for hypertext links. - * @param {LinkMatcherValidationCallback} callback The callback to use, this - * can be cleared with null. + * @param callback The callback to use, this can be cleared with null. */ public setHypertextValidationCallback(callback: LinkMatcherValidationCallback): void { this._linkMatchers[HYPERTEXT_LINK_MATCHER_ID].validationCallback = callback; @@ -129,12 +116,12 @@ export class Linkifier { /** * Registers a link matcher, allowing custom link patterns to be matched and * handled. - * @param {RegExp} regex The regular expression to search for, specifically - * this searches the textContent of the rows. You will want to use \s to match - * a space ' ' character for example. - * @param {LinkHandler} handler The callback when the link is called. - * @param {ILinkMatcherOptions} [options] Options for the link matcher. - * @return {number} The ID of the new matcher, this can be used to deregister. + * @param regex The regular expression to search for. Specifically, this + * searches the textContent of the rows. You will want to use \s to match a + * space ' ' character for example. + * @param handler The callback when the link is called. + * @param options Options for the link matcher. + * @return The ID of the new matcher, this can be used to deregister. */ public registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options: ILinkMatcherOptions = {}): number { if (this._nextLinkMatcherId !== HYPERTEXT_LINK_MATCHER_ID && !handler) { @@ -177,8 +164,8 @@ export class Linkifier { /** * Deregisters a link matcher if it has been registered. - * @param {number} matcherId The link matcher's ID (returned after register) - * @return {boolean} Whether a link matcher was found and deregistered. + * @param matcherId The link matcher's ID (returned after register) + * @return Whether a link matcher was found and deregistered. */ public deregisterLinkMatcher(matcherId: number): boolean { // ID 0 is the hypertext link matcher which cannot be deregistered @@ -193,7 +180,7 @@ export class Linkifier { /** * Linkifies a row. - * @param {number} rowIndex The index of the row to linkify. + * @param rowIndex The index of the row to linkify. */ private _linkifyRow(rowIndex: number): void { const absoluteRowIndex = this._terminal.buffer.ydisp + rowIndex; @@ -209,8 +196,10 @@ export class Linkifier { /** * Linkifies a row given a specific handler. * @param rowIndex The row index to linkify. - * @param text The text of the row. - * @param {LinkMatcher} matcher The link matcher for this line. + * @param text The text of the row (excludes text in the row that's already + * linkified). + * @param matcher The link matcher for this line. + * @param offset The how much of the row has already been linkified. * @return The link element(s) that were added. */ private _doLinkifyRow(rowIndex: number, text: string, matcher: LinkMatcher, offset: number = 0): void { @@ -248,6 +237,13 @@ export class Linkifier { } } + /** + * Registers a link to the mouse zone manager. + * @param x The column the link starts. + * @param y The row the link is on. + * @param uri The URI of the link. + * @param matcher The link matcher for the link. + */ private _addLink(x: number, y: number, uri: string, matcher: LinkMatcher): void { this._mouseZoneManager.add(new MouseZone( x + 1, @@ -257,7 +253,7 @@ export class Linkifier { if (matcher.handler) { return matcher.handler(e, uri); } - window.open(uri, '_blink'); + window.open(uri, '_blank'); }, e => { if (matcher.hoverCallback) { From e77f1a928759bfe880c124908b6d81d074ff18a6 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 3 Sep 2017 21:52:47 -0700 Subject: [PATCH 190/378] Use default colors if not defined in a theme Previous behavior didn't touch them, meaning partial themes could be inconsistent --- src/renderer/ColorManager.ts | 55 ++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/src/renderer/ColorManager.ts b/src/renderer/ColorManager.ts index 6b7f88c1..2b16b1c3 100644 --- a/src/renderer/ColorManager.ts +++ b/src/renderer/ColorManager.ts @@ -1,9 +1,6 @@ import { IColorSet } from './Interfaces'; import { ITheme } from '../Interfaces'; -// TODO: Ideally colors would be exposed through some theme manager since colors -// are moving to JS. - export enum COLOR_CODES { BLACK = 0, RED = 1, @@ -23,6 +20,9 @@ export enum COLOR_CODES { BRIGHT_WHITE = 15 } +const DEFAULT_FOREGROUND = '#ffffff'; +const DEFAULT_BACKGROUND = '#000000'; +const DEFAULT_CURSOR = '#ffffff'; export const DEFAULT_ANSI_COLORS = [ // dark: '#2e3436', @@ -75,32 +75,37 @@ export class ColorManager { constructor() { this.colors = { - foreground: '#ffffff', - background: '#000000', - cursor: '#ffffff', + foreground: DEFAULT_FOREGROUND, + background: DEFAULT_BACKGROUND, + cursor: DEFAULT_CURSOR, ansi: generate256Colors(DEFAULT_ANSI_COLORS) }; } + /** + * Sets the terminal's theme. + * @param theme The theme to use. If a partial theme is provided then default + * colors will be used where colors are not defined. + */ public setTheme(theme: ITheme): void { - if (theme.foreground) this.colors.foreground = theme.foreground; - if (theme.background) this.colors.background = theme.background; - if (theme.cursor) this.colors.cursor = theme.cursor; - if (theme.black) this.colors.ansi[0] = theme.black; - if (theme.red) this.colors.ansi[1] = theme.red; - if (theme.green) this.colors.ansi[2] = theme.green; - if (theme.yellow) this.colors.ansi[3] = theme.yellow; - if (theme.blue) this.colors.ansi[4] = theme.blue; - if (theme.magenta) this.colors.ansi[5] = theme.magenta; - if (theme.cyan) this.colors.ansi[6] = theme.cyan; - if (theme.white) this.colors.ansi[7] = theme.white; - if (theme.brightBlack) this.colors.ansi[8] = theme.brightBlack; - if (theme.brightRed) this.colors.ansi[9] = theme.brightRed; - if (theme.brightGreen) this.colors.ansi[10] = theme.brightGreen; - if (theme.brightYellow) this.colors.ansi[11] = theme.brightYellow; - if (theme.brightBlue) this.colors.ansi[12] = theme.brightBlue; - if (theme.brightMagenta) this.colors.ansi[13] = theme.brightMagenta; - if (theme.brightCyan) this.colors.ansi[14] = theme.brightCyan; - if (theme.brightWhite) this.colors.ansi[15] = theme.brightWhite; + this.colors.foreground = theme.foreground || DEFAULT_FOREGROUND; + this.colors.background = theme.background || DEFAULT_BACKGROUND; + this.colors.cursor = theme.cursor || DEFAULT_CURSOR; + this.colors.ansi[0] = theme.black || DEFAULT_ANSI_COLORS[0]; + this.colors.ansi[1] = theme.red || DEFAULT_ANSI_COLORS[1]; + this.colors.ansi[2] = theme.green || DEFAULT_ANSI_COLORS[2]; + this.colors.ansi[3] = theme.yellow || DEFAULT_ANSI_COLORS[3]; + this.colors.ansi[4] = theme.blue || DEFAULT_ANSI_COLORS[4]; + this.colors.ansi[5] = theme.magenta || DEFAULT_ANSI_COLORS[5]; + this.colors.ansi[6] = theme.cyan || DEFAULT_ANSI_COLORS[6]; + this.colors.ansi[7] = theme.white || DEFAULT_ANSI_COLORS[7]; + this.colors.ansi[8] = theme.brightBlack || DEFAULT_ANSI_COLORS[8]; + this.colors.ansi[9] = theme.brightRed || DEFAULT_ANSI_COLORS[9]; + this.colors.ansi[10] = theme.brightGreen || DEFAULT_ANSI_COLORS[10]; + this.colors.ansi[11] = theme.brightYellow || DEFAULT_ANSI_COLORS[11]; + this.colors.ansi[12] = theme.brightBlue || DEFAULT_ANSI_COLORS[12]; + this.colors.ansi[13] = theme.brightMagenta || DEFAULT_ANSI_COLORS[13]; + this.colors.ansi[14] = theme.brightCyan || DEFAULT_ANSI_COLORS[14]; + this.colors.ansi[15] = theme.brightWhite || DEFAULT_ANSI_COLORS[15]; } } From 29a7ee30f33f8712d9320b6465c5a678c1680dad Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 3 Sep 2017 22:39:47 -0700 Subject: [PATCH 191/378] Fix lint --- src/renderer/GridCache.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/GridCache.ts b/src/renderer/GridCache.ts index 56294ddc..3b3ae8df 100644 --- a/src/renderer/GridCache.ts +++ b/src/renderer/GridCache.ts @@ -5,7 +5,7 @@ export class GridCache { this.cache = []; } - public resize(width: number, height: number) { + public resize(width: number, height: number): void { for (let x = 0; x < width; x++) { if (this.cache.length <= x) { this.cache.push([]); From 0bfdff0b39010b8a49a2396aa1e389de132c3d35 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 4 Sep 2017 08:09:43 -0700 Subject: [PATCH 192/378] Theme unfocused cursor using cursor color --- src/renderer/ColorManager.ts | 19 ------------------- src/renderer/CursorRenderLayer.ts | 4 +--- 2 files changed, 1 insertion(+), 22 deletions(-) diff --git a/src/renderer/ColorManager.ts b/src/renderer/ColorManager.ts index 2b16b1c3..581f09a9 100644 --- a/src/renderer/ColorManager.ts +++ b/src/renderer/ColorManager.ts @@ -1,25 +1,6 @@ import { IColorSet } from './Interfaces'; import { ITheme } from '../Interfaces'; -export enum COLOR_CODES { - BLACK = 0, - RED = 1, - GREEN = 2, - YELLOW = 3, - BLUE = 4, - MAGENTA = 5, - CYAN = 6, - WHITE = 7, - BRIGHT_BLACK = 8, - BRIGHT_RED = 9, - BRIGHT_GREEN = 10, - BRIGHT_YELLOW = 11, - BRIGHT_BLUE = 12, - BRIGHT_MAGENTA = 13, - BRIGHT_CYAN = 14, - BRIGHT_WHITE = 15 -} - const DEFAULT_FOREGROUND = '#ffffff'; const DEFAULT_BACKGROUND = '#000000'; const DEFAULT_CURSOR = '#ffffff'; diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index 482d4e14..00abfb93 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -5,7 +5,6 @@ import { GridCache } from './GridCache'; import { FLAGS } from './Types'; import { BaseRenderLayer } from './BaseRenderLayer'; import { CharData } from '../Types'; -import { COLOR_CODES } from './ColorManager'; interface CursorState { x: number; @@ -131,7 +130,7 @@ export class CursorRenderLayer extends BaseRenderLayer { if (!terminal.isFocused) { this._clearCursor(); this._ctx.save(); - this._ctx.fillStyle = this.colors.ansi[COLOR_CODES.WHITE]; + this._ctx.fillStyle = this.colors.cursor; this._renderBlurCursor(terminal, terminal.buffer.x, viewportRelativeCursorY, charData); this._ctx.restore(); this._state.x = terminal.buffer.x; @@ -161,7 +160,6 @@ export class CursorRenderLayer extends BaseRenderLayer { } this._ctx.save(); - this._ctx.fillStyle = this.colors.ansi[COLOR_CODES.WHITE]; this._cursorRenderers[terminal.options.cursorStyle || 'block'](terminal, terminal.buffer.x, viewportRelativeCursorY, charData); this._ctx.restore(); From ff31f345516bd6b6a7b09e3251ba0c7c1047331f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 4 Sep 2017 08:14:40 -0700 Subject: [PATCH 193/378] Add selection to ITheme --- src/Interfaces.ts | 1 + src/renderer/CharAtlas.ts | 1 + src/renderer/ColorManager.ts | 3 +++ src/renderer/Interfaces.ts | 1 + src/renderer/SelectionRenderLayer.ts | 2 +- typings/xterm.d.ts | 2 ++ 6 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 9ce8ffd4..da6d2727 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -307,6 +307,7 @@ export interface ITheme { foreground?: string; background?: string; cursor?: string; + selection?: string; black?: string; red?: string; green?: string; diff --git a/src/renderer/CharAtlas.ts b/src/renderer/CharAtlas.ts index 4fcedd1a..05eb9a84 100644 --- a/src/renderer/CharAtlas.ts +++ b/src/renderer/CharAtlas.ts @@ -67,6 +67,7 @@ function generateConfig(scaledCharWidth: number, scaledCharHeight: number, termi foreground: colors.foreground, background: null, cursor: null, + selection: null, ansi: colors.ansi.slice(0, 16) }; return { diff --git a/src/renderer/ColorManager.ts b/src/renderer/ColorManager.ts index 581f09a9..17aee392 100644 --- a/src/renderer/ColorManager.ts +++ b/src/renderer/ColorManager.ts @@ -4,6 +4,7 @@ import { ITheme } from '../Interfaces'; const DEFAULT_FOREGROUND = '#ffffff'; const DEFAULT_BACKGROUND = '#000000'; const DEFAULT_CURSOR = '#ffffff'; +const DEFAULT_SELECTION = 'rgba(255, 255, 255, 0.9)'; export const DEFAULT_ANSI_COLORS = [ // dark: '#2e3436', @@ -59,6 +60,7 @@ export class ColorManager { foreground: DEFAULT_FOREGROUND, background: DEFAULT_BACKGROUND, cursor: DEFAULT_CURSOR, + selection: DEFAULT_SELECTION, ansi: generate256Colors(DEFAULT_ANSI_COLORS) }; } @@ -72,6 +74,7 @@ export class ColorManager { this.colors.foreground = theme.foreground || DEFAULT_FOREGROUND; this.colors.background = theme.background || DEFAULT_BACKGROUND; this.colors.cursor = theme.cursor || DEFAULT_CURSOR; + this.colors.selection = theme.selection || DEFAULT_SELECTION; this.colors.ansi[0] = theme.black || DEFAULT_ANSI_COLORS[0]; this.colors.ansi[1] = theme.red || DEFAULT_ANSI_COLORS[1]; this.colors.ansi[2] = theme.green || DEFAULT_ANSI_COLORS[2]; diff --git a/src/renderer/Interfaces.ts b/src/renderer/Interfaces.ts index f7645abf..04e24e19 100644 --- a/src/renderer/Interfaces.ts +++ b/src/renderer/Interfaces.ts @@ -66,5 +66,6 @@ export interface IColorSet { foreground: string; background: string; cursor: string; + selection: string; ansi: string[]; } diff --git a/src/renderer/SelectionRenderLayer.ts b/src/renderer/SelectionRenderLayer.ts index 7f131da8..91e62a0c 100644 --- a/src/renderer/SelectionRenderLayer.ts +++ b/src/renderer/SelectionRenderLayer.ts @@ -63,7 +63,7 @@ export class SelectionRenderLayer extends BaseRenderLayer { // Draw first row const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0; const startRowEndCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : terminal.cols; - this._ctx.fillStyle = 'rgba(255,255,255,0.3)'; + this._ctx.fillStyle = this.colors.selection; this.fillCells(startCol, viewportCappedStartRow, startRowEndCol - startCol, 1); // Draw middle rows diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 866021e8..eff26c9b 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -83,6 +83,8 @@ interface ITheme { background?: string, /** The cursor color */ cursor?: string, + /** The selection color (can be transparent) */ + selection?: string, /** ANSI black (eg. `\x1b[30m`) */ black?: string, /** ANSI red (eg. `\x1b[31m`) */ From a17950290c9951dde0af2862e4eb530c639b0097 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 4 Sep 2017 09:04:50 -0700 Subject: [PATCH 194/378] Support emoji --- src/renderer/ForegroundRenderLayer.ts | 58 ++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 10 deletions(-) diff --git a/src/renderer/ForegroundRenderLayer.ts b/src/renderer/ForegroundRenderLayer.ts index 907f686d..71168f26 100644 --- a/src/renderer/ForegroundRenderLayer.ts +++ b/src/renderer/ForegroundRenderLayer.ts @@ -6,6 +6,13 @@ import { GridCache } from './GridCache'; import { CharData } from '../Types'; import { BaseRenderLayer, INVERTED_DEFAULT_COLOR } from './BaseRenderLayer'; +/** + * This CharData looks like a null character, which will forc a clear and render + * when the character changes (a regular space ' ' character may not as it's + * drawn state is a cleared cell). + */ +const EMOJI_OWNED_CHAR_DATA: CharData = [null, '', 0, -1]; + export class ForegroundRenderLayer extends BaseRenderLayer { private _state: GridCache; @@ -27,13 +34,6 @@ export class ForegroundRenderLayer extends BaseRenderLayer { } public onGridChanged(terminal: ITerminal, startRow: number, endRow: number): void { - // TODO: Ensure that the render is eventually performed - // Don't bother render until the atlas bitmap is ready - // TODO: Move this to BaseRenderLayer? - // if (!BaseRenderLayer._charAtlas) { - // return; - // } - // Resize has not been called yet if (this._state.cache.length === 0) { return; @@ -48,7 +48,7 @@ export class ForegroundRenderLayer extends BaseRenderLayer { const code: number = charData[CHAR_DATA_CODE_INDEX]; const char: string = charData[CHAR_DATA_CHAR_INDEX]; const attr: number = charData[CHAR_DATA_ATTR_INDEX]; - const width: number = charData[CHAR_DATA_WIDTH_INDEX]; + let width: number = charData[CHAR_DATA_WIDTH_INDEX]; // The character to the left is a wide character, drawing is owned by // the char at x-1 @@ -57,6 +57,18 @@ export class ForegroundRenderLayer extends BaseRenderLayer { continue; } + // If the character is a space and the character to the left is an + // emoji, skip the character and allow the emoji char to take full + // control over this character's cell. + if (code === 32 /*' '*/) { + if (x > 0) { + const previousChar: CharData = line[x - 1]; + if (this._isEmoji(previousChar[CHAR_DATA_CHAR_INDEX])) { + continue; + } + } + } + // Skip rendering if the character is identical const state = this._state.cache[x][y]; if (state && state[CHAR_DATA_CHAR_INDEX] === char && state[CHAR_DATA_ATTR_INDEX] === attr) { @@ -67,7 +79,7 @@ export class ForegroundRenderLayer extends BaseRenderLayer { // Clear the old character if present if (state && state[CHAR_DATA_CODE_INDEX] !== 32 /*' '*/) { - this.clearChar(x, y); + this._clearChar(x, y); } this._state.cache[x][y] = charData; @@ -78,6 +90,19 @@ export class ForegroundRenderLayer extends BaseRenderLayer { continue; } + // If the character is an emoji and the character to the right is a + // space, take ownership of the cell to the right. + if (this._isEmoji(char)) { + if (x < line.length && line[x + 1][CHAR_DATA_CODE_INDEX] === 32 /*' '*/) { + width = 2; + this._clearChar(x + 1, y); + // The emoji owned char data will force a clear and render when the + // emoji is no longer to the left of the character and also when the + // space changes to another character. + this._state.cache[x + 1][y] = EMOJI_OWNED_CHAR_DATA; + } + } + let fg = (attr >> 9) & 0x1ff; // If inverse flag is on, the foreground should become the background. @@ -117,7 +142,20 @@ export class ForegroundRenderLayer extends BaseRenderLayer { } } - private clearChar(x: number, y: number): void { + /** + * Whether the character is an emoji. + * @param char The character to search. + */ + private _isEmoji(char: string): boolean { + return char.search(/([\uD800-\uDBFF][\uDC00-\uDFFF])/g) >= 0; + } + + /** + * Clear the charcater at the cell specified. + * @param x The column of the char. + * @param y The row of the char. + */ + private _clearChar(x: number, y: number): void { let colsToClear = 1; // Clear the adjacent character if it was wide const state = this._state.cache[x][y]; From 1c3028c59f9afb4623c93d142aedb9ffc49b4cd6 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 4 Sep 2017 09:23:14 -0700 Subject: [PATCH 195/378] Work around an emoji bug --- src/renderer/ForegroundRenderLayer.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/renderer/ForegroundRenderLayer.ts b/src/renderer/ForegroundRenderLayer.ts index 71168f26..ef9c3c60 100644 --- a/src/renderer/ForegroundRenderLayer.ts +++ b/src/renderer/ForegroundRenderLayer.ts @@ -93,6 +93,13 @@ export class ForegroundRenderLayer extends BaseRenderLayer { // If the character is an emoji and the character to the right is a // space, take ownership of the cell to the right. if (this._isEmoji(char)) { + // If the character is an emoji, we want to force a re-render on every + // frame. This is specifically to work around the case where two + // emoji's `a` and `b` are adjacent, the cursor is moved to b and a + // space is added. Without this, the first half of `b` would never + // get removed, and `a` would not re-render because it thinks it's + // already in the correct state. + this._state.cache[x][y] = EMOJI_OWNED_CHAR_DATA; if (x < line.length && line[x + 1][CHAR_DATA_CODE_INDEX] === 32 /*' '*/) { width = 2; this._clearChar(x + 1, y); From a35cf02208345f5b7790d5659069ab9eae3c5a12 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 4 Sep 2017 09:39:44 -0700 Subject: [PATCH 196/378] Add GridCache tests --- src/renderer/GridCache.test.ts | 61 ++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 src/renderer/GridCache.test.ts diff --git a/src/renderer/GridCache.test.ts b/src/renderer/GridCache.test.ts new file mode 100644 index 00000000..37e55ee1 --- /dev/null +++ b/src/renderer/GridCache.test.ts @@ -0,0 +1,61 @@ +/** + * @license MIT + */ + +import { assert } from 'chai'; +import { GridCache } from './GridCache'; + +describe('GridCache', () => { + let grid: GridCache; + + beforeEach(() => { + grid = new GridCache(); + }); + + describe('constructor', () => { + it('should create an empty cache', () => { + assert.equal(grid.cache.length, 0); + }); + }); + + describe('resize', () => { + it('should fill all new elements with null', () => { + grid.resize(2, 2); + assert.equal(grid.cache.length, 2); + assert.equal(grid.cache[0].length, 2); + assert.equal(grid.cache[0][0], null); + assert.equal(grid.cache[0][1], null); + assert.equal(grid.cache[1].length, 2); + assert.equal(grid.cache[1][0], null); + assert.equal(grid.cache[1][1], null); + grid.resize(3, 2); + assert.equal(grid.cache.length, 3); + assert.equal(grid.cache[2][0], null); + assert.equal(grid.cache[2][1], null); + }); + + it('should remove rows/cols from the cache when reduced', () => { + grid.resize(2, 2); + grid.resize(1, 1); + assert.equal(grid.cache.length, 1); + assert.equal(grid.cache[0].length, 1); + }); + + it('should not touch existing cache entries if they fit in the new cache', () => { + grid.resize(1, 1); + assert.equal(grid.cache[0][0], null); + grid.cache[0][0] = 1; + grid.resize(2, 1); + assert.equal(grid.cache[0][0], 1); + }); + }); + + describe('clear', () => { + it('should make all entries null', () => { + grid.resize(1, 1); + grid.cache[0][0] = 1; + grid.clear(); + assert.equal(grid.cache[0][0], null); + }); + }); +}); From 6f6f17f5dc55411af1db457761cde79d3f1270e1 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 4 Sep 2017 09:52:35 -0700 Subject: [PATCH 197/378] Add tests for ColorManager --- src/renderer/ColorManager.test.ts | 288 ++++++++++++++++++++++++++++++ src/renderer/ColorManager.ts | 2 +- 2 files changed, 289 insertions(+), 1 deletion(-) create mode 100644 src/renderer/ColorManager.test.ts diff --git a/src/renderer/ColorManager.test.ts b/src/renderer/ColorManager.test.ts new file mode 100644 index 00000000..6bd9c977 --- /dev/null +++ b/src/renderer/ColorManager.test.ts @@ -0,0 +1,288 @@ +/** + * @license MIT + */ + +import { assert } from 'chai'; +import { ColorManager } from './ColorManager'; + +describe('ColorManager', () => { + let cm: ColorManager; + + beforeEach(() => { + cm = new ColorManager(); + }); + + describe('constructor', () => { + it('should fill all colors with values', () => { + for (let key in cm.colors) { + if (typeof key === 'string') { + // A #rrggbb or rgba(...) + assert.ok(cm.colors[key].length >= 7); + } + } + assert.equal(cm.colors.ansi.length, 256); + }); + + it('should fill 240 colors with expected values', () => { + assert.equal(cm.colors.ansi[16], '#000000'); + assert.equal(cm.colors.ansi[17], '#00005f'); + assert.equal(cm.colors.ansi[18], '#000087'); + assert.equal(cm.colors.ansi[19], '#0000af'); + assert.equal(cm.colors.ansi[20], '#0000d7'); + assert.equal(cm.colors.ansi[21], '#0000ff'); + assert.equal(cm.colors.ansi[22], '#005f00'); + assert.equal(cm.colors.ansi[23], '#005f5f'); + assert.equal(cm.colors.ansi[24], '#005f87'); + assert.equal(cm.colors.ansi[25], '#005faf'); + assert.equal(cm.colors.ansi[26], '#005fd7'); + assert.equal(cm.colors.ansi[27], '#005fff'); + assert.equal(cm.colors.ansi[28], '#008700'); + assert.equal(cm.colors.ansi[29], '#00875f'); + assert.equal(cm.colors.ansi[30], '#008787'); + assert.equal(cm.colors.ansi[31], '#0087af'); + assert.equal(cm.colors.ansi[32], '#0087d7'); + assert.equal(cm.colors.ansi[33], '#0087ff'); + assert.equal(cm.colors.ansi[34], '#00af00'); + assert.equal(cm.colors.ansi[35], '#00af5f'); + assert.equal(cm.colors.ansi[36], '#00af87'); + assert.equal(cm.colors.ansi[37], '#00afaf'); + assert.equal(cm.colors.ansi[38], '#00afd7'); + assert.equal(cm.colors.ansi[39], '#00afff'); + assert.equal(cm.colors.ansi[40], '#00d700'); + assert.equal(cm.colors.ansi[41], '#00d75f'); + assert.equal(cm.colors.ansi[42], '#00d787'); + assert.equal(cm.colors.ansi[43], '#00d7af'); + assert.equal(cm.colors.ansi[44], '#00d7d7'); + assert.equal(cm.colors.ansi[45], '#00d7ff'); + assert.equal(cm.colors.ansi[46], '#00ff00'); + assert.equal(cm.colors.ansi[47], '#00ff5f'); + assert.equal(cm.colors.ansi[48], '#00ff87'); + assert.equal(cm.colors.ansi[49], '#00ffaf'); + assert.equal(cm.colors.ansi[50], '#00ffd7'); + assert.equal(cm.colors.ansi[51], '#00ffff'); + assert.equal(cm.colors.ansi[52], '#5f0000'); + assert.equal(cm.colors.ansi[53], '#5f005f'); + assert.equal(cm.colors.ansi[54], '#5f0087'); + assert.equal(cm.colors.ansi[55], '#5f00af'); + assert.equal(cm.colors.ansi[56], '#5f00d7'); + assert.equal(cm.colors.ansi[57], '#5f00ff'); + assert.equal(cm.colors.ansi[58], '#5f5f00'); + assert.equal(cm.colors.ansi[59], '#5f5f5f'); + assert.equal(cm.colors.ansi[60], '#5f5f87'); + assert.equal(cm.colors.ansi[61], '#5f5faf'); + assert.equal(cm.colors.ansi[62], '#5f5fd7'); + assert.equal(cm.colors.ansi[63], '#5f5fff'); + assert.equal(cm.colors.ansi[64], '#5f8700'); + assert.equal(cm.colors.ansi[65], '#5f875f'); + assert.equal(cm.colors.ansi[66], '#5f8787'); + assert.equal(cm.colors.ansi[67], '#5f87af'); + assert.equal(cm.colors.ansi[68], '#5f87d7'); + assert.equal(cm.colors.ansi[69], '#5f87ff'); + assert.equal(cm.colors.ansi[70], '#5faf00'); + assert.equal(cm.colors.ansi[71], '#5faf5f'); + assert.equal(cm.colors.ansi[72], '#5faf87'); + assert.equal(cm.colors.ansi[73], '#5fafaf'); + assert.equal(cm.colors.ansi[74], '#5fafd7'); + assert.equal(cm.colors.ansi[75], '#5fafff'); + assert.equal(cm.colors.ansi[76], '#5fd700'); + assert.equal(cm.colors.ansi[77], '#5fd75f'); + assert.equal(cm.colors.ansi[78], '#5fd787'); + assert.equal(cm.colors.ansi[79], '#5fd7af'); + assert.equal(cm.colors.ansi[80], '#5fd7d7'); + assert.equal(cm.colors.ansi[81], '#5fd7ff'); + assert.equal(cm.colors.ansi[82], '#5fff00'); + assert.equal(cm.colors.ansi[83], '#5fff5f'); + assert.equal(cm.colors.ansi[84], '#5fff87'); + assert.equal(cm.colors.ansi[85], '#5fffaf'); + assert.equal(cm.colors.ansi[86], '#5fffd7'); + assert.equal(cm.colors.ansi[87], '#5fffff'); + assert.equal(cm.colors.ansi[88], '#870000'); + assert.equal(cm.colors.ansi[89], '#87005f'); + assert.equal(cm.colors.ansi[90], '#870087'); + assert.equal(cm.colors.ansi[91], '#8700af'); + assert.equal(cm.colors.ansi[92], '#8700d7'); + assert.equal(cm.colors.ansi[93], '#8700ff'); + assert.equal(cm.colors.ansi[94], '#875f00'); + assert.equal(cm.colors.ansi[95], '#875f5f'); + assert.equal(cm.colors.ansi[96], '#875f87'); + assert.equal(cm.colors.ansi[97], '#875faf'); + assert.equal(cm.colors.ansi[98], '#875fd7'); + assert.equal(cm.colors.ansi[99], '#875fff'); + assert.equal(cm.colors.ansi[100], '#878700'); + assert.equal(cm.colors.ansi[101], '#87875f'); + assert.equal(cm.colors.ansi[102], '#878787'); + assert.equal(cm.colors.ansi[103], '#8787af'); + assert.equal(cm.colors.ansi[104], '#8787d7'); + assert.equal(cm.colors.ansi[105], '#8787ff'); + assert.equal(cm.colors.ansi[106], '#87af00'); + assert.equal(cm.colors.ansi[107], '#87af5f'); + assert.equal(cm.colors.ansi[108], '#87af87'); + assert.equal(cm.colors.ansi[109], '#87afaf'); + assert.equal(cm.colors.ansi[110], '#87afd7'); + assert.equal(cm.colors.ansi[111], '#87afff'); + assert.equal(cm.colors.ansi[112], '#87d700'); + assert.equal(cm.colors.ansi[113], '#87d75f'); + assert.equal(cm.colors.ansi[114], '#87d787'); + assert.equal(cm.colors.ansi[115], '#87d7af'); + assert.equal(cm.colors.ansi[116], '#87d7d7'); + assert.equal(cm.colors.ansi[117], '#87d7ff'); + assert.equal(cm.colors.ansi[118], '#87ff00'); + assert.equal(cm.colors.ansi[119], '#87ff5f'); + assert.equal(cm.colors.ansi[120], '#87ff87'); + assert.equal(cm.colors.ansi[121], '#87ffaf'); + assert.equal(cm.colors.ansi[122], '#87ffd7'); + assert.equal(cm.colors.ansi[123], '#87ffff'); + assert.equal(cm.colors.ansi[124], '#af0000'); + assert.equal(cm.colors.ansi[125], '#af005f'); + assert.equal(cm.colors.ansi[126], '#af0087'); + assert.equal(cm.colors.ansi[127], '#af00af'); + assert.equal(cm.colors.ansi[128], '#af00d7'); + assert.equal(cm.colors.ansi[129], '#af00ff'); + assert.equal(cm.colors.ansi[130], '#af5f00'); + assert.equal(cm.colors.ansi[131], '#af5f5f'); + assert.equal(cm.colors.ansi[132], '#af5f87'); + assert.equal(cm.colors.ansi[133], '#af5faf'); + assert.equal(cm.colors.ansi[134], '#af5fd7'); + assert.equal(cm.colors.ansi[135], '#af5fff'); + assert.equal(cm.colors.ansi[136], '#af8700'); + assert.equal(cm.colors.ansi[137], '#af875f'); + assert.equal(cm.colors.ansi[138], '#af8787'); + assert.equal(cm.colors.ansi[139], '#af87af'); + assert.equal(cm.colors.ansi[140], '#af87d7'); + assert.equal(cm.colors.ansi[141], '#af87ff'); + assert.equal(cm.colors.ansi[142], '#afaf00'); + assert.equal(cm.colors.ansi[143], '#afaf5f'); + assert.equal(cm.colors.ansi[144], '#afaf87'); + assert.equal(cm.colors.ansi[145], '#afafaf'); + assert.equal(cm.colors.ansi[146], '#afafd7'); + assert.equal(cm.colors.ansi[147], '#afafff'); + assert.equal(cm.colors.ansi[148], '#afd700'); + assert.equal(cm.colors.ansi[149], '#afd75f'); + assert.equal(cm.colors.ansi[150], '#afd787'); + assert.equal(cm.colors.ansi[151], '#afd7af'); + assert.equal(cm.colors.ansi[152], '#afd7d7'); + assert.equal(cm.colors.ansi[153], '#afd7ff'); + assert.equal(cm.colors.ansi[154], '#afff00'); + assert.equal(cm.colors.ansi[155], '#afff5f'); + assert.equal(cm.colors.ansi[156], '#afff87'); + assert.equal(cm.colors.ansi[157], '#afffaf'); + assert.equal(cm.colors.ansi[158], '#afffd7'); + assert.equal(cm.colors.ansi[159], '#afffff'); + assert.equal(cm.colors.ansi[160], '#d70000'); + assert.equal(cm.colors.ansi[161], '#d7005f'); + assert.equal(cm.colors.ansi[162], '#d70087'); + assert.equal(cm.colors.ansi[163], '#d700af'); + assert.equal(cm.colors.ansi[164], '#d700d7'); + assert.equal(cm.colors.ansi[165], '#d700ff'); + assert.equal(cm.colors.ansi[166], '#d75f00'); + assert.equal(cm.colors.ansi[167], '#d75f5f'); + assert.equal(cm.colors.ansi[168], '#d75f87'); + assert.equal(cm.colors.ansi[169], '#d75faf'); + assert.equal(cm.colors.ansi[170], '#d75fd7'); + assert.equal(cm.colors.ansi[171], '#d75fff'); + assert.equal(cm.colors.ansi[172], '#d78700'); + assert.equal(cm.colors.ansi[173], '#d7875f'); + assert.equal(cm.colors.ansi[174], '#d78787'); + assert.equal(cm.colors.ansi[175], '#d787af'); + assert.equal(cm.colors.ansi[176], '#d787d7'); + assert.equal(cm.colors.ansi[177], '#d787ff'); + assert.equal(cm.colors.ansi[178], '#d7af00'); + assert.equal(cm.colors.ansi[179], '#d7af5f'); + assert.equal(cm.colors.ansi[180], '#d7af87'); + assert.equal(cm.colors.ansi[181], '#d7afaf'); + assert.equal(cm.colors.ansi[182], '#d7afd7'); + assert.equal(cm.colors.ansi[183], '#d7afff'); + assert.equal(cm.colors.ansi[184], '#d7d700'); + assert.equal(cm.colors.ansi[185], '#d7d75f'); + assert.equal(cm.colors.ansi[186], '#d7d787'); + assert.equal(cm.colors.ansi[187], '#d7d7af'); + assert.equal(cm.colors.ansi[188], '#d7d7d7'); + assert.equal(cm.colors.ansi[189], '#d7d7ff'); + assert.equal(cm.colors.ansi[190], '#d7ff00'); + assert.equal(cm.colors.ansi[191], '#d7ff5f'); + assert.equal(cm.colors.ansi[192], '#d7ff87'); + assert.equal(cm.colors.ansi[193], '#d7ffaf'); + assert.equal(cm.colors.ansi[194], '#d7ffd7'); + assert.equal(cm.colors.ansi[195], '#d7ffff'); + assert.equal(cm.colors.ansi[196], '#ff0000'); + assert.equal(cm.colors.ansi[197], '#ff005f'); + assert.equal(cm.colors.ansi[198], '#ff0087'); + assert.equal(cm.colors.ansi[199], '#ff00af'); + assert.equal(cm.colors.ansi[200], '#ff00d7'); + assert.equal(cm.colors.ansi[201], '#ff00ff'); + assert.equal(cm.colors.ansi[202], '#ff5f00'); + assert.equal(cm.colors.ansi[203], '#ff5f5f'); + assert.equal(cm.colors.ansi[204], '#ff5f87'); + assert.equal(cm.colors.ansi[205], '#ff5faf'); + assert.equal(cm.colors.ansi[206], '#ff5fd7'); + assert.equal(cm.colors.ansi[207], '#ff5fff'); + assert.equal(cm.colors.ansi[208], '#ff8700'); + assert.equal(cm.colors.ansi[209], '#ff875f'); + assert.equal(cm.colors.ansi[210], '#ff8787'); + assert.equal(cm.colors.ansi[211], '#ff87af'); + assert.equal(cm.colors.ansi[212], '#ff87d7'); + assert.equal(cm.colors.ansi[213], '#ff87ff'); + assert.equal(cm.colors.ansi[214], '#ffaf00'); + assert.equal(cm.colors.ansi[215], '#ffaf5f'); + assert.equal(cm.colors.ansi[216], '#ffaf87'); + assert.equal(cm.colors.ansi[217], '#ffafaf'); + assert.equal(cm.colors.ansi[218], '#ffafd7'); + assert.equal(cm.colors.ansi[219], '#ffafff'); + assert.equal(cm.colors.ansi[220], '#ffd700'); + assert.equal(cm.colors.ansi[221], '#ffd75f'); + assert.equal(cm.colors.ansi[222], '#ffd787'); + assert.equal(cm.colors.ansi[223], '#ffd7af'); + assert.equal(cm.colors.ansi[224], '#ffd7d7'); + assert.equal(cm.colors.ansi[225], '#ffd7ff'); + assert.equal(cm.colors.ansi[226], '#ffff00'); + assert.equal(cm.colors.ansi[227], '#ffff5f'); + assert.equal(cm.colors.ansi[228], '#ffff87'); + assert.equal(cm.colors.ansi[229], '#ffffaf'); + assert.equal(cm.colors.ansi[230], '#ffffd7'); + assert.equal(cm.colors.ansi[231], '#ffffff'); + assert.equal(cm.colors.ansi[232], '#080808'); + assert.equal(cm.colors.ansi[233], '#121212'); + assert.equal(cm.colors.ansi[234], '#1c1c1c'); + assert.equal(cm.colors.ansi[235], '#262626'); + assert.equal(cm.colors.ansi[236], '#303030'); + assert.equal(cm.colors.ansi[237], '#3a3a3a'); + assert.equal(cm.colors.ansi[238], '#444444'); + assert.equal(cm.colors.ansi[239], '#4e4e4e'); + assert.equal(cm.colors.ansi[240], '#585858'); + assert.equal(cm.colors.ansi[241], '#626262'); + assert.equal(cm.colors.ansi[242], '#6c6c6c'); + assert.equal(cm.colors.ansi[243], '#767676'); + assert.equal(cm.colors.ansi[244], '#808080'); + assert.equal(cm.colors.ansi[245], '#8a8a8a'); + assert.equal(cm.colors.ansi[246], '#949494'); + assert.equal(cm.colors.ansi[247], '#9e9e9e'); + assert.equal(cm.colors.ansi[248], '#a8a8a8'); + assert.equal(cm.colors.ansi[249], '#b2b2b2'); + assert.equal(cm.colors.ansi[250], '#bcbcbc'); + assert.equal(cm.colors.ansi[251], '#c6c6c6'); + assert.equal(cm.colors.ansi[252], '#d0d0d0'); + assert.equal(cm.colors.ansi[253], '#dadada'); + assert.equal(cm.colors.ansi[254], '#e4e4e4'); + assert.equal(cm.colors.ansi[255], '#eeeeee'); + }); + }); + + describe('setTheme', () => { + it('should set a partial set of colors, using the default if not present', () => { + assert.equal(cm.colors.background, '#000000'); + assert.equal(cm.colors.foreground, '#ffffff'); + cm.setTheme({ + background: '#FF0000', + foreground: '#00FF00' + }); + assert.equal(cm.colors.background, '#FF0000'); + assert.equal(cm.colors.foreground, '#00FF00'); + cm.setTheme({ + background: '#0000FF' + }); + assert.equal(cm.colors.background, '#0000FF'); + // FG reverts back to default + assert.equal(cm.colors.foreground, '#ffffff'); + }); + }); +}); diff --git a/src/renderer/ColorManager.ts b/src/renderer/ColorManager.ts index 17aee392..2ae785dd 100644 --- a/src/renderer/ColorManager.ts +++ b/src/renderer/ColorManager.ts @@ -4,7 +4,7 @@ import { ITheme } from '../Interfaces'; const DEFAULT_FOREGROUND = '#ffffff'; const DEFAULT_BACKGROUND = '#000000'; const DEFAULT_CURSOR = '#ffffff'; -const DEFAULT_SELECTION = 'rgba(255, 255, 255, 0.9)'; +const DEFAULT_SELECTION = 'rgba(255, 255, 255, 0.3)'; export const DEFAULT_ANSI_COLORS = [ // dark: '#2e3436', From 1f38c2f4679c30cc34cb66991706fe509e1e000d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 4 Sep 2017 09:53:53 -0700 Subject: [PATCH 198/378] Remove unused file --- src/renderer/Canvas.ts | 14 -------------- src/renderer/Renderer.ts | 1 - 2 files changed, 15 deletions(-) delete mode 100644 src/renderer/Canvas.ts diff --git a/src/renderer/Canvas.ts b/src/renderer/Canvas.ts deleted file mode 100644 index 97e7f9ea..00000000 --- a/src/renderer/Canvas.ts +++ /dev/null @@ -1,14 +0,0 @@ -export function createBackgroundFillData(width: number, height: number, r: number, g: number, b: number, a: number): Uint8ClampedArray { - const data = new Uint8ClampedArray(width * height * 4); - let offset = 0; - for (let i = 0; i < height; i++) { - for (let j = 0; j < width; j++) { - data[offset] = r; - data[offset + 1] = g; - data[offset + 2] = b; - data[offset + 3] = a; - offset += 4; - } - } - return data; -} diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 6b12188e..66fa6a15 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -4,7 +4,6 @@ import { ITerminal, ITheme } from '../Interfaces'; import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from '../Buffer'; -import { createBackgroundFillData } from './Canvas'; import { BackgroundRenderLayer } from './BackgroundRenderLayer'; import { ForegroundRenderLayer } from './ForegroundRenderLayer'; import { SelectionRenderLayer } from './SelectionRenderLayer'; From 9506c01824a4a348cbd637945ffd7d3ba3e89ef9 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 4 Sep 2017 10:27:07 -0700 Subject: [PATCH 199/378] Add tests for utils/Mouse --- src/utils/Mouse.test.ts | 62 +++++++++++++++++++++++++++++++++++++ src/utils/Mouse.ts | 6 ++-- src/utils/TestUtils.test.ts | 8 +++++ 3 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 src/utils/Mouse.test.ts diff --git a/src/utils/Mouse.test.ts b/src/utils/Mouse.test.ts new file mode 100644 index 00000000..15b68ef2 --- /dev/null +++ b/src/utils/Mouse.test.ts @@ -0,0 +1,62 @@ +/** + * @license MIT + */ + +import jsdom = require('jsdom'); +import { assert } from 'chai'; +import { getCoords } from './Mouse'; +import { MockCharMeasure } from './TestUtils.test'; + +const CHAR_WIDTH = 10; +const CHAR_HEIGHT = 20; + +describe('getCoords', () => { + let dom: jsdom.JSDOM; + let window: Window; + let document: Document; + + let charMeasure: MockCharMeasure; + + beforeEach(() => { + dom = new jsdom.JSDOM(''); + window = dom.window; + document = window.document; + charMeasure = new MockCharMeasure(); + charMeasure.width = CHAR_WIDTH; + charMeasure.height = CHAR_HEIGHT; + }); + + describe('when charMeasure is not initialized', () => { + it('should return null', () => { + charMeasure = new MockCharMeasure(); + assert.equal(getCoords({ pageX: 0, pageY: 0 }, document.createElement('div'), charMeasure, 1, 10, 10), null); + }); + }); + + describe('when pageX/pageY are not supported', () => { + it('should return null', () => { + assert.equal(getCoords({ pageX: undefined, pageY: undefined }, document.createElement('div'), charMeasure, 1, 10, 10), null); + }); + }); + + it('should return the cell that was clicked', () => { + let coords: [number, number]; + coords = getCoords({ pageX: CHAR_WIDTH / 2, pageY: CHAR_HEIGHT / 2 }, document.createElement('div'), charMeasure, 1, 10, 10); + assert.deepEqual(coords, [1, 1]); + coords = getCoords({ pageX: CHAR_WIDTH, pageY: CHAR_HEIGHT }, document.createElement('div'), charMeasure, 1, 10, 10); + assert.deepEqual(coords, [1, 1]); + coords = getCoords({ pageX: CHAR_WIDTH, pageY: CHAR_HEIGHT + 1 }, document.createElement('div'), charMeasure, 1, 10, 10); + assert.deepEqual(coords, [1, 2]); + coords = getCoords({ pageX: CHAR_WIDTH + 1, pageY: CHAR_HEIGHT }, document.createElement('div'), charMeasure, 1, 10, 10); + assert.deepEqual(coords, [2, 1]); + }); + + it('should ensure the coordinates are returned within the terminal bounds', () => { + let coords: [number, number]; + coords = getCoords({ pageX: -1, pageY: -1 }, document.createElement('div'), charMeasure, 1, 10, 10); + assert.deepEqual(coords, [1, 1]); + // Event are double the cols/rows + coords = getCoords({ pageX: CHAR_WIDTH * 20, pageY: CHAR_HEIGHT * 20 }, document.createElement('div'), charMeasure, 1, 10, 10); + assert.deepEqual(coords, [11, 11], 'coordinates should never come back as larger than the terminal'); + }); +}); diff --git a/src/utils/Mouse.ts b/src/utils/Mouse.ts index 0d433cd6..3bbad855 100644 --- a/src/utils/Mouse.ts +++ b/src/utils/Mouse.ts @@ -4,7 +4,7 @@ import { ICharMeasure } from '../Interfaces'; -export function getCoordsRelativeToElement(event: MouseEvent, element: HTMLElement): [number, number] { +export function getCoordsRelativeToElement(event: {pageX: number, pageY: number}, element: HTMLElement): [number, number] { // Ignore browsers that don't support MouseEvent.pageX if (event.pageX == null) { return null; @@ -15,7 +15,7 @@ export function getCoordsRelativeToElement(event: MouseEvent, element: HTMLEleme // Converts the coordinates from being relative to the document to being // relative to the terminal. - while (element && element !== self.document.documentElement) { + while (element) { x -= element.offsetLeft; y -= element.offsetTop; element = 'offsetParent' in element ? element.offsetParent : element.parentElement; @@ -36,7 +36,7 @@ export function getCoordsRelativeToElement(event: MouseEvent, element: HTMLEleme * apply an offset to the x value such that the left half of the cell will * select that cell and the right half will select the next cell. */ -export function getCoords(event: MouseEvent, element: HTMLElement, charMeasure: ICharMeasure, lineHeight: number, colCount: number, rowCount: number, isSelection?: boolean): [number, number] { +export function getCoords(event: {pageX: number, pageY: number}, element: HTMLElement, charMeasure: ICharMeasure, lineHeight: number, colCount: number, rowCount: number, isSelection?: boolean): [number, number] { // Coordinates cannot be measured if charMeasure has not been initialized if (!charMeasure.width || !charMeasure.height) { return null; diff --git a/src/utils/TestUtils.test.ts b/src/utils/TestUtils.test.ts index 51f191a8..ed0a19fb 100644 --- a/src/utils/TestUtils.test.ts +++ b/src/utils/TestUtils.test.ts @@ -68,6 +68,14 @@ export class MockTerminal implements ITerminal { } } +export class MockCharMeasure implements ICharMeasure { + width: number; + height: number; + measure(options: ITerminalOptions): void { + throw new Error('Method not implemented.'); + } +} + export class MockInputHandlingTerminal implements IInputHandlingTerminal { element: HTMLElement; options: ITerminalOptions = {}; From da1e3b40cca8017d3d311f57a7e1edc1032b359c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 4 Sep 2017 12:59:59 -0700 Subject: [PATCH 200/378] Don't use ImageBitmap on Firefox --- src/renderer/CharAtlas.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/renderer/CharAtlas.ts b/src/renderer/CharAtlas.ts index 05eb9a84..e2562873 100644 --- a/src/renderer/CharAtlas.ts +++ b/src/renderer/CharAtlas.ts @@ -1,5 +1,6 @@ import { ITerminal, ITheme } from '../Interfaces'; import { IColorSet } from '../renderer/Interfaces'; +import { isFirefox } from '../utils/Browser'; export const CHAR_ATLAS_CELL_SPACING = 1; @@ -152,8 +153,10 @@ class CharAtlasGenerator { const charAtlasImageData = this._ctx.getImageData(0, 0, this._canvas.width, this._canvas.height); // Support is patchy for createImageBitmap at the moment, pass a canvas back - // if support is lacking as drawImage works there too. - if (!('createImageBitmap' in window)) { + // if support is lacking as drawImage works there too. Firefox is also + // included here as ImageBitmap appears both buggy and has horrible + // performance (tested on v55). + if (!('createImageBitmap' in window) || isFirefox) { // Regenerate canvas and context as they are now owned by the char atlas const result = this._canvas; this._canvas = this._document.createElement('canvas'); From 8e07af04e37d7765da9e63c015ea33f9401a2b75 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 4 Sep 2017 13:39:33 -0700 Subject: [PATCH 201/378] Drop support for IE/Opera and indicate latest Opera uses Blink under the hood so supporting Chrome is basically supporting Opera --- README.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index edfa1169..f6b805ef 100644 --- a/README.md +++ b/README.md @@ -97,12 +97,10 @@ xterm.fit(); Since xterm.js is typically implemented as a developer tool, only modern browsers are supported officially. Here is a list of the versions we aim to support: -- Chrome 48+ -- Edge 13+ -- Firefox 44+ -- Internet Explorer 11+ -- Opera 35+ -- Safari 8+ +- Chrome latest +- Edge latest +- Firefox latest +- Safari latest Xterm.js works seamlessly in Electron apps and may even work on earlier versions of the browsers but these are the browsers we strive to keep working. From 0dc24cf800e4f4efdfbc7e4182c8262833610b59 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 4 Sep 2017 13:41:56 -0700 Subject: [PATCH 202/378] Move theme setting into setOption API --- src/Interfaces.ts | 1 + src/Terminal.ts | 46 ++++++++++++++++++++++++++++++++++------------ typings/xterm.d.ts | 15 ++++++++++----- 3 files changed, 45 insertions(+), 17 deletions(-) diff --git a/src/Interfaces.ts b/src/Interfaces.ts index da6d2727..a0d75a54 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -134,6 +134,7 @@ export interface ITerminalOptions { scrollback?: number; tabStopWidth?: number; termName?: string; + theme?: ITheme; useFlowControl?: boolean; } diff --git a/src/Terminal.ts b/src/Terminal.ts index cab25b03..15c6335a 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -82,7 +82,8 @@ const DEFAULT_OPTIONS: ITerminalOptions = { cancelEvents: false, disableStdin: false, useFlowControl: false, - tabStopWidth: 8 + tabStopWidth: 8, + theme: null // programFeatures: false, // focusKeys: false, }; @@ -320,16 +321,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT return document.activeElement === this.textarea; } - public setTheme(theme: ITheme): void { - // TODO: Allow setting of theme before renderer is ready - if (this.renderer) { - const colors = this.renderer.setTheme(theme); - if (this.viewport) { - this.viewport.onThemeChanged(colors); - } - } - } - /** * Retrieves an option's value from the terminal. * @param {string} key The option key. @@ -377,6 +368,14 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT return; } break; + case 'theme': + // If open has been called we do not want to set options.theme as the + // source of truth is owned by the renderer. + if (this.renderer) { + this._setTheme(value); + return; + } + break; case 'scrollback': if (value < 0) { console.warn(`${key} cannot be less than 0, value: ${value}`); @@ -420,7 +419,10 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT case 'bellSound': case 'bellStyle': this.syncBellSound(); break; } - this.renderer.onOptionsChanged(); + // Inform renderer of changes + if (this.renderer) { + this.renderer.onOptionsChanged(); + } } /** @@ -644,6 +646,15 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT // Measure the character size this.charMeasure.measure(this.options); + // Set the theme if it was set via setOption/constructor before open. This + // must be run after CharMeasure.measure as it depends on char dimensions. + setTimeout(() => { + if (this.options.theme) { + this._setTheme(this.options.theme); + this.options.theme = null; + } + }, 0); + // Setup loop that draws to screen this.refresh(0, this.rows - 1); @@ -655,6 +666,17 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.bindMouse(); } + /** + * Sets the theme on the renderer. The renderer must have been initialized. + * @param theme The theme to ste. + */ + private _setTheme(theme: ITheme): void { + const colors = this.renderer.setTheme(theme); + if (this.viewport) { + this.viewport.onThemeChanged(colors); + } + } + /** * Attempts to load an add-on using CommonJS or RequireJS (whichever is available). * @param {string} addon The name of the addon to load diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index eff26c9b..d98f6c5d 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -71,6 +71,11 @@ interface ITerminalOptions { * The size of tab stops in the terminal. */ tabStopWidth?: number; + + /** + * The color theme of the terminal. + */ + theme?: ITheme; } /** @@ -464,13 +469,13 @@ declare module 'xterm' { * @param key The option key. * @param value The option value. */ - setOption(key: string, value: any): void; - + setOption(key: 'theme', value: ITheme): void; /** - * Sets the theme of the terminal. - * @param theme The theme to use. + * Sets an option on the terminal. + * @param key The option key. + * @param value The option value. */ - setTheme(theme: ITheme): void; + setOption(key: string, value: any): void; /** * Tells the renderer to refresh terminal content between two rows From 46b0857ef6c7d85420160ca8b19cef63f7336896 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 4 Sep 2017 14:13:41 -0700 Subject: [PATCH 203/378] Resolve TODOs --- src/Linkifier.ts | 7 +++++-- src/renderer/BaseRenderLayer.ts | 3 --- src/renderer/Renderer.ts | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Linkifier.ts b/src/Linkifier.ts index d3150b4f..e04ad767 100644 --- a/src/Linkifier.ts +++ b/src/Linkifier.ts @@ -77,7 +77,6 @@ export class Linkifier { // Clear out any existing links this._mouseZoneManager.clearAll(); - // TODO: Cancel any validation callbacks if (this._rowsTimeoutId) { clearTimeout(this._rowsTimeoutId); @@ -91,6 +90,7 @@ export class Linkifier { * @param end The row to end at. */ private _linkifyRows(start: number, end: number): void { + this._rowsTimeoutId = null; for (let i = start; i <= end; i++) { this._linkifyRow(i); } @@ -220,8 +220,11 @@ export class Linkifier { // Ensure the link is valid before registering if (matcher.validationCallback) { matcher.validationCallback(text, isValid => { + // Discard link if the line has already changed + if (this._rowsTimeoutId) { + return; + } if (isValid) { - // TODO: Discard link if the line has already changed? this._addLink(offset + index, rowIndex, uri, matcher); } }); diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 462c4eca..08a229ed 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -14,8 +14,6 @@ export abstract class BaseRenderLayer implements IRenderLayer { private scaledLineHeight: number; private scaledLineDrawY: number; - // TODO: This should be shared between terminals, but not for static as some - // terminals may have different styles private _charAtlas: HTMLCanvasElement | ImageBitmap; constructor( @@ -32,7 +30,6 @@ export abstract class BaseRenderLayer implements IRenderLayer { container.appendChild(this._canvas); } - // TODO: Should this do anything? public onOptionsChanged(terminal: ITerminal): void {} public onBlur(terminal: ITerminal): void {} public onFocus(terminal: ITerminal): void {} diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 66fa6a15..a6d679a2 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -33,13 +33,13 @@ export class Renderer implements IRenderer { public setTheme(theme: ITheme): IColorSet { this._colorManager.setTheme(theme); + // Clear layers and force a full render this._renderLayers.forEach(l => { l.onThemeChanged(this._terminal, this._colorManager.colors); l.reset(this._terminal); }); - // TODO: This is currently done for every single terminal, but it's static so it's wasting time this._terminal.refresh(0, this._terminal.rows - 1); return this._colorManager.colors; From 1e728d453b7c5981853faa6978025ea23f9cfb7e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 4 Sep 2017 14:19:17 -0700 Subject: [PATCH 204/378] Add new API to typings test --- fixtures/typings-test/typings-test.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/fixtures/typings-test/typings-test.ts b/fixtures/typings-test/typings-test.ts index 90958f83..df79d2d0 100644 --- a/fixtures/typings-test/typings-test.ts +++ b/fixtures/typings-test/typings-test.ts @@ -168,6 +168,10 @@ namespace methods_core { t.setOption('bellStyle', 'visual'); t.setOption('bellStyle', 'sound'); t.setOption('bellStyle', 'both'); + t.setOption('fontSize', 1); + t.setOption('lineHeight', 1); + t.setOption('fontFamily', 'foo'); + t.setOption('theme', {background: '#ff0000'}); } } namespace scrolling { @@ -212,6 +216,9 @@ namespace methods_experimental { priority: 1, validationCallback: (uri: string, callback: (isValid: boolean) => void) => { console.log(uri, callback); + }, + hoverCallback: (e: MouseEvent, uri: string) => { + console.log(e, uri); } }); t.deregisterLinkMatcher(1); From 5c2d5dcd274335f1e7067f45b4a74960f9d9aa4f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 4 Sep 2017 19:34:57 -0700 Subject: [PATCH 205/378] Document methods of BaseRenderLayer --- src/renderer/BaseRenderLayer.ts | 94 +++++++++++++++++++++++---- src/renderer/CursorRenderLayer.ts | 10 +-- src/renderer/ForegroundRenderLayer.ts | 2 +- 3 files changed, 88 insertions(+), 18 deletions(-) diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 08a229ed..7bb7e9c5 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -41,6 +41,11 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._refreshCharAtlas(terminal, colorSet); } + /** + * Refreshes the char atlas, aquiring a new one if necessary. + * @param terminal The terminal. + * @param colorSet The color set to use for the char atlas. + */ private _refreshCharAtlas(terminal: ITerminal, colorSet: IColorSet): void { this._charAtlas = null; const result = acquireCharAtlas(terminal, this.colors); @@ -68,11 +73,24 @@ export abstract class BaseRenderLayer implements IRenderLayer { public abstract reset(terminal: ITerminal): void; - protected fillCells(startCol: number, startRow: number, colWidth: number, colHeight: number): void { - this._ctx.fillRect(startCol * this.scaledCharWidth, startRow * this.scaledLineHeight, colWidth * this.scaledCharWidth, colHeight * this.scaledLineHeight); + /** + * Fills 1+ cells completely. This uses the existing fillStyle on the context. + * @param x The column to start at. + * @param y The row to start at + * @param width The number of columns to fill. + * @param height The number of rows to fill. + */ + protected fillCells(x: number, y: number, width: number, height: number): void { + this._ctx.fillRect(x * this.scaledCharWidth, y * this.scaledLineHeight, width * this.scaledCharWidth, height * this.scaledLineHeight); } - protected drawBottomLineAtCell(x: number, y: number): void { + /** + * Fills a 1px line (2px on HDPI) at the bottom of the cell. This uses the + * existing fillStyle on the context. + * @param x The column to fill. + * @param y The row to fill. + */ + protected fillBottomLineAtCell(x: number, y: number): void { this._ctx.fillRect( x * this.scaledCharWidth, (y + 1) * this.scaledLineHeight - window.devicePixelRatio - 1 /* Ensure it's drawn within the cell */, @@ -80,7 +98,13 @@ export abstract class BaseRenderLayer implements IRenderLayer { window.devicePixelRatio); } - protected drawLeftLineAtCell(x: number, y: number): void { + /** + * Fills a 1px line (2px on HDPI) at the left of the cell. This uses the + * existing fillStyle on the context. + * @param x The column to fill. + * @param y The row to fill. + */ + protected fillLeftLineAtCell(x: number, y: number): void { this._ctx.fillRect( x * this.scaledCharWidth, y * this.scaledLineHeight, @@ -88,8 +112,13 @@ export abstract class BaseRenderLayer implements IRenderLayer { this.scaledLineHeight); } - protected drawRectAtCell(x: number, y: number, width: number, height: number, color: string): void { - this._ctx.strokeStyle = color; + /** + * Strokes a 1px rectangle (2px on HDPI) around a cell. This uses the existing + * strokeStyle on the context. + * @param x The column to fill. + * @param y The row to fill. + */ + protected strokeRectAtCell(x: number, y: number, width: number, height: number): void { this._ctx.lineWidth = window.devicePixelRatio; this._ctx.strokeRect( x * this.scaledCharWidth + window.devicePixelRatio / 2, @@ -98,19 +127,37 @@ export abstract class BaseRenderLayer implements IRenderLayer { (height * this.scaledLineHeight) - window.devicePixelRatio); } + /** + * Clears the entire canvas. + */ protected clearAll(): void { this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); } - protected clearCells(startCol: number, startRow: number, colWidth: number, colHeight: number): void { - this._ctx.clearRect(startCol * this.scaledCharWidth, startRow * this.scaledLineHeight, colWidth * this.scaledCharWidth, colHeight * this.scaledLineHeight); + /** + * Clears 1+ cells completely. + * @param x The column to start at. + * @param y The row to start at. + * @param width The number of columns to clear. + * @param height The number of rows to clear. + */ + protected clearCells(x: number, y: number, width: number, height: number): void { + this._ctx.clearRect(x * this.scaledCharWidth, y * this.scaledLineHeight, width * this.scaledCharWidth, height * this.scaledLineHeight); } - protected drawCharTrueColor(terminal: ITerminal, charData: CharData, x: number, y: number, color: string): void { - this._ctx.save(); + /** + * Draws a truecolor character at the cell. The character will be clipped to + * ensure that it fits with the cell, including the cell to the right if it's + * a wide character. This uses the existing fillStyle on the context. + * @param terminal The terminal. + * @param charData The char data for the character to draw. + * @param x The column to draw at. + * @param y The row to draw at. + * @param color The color of the character. + */ + protected fillCharTrueColor(terminal: ITerminal, charData: CharData, x: number, y: number): void { this._ctx.font = `${terminal.options.fontSize * window.devicePixelRatio}px ${terminal.options.fontFamily}`; this._ctx.textBaseline = 'top'; - this._ctx.fillStyle = color; // Since uncached characters are not coming off the char atlas with source // coordinates, it means that text drawn to the canvas (particularly '_') @@ -119,11 +166,21 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._ctx.beginPath(); this._ctx.rect(x * this.scaledCharWidth, y * this.scaledLineHeight + this.scaledLineDrawY, charData[CHAR_DATA_WIDTH_INDEX] * this.scaledCharWidth, this.scaledCharHeight); this._ctx.clip(); - this._ctx.fillText(charData[CHAR_DATA_CHAR_INDEX], x * this.scaledCharWidth, y * this.scaledCharHeight); - this._ctx.restore(); } + /** + * Draws a character at a cell. If possible this will draw using the character + * atlas to reduce draw time. + * @param terminal The terminal. + * @param char The character. + * @param code The character code. + * @param width The width of the character. + * @param x The column to draw at. + * @param y The row to draw at. + * @param fg The foreground color, in the format stored within the attributes. + * @param bold Whether the text is bold. + */ protected drawChar(terminal: ITerminal, char: string, code: number, width: number, x: number, y: number, fg: number, bold: boolean): void { // Clear the cell next to this character if it's wide if (width === 2) { @@ -157,6 +214,17 @@ export abstract class BaseRenderLayer implements IRenderLayer { // this._ctx.drawImage(this._charAtlas, 0, 0); } + /** + * Draws a character at a cell. The character will be clipped to + * ensure that it fits with the cell, including the cell to the right if it's + * a wide character. + * @param terminal The terminal. + * @param char The character. + * @param width The width of the character. + * @param fg The foreground color, in the format stored within the attributes. + * @param x The column to draw at. + * @param y The row to draw at. + */ private _drawUncachedChar(terminal: ITerminal, char: string, width: number, fg: number, x: number, y: number): void { this._ctx.save(); this._ctx.font = `${terminal.options.fontSize * window.devicePixelRatio}px ${terminal.options.fontFamily}`; diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index 00abfb93..9f5136ff 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -186,7 +186,7 @@ export class CursorRenderLayer extends BaseRenderLayer { private _renderBarCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void { this._ctx.save(); this._ctx.fillStyle = this.colors.cursor; - this.drawLeftLineAtCell(x, y); + this.fillLeftLineAtCell(x, y); this._ctx.restore(); } @@ -194,20 +194,22 @@ export class CursorRenderLayer extends BaseRenderLayer { this._ctx.save(); this._ctx.fillStyle = this.colors.cursor; this.fillCells(x, y, charData[CHAR_DATA_WIDTH_INDEX], 1); + this._ctx.fillStyle = this.colors.background; + this.fillCharTrueColor(terminal, charData, x, y); this._ctx.restore(); - this.drawCharTrueColor(terminal, charData, x, y, this.colors.background); } private _renderUnderlineCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void { this._ctx.save(); this._ctx.fillStyle = this.colors.cursor; - this.drawBottomLineAtCell(x, y); + this.fillBottomLineAtCell(x, y); this._ctx.restore(); } private _renderBlurCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void { this._ctx.save(); - this.drawRectAtCell(x, y, charData[CHAR_DATA_WIDTH_INDEX], 1, this.colors.cursor); + this._ctx.strokeStyle = this.colors.cursor; + this.strokeRectAtCell(x, y, charData[CHAR_DATA_WIDTH_INDEX], 1); this._ctx.restore(); } } diff --git a/src/renderer/ForegroundRenderLayer.ts b/src/renderer/ForegroundRenderLayer.ts index ef9c3c60..05141aed 100644 --- a/src/renderer/ForegroundRenderLayer.ts +++ b/src/renderer/ForegroundRenderLayer.ts @@ -139,7 +139,7 @@ export class ForegroundRenderLayer extends BaseRenderLayer { } else { this._ctx.fillStyle = this.colors.foreground; } - this.drawBottomLineAtCell(x, y); + this.fillBottomLineAtCell(x, y); } this.drawChar(terminal, char, code, width, x, y, fg, !!(flags & FLAGS.BOLD)); From 7b199ac17f52146296fd30986a1996bb96258074 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 4 Sep 2017 19:40:49 -0700 Subject: [PATCH 206/378] Some more documentation --- src/renderer/CharAtlas.ts | 10 ++++++++++ src/renderer/ColorManager.ts | 7 +++++++ 2 files changed, 17 insertions(+) diff --git a/src/renderer/CharAtlas.ts b/src/renderer/CharAtlas.ts index e2562873..b4264567 100644 --- a/src/renderer/CharAtlas.ts +++ b/src/renderer/CharAtlas.ts @@ -20,6 +20,12 @@ interface ICharAtlasCacheEntry { let charAtlasCache: ICharAtlasCacheEntry[] = []; +/** + * Acquires a char atlas, either generating a new one or returning an existing + * one that is in use by another terminal. + * @param terminal The terminal. + * @param colors The colors to use. + */ export function acquireCharAtlas(terminal: ITerminal, colors: IColorSet): HTMLCanvasElement | Promise { const scaledCharWidth = terminal.charMeasure.width * window.devicePixelRatio; const scaledCharHeight = terminal.charMeasure.height * window.devicePixelRatio; @@ -95,6 +101,10 @@ function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean { let generator: CharAtlasGenerator; +/** + * Initializes the char atlas generator. + * @param document The document. + */ export function initialize(document: Document): void { if (!generator) { generator = new CharAtlasGenerator(document); diff --git a/src/renderer/ColorManager.ts b/src/renderer/ColorManager.ts index 2ae785dd..057292f7 100644 --- a/src/renderer/ColorManager.ts +++ b/src/renderer/ColorManager.ts @@ -26,6 +26,10 @@ export const DEFAULT_ANSI_COLORS = [ '#eeeeec' ]; +/** + * Fills an existing 16 length string with the remaining 240 ANSI colors. + * @param first16Colors The first 16 ANSI colors. + */ function generate256Colors(first16Colors: string[]): string[] { let colors = first16Colors.slice(); @@ -52,6 +56,9 @@ function toPaddedHex(c: number): string { return s.length < 2 ? '0' + s : s; } +/** + * Manages the source of truth for a terminal's colors. + */ export class ColorManager { public colors: IColorSet; From 2f168da1dc753dfec7ddbf4ad07a837284ffc79c Mon Sep 17 00:00:00 2001 From: Vincent Woo Date: Tue, 5 Sep 2017 19:14:11 -0700 Subject: [PATCH 207/378] Delete unused timer variables --- src/addons/attach/attach.js | 2 -- src/addons/terminado/terminado.js | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/addons/attach/attach.js b/src/addons/attach/attach.js index 34dc4f68..81b704f0 100644 --- a/src/addons/attach/attach.js +++ b/src/addons/attach/attach.js @@ -44,8 +44,6 @@ term._flushBuffer = function () { term.write(term._attachSocketBuffer); term._attachSocketBuffer = null; - clearTimeout(term._attachSocketBufferTimer); - term._attachSocketBufferTimer = null; }; term._pushToBuffer = function (data) { diff --git a/src/addons/terminado/terminado.js b/src/addons/terminado/terminado.js index d1414c86..8b9f1cc0 100644 --- a/src/addons/terminado/terminado.js +++ b/src/addons/terminado/terminado.js @@ -45,8 +45,6 @@ term._flushBuffer = function () { term.write(term._attachSocketBuffer); term._attachSocketBuffer = null; - clearTimeout(term._attachSocketBufferTimer); - term._attachSocketBufferTimer = null; }; term._pushToBuffer = function (data) { From b69ff5d31aa7b68039f8338abf708981fbc08134 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 6 Sep 2017 10:22:33 -0700 Subject: [PATCH 208/378] Redraw terminal when devicePixelRatio changes --- src/Terminal.ts | 11 ++++------- src/renderer/Interfaces.ts | 5 +++-- src/renderer/Renderer.ts | 28 ++++++++++++++++++++-------- src/utils/TestUtils.test.ts | 5 +++-- 4 files changed, 30 insertions(+), 19 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 15c6335a..d42978f7 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -408,7 +408,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT case 'lineHeight': // When the font changes the size of the cells may change which requires a renderer clear this.renderer.clear(); - this.renderer.onResize(this.cols, this.rows); + this.renderer.onResize(this.cols, this.rows, false); this.refresh(0, this.rows - 1); // this.charMeasure.measure(this.options); case 'scrollback': @@ -620,14 +620,11 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.charMeasure.on('charsizechanged', () => this.viewport.syncScrollArea()); this.renderer = new Renderer(this); this.on('cursormove', () => this.renderer.onCursorMove()); - this.on('resize', () => this.renderer.onResize(this.cols, this.rows)); + this.on('resize', () => this.renderer.onResize(this.cols, this.rows, false)); this.on('blur', () => this.renderer.onBlur()); this.on('focus', () => this.renderer.onFocus()); - this.charMeasure.on('charsizechanged', () => { - this.renderer.onCharSizeChanged(this.charMeasure.width, this.charMeasure.height); - // Force a refresh for the char size change - this.renderer.queueRefresh(0, this.rows - 1); - }); + window.addEventListener('resize', () => this.renderer.onWindowResize(window.devicePixelRatio)); + this.charMeasure.on('charsizechanged', () => this.renderer.onResize(this.cols, this.rows, true)); this.selectionManager = new SelectionManager(this, this.buffer, this.charMeasure); this.element.addEventListener('mousedown', (e: MouseEvent) => this.selectionManager.onMouseDown(e)); diff --git a/src/renderer/Interfaces.ts b/src/renderer/Interfaces.ts index 04e24e19..e6b09843 100644 --- a/src/renderer/Interfaces.ts +++ b/src/renderer/Interfaces.ts @@ -2,8 +2,9 @@ import { ITerminal, ITerminalOptions, ITheme } from '../Interfaces'; export interface IRenderer { setTheme(theme: ITheme): IColorSet; - onResize(cols: number, rows: number): void; - onCharSizeChanged(charWidth: number, charHeight: number): void; + onWindowResize(devicePixelRatio: number): void; + onResize(cols: number, rows: number, didCharSizeChange: boolean): void; + onCharSizeChanged(): void; onBlur(): void; onFocus(): void; onSelectionChanged(start: [number, number], end: [number, number]): void; diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index a6d679a2..c032bc8f 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -18,6 +18,7 @@ export class Renderer implements IRenderer { private _refreshAnimationFrame = null; private _renderLayers: IRenderLayer[]; + private _devicePixelRatio: number; private _colorManager: ColorManager; @@ -29,6 +30,16 @@ export class Renderer implements IRenderer { new ForegroundRenderLayer(this._terminal.element, 2, this._colorManager.colors), new CursorRenderLayer(this._terminal.element, 3, this._colorManager.colors) ]; + this._devicePixelRatio = window.devicePixelRatio; + } + + public onWindowResize(devicePixelRatio: number): void { + // If the device pixel ratio changed, the char atlas needs to be regenerated + // and the terminal needs to refreshed + if (this._devicePixelRatio !== devicePixelRatio) { + this._devicePixelRatio = devicePixelRatio; + this.onResize(this._terminal.cols, this._terminal.rows, true); + } } public setTheme(theme: ITheme): IColorSet { @@ -45,19 +56,20 @@ export class Renderer implements IRenderer { return this._colorManager.colors; } - public onResize(cols: number, rows: number): void { + public onResize(cols: number, rows: number, didCharSizeChange: boolean): void { if (!this._terminal.charMeasure.width || !this._terminal.charMeasure.height) { return; } - const width = this._terminal.charMeasure.width * this._terminal.cols; - const height = Math.floor(this._terminal.charMeasure.height * this._terminal.options.lineHeight) * this._terminal.rows; - this._renderLayers.forEach(l => l.resize(this._terminal, width, height, false)); + const width = this._terminal.charMeasure.width * cols; + const height = Math.floor(this._terminal.charMeasure.height * this._terminal.options.lineHeight) * rows; + // Resize all render layers + this._renderLayers.forEach(l => l.resize(this._terminal, width, height, didCharSizeChange)); + // Force a refresh + this._terminal.refresh(0, this._terminal.rows - 1); } - public onCharSizeChanged(charWidth: number, charHeight: number): void { - const width = charWidth * this._terminal.cols; - const height = Math.floor(charHeight * this._terminal.options.lineHeight) * this._terminal.rows; - this._renderLayers.forEach(l => l.resize(this._terminal, width, height, true)); + public onCharSizeChanged(): void { + this.onResize(this._terminal.cols, this._terminal.rows, true); } public onBlur(): void { diff --git a/src/utils/TestUtils.test.ts b/src/utils/TestUtils.test.ts index ed0a19fb..652c3aa3 100644 --- a/src/utils/TestUtils.test.ts +++ b/src/utils/TestUtils.test.ts @@ -214,13 +214,14 @@ export class MockBuffer implements IBuffer { export class MockRenderer implements IRenderer { setTheme(theme: ITheme): IColorSet { return {}; } - onResize(cols: number, rows: number): void {} - onCharSizeChanged(charWidth: number, charHeight: number): void {} + onResize(cols: number, rows: number, didCharSizeChange: boolean): void {} + onCharSizeChanged(): void {} onBlur(): void {} onFocus(): void {} onSelectionChanged(start: [number, number], end: [number, number]): void {} onCursorMove(): void {} onOptionsChanged(): void {} + onWindowResize(devicePixelRatio: number): void {} clear(): void {} queueRefresh(start: number, end: number): void {} } From fb90c98f819848c0339a45dfa4bf6dcd59afa8d7 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 6 Sep 2017 10:36:34 -0700 Subject: [PATCH 209/378] Fix blurry text by supporting floating pt devicePixelRatios --- src/renderer/BaseRenderLayer.ts | 29 ++++++++++++++++++++++++----- src/renderer/CharAtlas.ts | 4 +--- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 7bb7e9c5..0e657ae7 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -48,7 +48,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { */ private _refreshCharAtlas(terminal: ITerminal, colorSet: IColorSet): void { this._charAtlas = null; - const result = acquireCharAtlas(terminal, this.colors); + const result = acquireCharAtlas(terminal, this.colors, this.scaledCharWidth, this.scaledCharHeight); if (result instanceof HTMLCanvasElement) { this._charAtlas = result; } else { @@ -57,12 +57,31 @@ export abstract class BaseRenderLayer implements IRenderLayer { } public resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void { - this.scaledCharWidth = terminal.charMeasure.width * window.devicePixelRatio; - this.scaledCharHeight = terminal.charMeasure.height * window.devicePixelRatio; + // Calculate the scaled character dimensions, if devicePixelRatio is a + // floating point number then the value is ceiled to ensure there is enough + // space to draw the character to the cell + this.scaledCharWidth = Math.ceil(terminal.charMeasure.width * window.devicePixelRatio); + this.scaledCharHeight = Math.ceil(terminal.charMeasure.height * window.devicePixelRatio); + + // Calculate the scaled line height, if lineHeight is not 1 then the value + // will be floored because since lineHeight can never be lower then 1, there + // is a guarentee that the scaled line height will always be larger than + // scaled char height. this.scaledLineHeight = Math.floor(this.scaledCharHeight * terminal.options.lineHeight); + + // Calculate the y coordinate within a cell that text should draw from in + // order to draw in the center of a cell. this.scaledLineDrawY = terminal.options.lineHeight === 1 ? 0 : Math.round((this.scaledLineHeight - this.scaledCharHeight) / 2); - this._canvas.width = canvasWidth * window.devicePixelRatio; - this._canvas.height = canvasHeight * window.devicePixelRatio; + + // Recalcualte the canvas dimensions; width/height define the actual number + // of pixels in the canvas, style.width/height define the size of the canvas + // on the page. It's very important that this rounds to nearest integer and + // not ceils as browsers often set window.devicePixelRatio as something like + // 1.100000023841858, when it's actually 1.1. Ceiling causes blurriness as + // the backing canvas image is 1 pixel too large for the canvas element + // size. + this._canvas.width = Math.round(canvasWidth * window.devicePixelRatio); + this._canvas.height = Math.round(canvasHeight * window.devicePixelRatio); this._canvas.style.width = `${canvasWidth}px`; this._canvas.style.height = `${canvasHeight}px`; diff --git a/src/renderer/CharAtlas.ts b/src/renderer/CharAtlas.ts index b4264567..434b263b 100644 --- a/src/renderer/CharAtlas.ts +++ b/src/renderer/CharAtlas.ts @@ -26,9 +26,7 @@ let charAtlasCache: ICharAtlasCacheEntry[] = []; * @param terminal The terminal. * @param colors The colors to use. */ -export function acquireCharAtlas(terminal: ITerminal, colors: IColorSet): HTMLCanvasElement | Promise { - const scaledCharWidth = terminal.charMeasure.width * window.devicePixelRatio; - const scaledCharHeight = terminal.charMeasure.height * window.devicePixelRatio; +export function acquireCharAtlas(terminal: ITerminal, colors: IColorSet, scaledCharWidth: number, scaledCharHeight: number): HTMLCanvasElement | Promise { const newConfig = generateConfig(scaledCharWidth, scaledCharHeight, terminal, colors); // Check to see if the terminal already owns this config From 66c891df5b0243cd276740ffb3bf169120317680 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 6 Sep 2017 15:36:21 -0700 Subject: [PATCH 210/378] Fix resize adding right padding on demo --- demo/main.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/demo/main.js b/demo/main.js index f05a8cef..f4fb0a2a 100644 --- a/demo/main.js +++ b/demo/main.js @@ -2,9 +2,7 @@ var term, protocol, socketURL, socket, - pid, - charWidth, - charHeight; + pid; var terminalContainer = document.getElementById('terminal-container'), actionElements = { @@ -21,11 +19,13 @@ var terminalContainer = document.getElementById('terminal-container'), colsElement = document.getElementById('cols'), rowsElement = document.getElementById('rows'); -function setTerminalSize () { - var cols = parseInt(colsElement.value, 10), - rows = parseInt(rowsElement.value, 10), - width = (cols * charWidth).toString() + 'px', - height = (rows * charHeight).toString() + 'px'; +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.charMeasure.width + 20 /*room for scrollbar*/).toString() + 'px'; + var height = (rows * term.charMeasure.height).toString() + 'px'; terminalContainer.style.width = width; terminalContainer.style.height = height; @@ -97,10 +97,10 @@ function createTerminal() { colsElement.value = term.cols; rowsElement.value = term.rows; - fetch('/terminals?cols=' + term.cols + '&rows=' + term.rows, {method: 'POST'}).then(function (res) { + // Set terminal size again to set the specific dimensions on the demo + setTerminalSize(); - charWidth = Math.ceil(term.element.offsetWidth / term.cols); - charHeight = Math.ceil(term.element.offsetHeight / term.rows); + fetch('/terminals?cols=' + term.cols + '&rows=' + term.rows, {method: 'POST'}).then(function (res) { res.text().then(function (pid) { window.pid = pid; From e960a0a82ce8dd82329eaa99c06db73ff8e53357 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 6 Sep 2017 16:05:09 -0700 Subject: [PATCH 211/378] Add ILinkMatcherOptions.hoverEndCallback --- src/Interfaces.ts | 8 ++++++-- src/Linkifier.ts | 12 +++++++++--- src/Types.ts | 3 ++- src/input/Interfaces.ts | 3 ++- src/input/MouseZoneManager.ts | 22 ++++++++++++++++++---- typings/xterm.d.ts | 9 +++++++-- 6 files changed, 44 insertions(+), 13 deletions(-) diff --git a/src/Interfaces.ts b/src/Interfaces.ts index a0d75a54..790bf419 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -241,9 +241,13 @@ export interface ILinkMatcherOptions { */ validationCallback?: LinkMatcherValidationCallback; /** - * A callback that fired when the mouse hovers over a link. + * A callback that fires when the mouse hovers over a link. */ - hoverCallback?: LinkMatcherHandler; + hoverStartCallback?: LinkMatcherHandler; + /** + * A callback that fires when the mouse leaves a link that was hovered. + */ + hoverEndCallback?: () => 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 diff --git a/src/Linkifier.ts b/src/Linkifier.ts index e04ad767..2adb6f63 100644 --- a/src/Linkifier.ts +++ b/src/Linkifier.ts @@ -133,7 +133,8 @@ export class Linkifier { handler, matchIndex: options.matchIndex, validationCallback: options.validationCallback, - hoverCallback: options.hoverCallback, + hoverStartCallback: options.hoverStartCallback, + hoverEndCallback: options.hoverEndCallback, priority: options.priority || 0 }; this._addLinkMatcherToList(matcher); @@ -259,8 +260,13 @@ export class Linkifier { window.open(uri, '_blank'); }, e => { - if (matcher.hoverCallback) { - return matcher.hoverCallback(e, uri); + if (matcher.hoverStartCallback) { + matcher.hoverStartCallback(e, uri); + } + }, + () => { + if (matcher.hoverEndCallback) { + matcher.hoverEndCallback(); } } )); diff --git a/src/Types.ts b/src/Types.ts index 1ced3d73..1af21e78 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -6,7 +6,8 @@ export type LinkMatcher = { id: number, regex: RegExp, handler: LinkMatcherHandler, - hoverCallback?: LinkMatcherHandler, + hoverStartCallback?: LinkMatcherHandler, + hoverEndCallback?: () => void, matchIndex?: number, validationCallback?: LinkMatcherValidationCallback, priority?: number diff --git a/src/input/Interfaces.ts b/src/input/Interfaces.ts index 15dcbae9..8756e540 100644 --- a/src/input/Interfaces.ts +++ b/src/input/Interfaces.ts @@ -8,5 +8,6 @@ export interface IMouseZone { x2: number; y: number; clickCallback: (e: MouseEvent) => any; - hoverCallback?: (e: MouseEvent) => any; + hoverStartCallback?: (e: MouseEvent) => any; + hoverEndCallback?: () => any; } diff --git a/src/input/MouseZoneManager.ts b/src/input/MouseZoneManager.ts index 37a6647d..7789082e 100644 --- a/src/input/MouseZoneManager.ts +++ b/src/input/MouseZoneManager.ts @@ -20,7 +20,8 @@ export class MouseZoneManager implements IMouseZoneManager { private _mouseDownListener: (e: MouseEvent) => any; private _clickListener: (e: MouseEvent) => any; - private _hoverTimeout: number; + private _hoverTimeout: number = null; + private _currentZone: IMouseZone = null; private _lastHoverCoords: [number, number] = [null, null]; constructor( @@ -60,11 +61,22 @@ export class MouseZoneManager implements IMouseZoneManager { } private _onMouseMove(e: MouseEvent): void { + // TODO: Ideally this would only clear the hover state when the mouse moves + // outside of the mouse zone if (this._lastHoverCoords[0] !== e.pageX && this._lastHoverCoords[1] !== e.pageY) { + // Restart the timeout if (this._hoverTimeout) { clearTimeout(this._hoverTimeout); } this._hoverTimeout = setTimeout(() => this._onHover(e), HOVER_DURATION); + + // Fire the hover end callback if a zone was being hovered + if (this._currentZone) { + this._currentZone.hoverEndCallback(); + this._currentZone = null; + } + + // Record the current coordinates this._lastHoverCoords = [e.pageX, e.pageY]; } } @@ -72,8 +84,9 @@ export class MouseZoneManager implements IMouseZoneManager { private _onHover(e: MouseEvent): void { const coords = getCoords(e, this._terminal.element, this._terminal.charMeasure, this._terminal.options.lineHeight, this._terminal.cols, this._terminal.rows); const zone = this._findZoneEventAt(e); - if (zone && zone.hoverCallback) { - zone.hoverCallback(e); + if (zone && zone.hoverStartCallback) { + this._currentZone = zone; + zone.hoverStartCallback(e); } } @@ -103,7 +116,8 @@ export class MouseZone implements IMouseZone { public x2: number, public y: number, public clickCallback: (e: MouseEvent) => any, - public hoverCallback?: (e: MouseEvent) => any + public hoverStartCallback?: (e: MouseEvent) => any, + public hoverEndCallback?: () => void ) { } } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index d98f6c5d..b219732a 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -141,9 +141,14 @@ interface ILinkMatcherOptions { validationCallback?: (uri: string, callback: (isValid: boolean) => void) => void; /** - * A callback that fired when the mouse hovers over a link. + * A callback that fires when the mouse hovers over a link. */ - hoverCallback?: (event: MouseEvent, uri: string) => boolean | void; + hoverStartCallback?: (event: MouseEvent, uri: string) => boolean | void; + + /** + * A callback that fires when the mouse leaves a link that was hovered. + */ + hoverEndCallback?: (event: MouseEvent, uri: string) => boolean | void; /** * The priority of the link matcher, this defines the order in which the link From 67962a5e5530ee872f8c24182cbd69a4063315f7 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 7 Sep 2017 07:19:48 -0700 Subject: [PATCH 212/378] typescript@2.4 For string enums --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 21ab9b89..f32918df 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "nodemon": "1.10.2", "sorcery": "^0.10.0", "tslint": "^4.0.2", - "typescript": "~2.3.0", + "typescript": "~2.4.0", "vinyl-buffer": "^1.0.0", "vinyl-source-stream": "^1.1.0" }, From 4eb356f6bef63bb87d3c57e76ee6776493308095 Mon Sep 17 00:00:00 2001 From: Thomas Zilz Date: Thu, 7 Sep 2017 17:13:15 +0200 Subject: [PATCH 213/378] Fix CSI Ps M to delete the correct line --- src/InputHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index abfdfa0e..3bc87d96 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -467,7 +467,7 @@ export class InputHandler implements IInputHandler { while (param--) { // test: echo -e '\e[44m\e[1M\e[0m' // blankLine(true) - xterm/linux behavior - this._terminal.buffer.lines.splice(row - 1, 1); + this._terminal.buffer.lines.splice(row, 1); this._terminal.buffer.lines.splice(j, 0, this._terminal.blankLine(true)); } From ea8dbbd29fd9d61ffa0e4adf510363773daafdc9 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 7 Sep 2017 10:20:48 -0700 Subject: [PATCH 214/378] Underline links on hover --- src/Interfaces.ts | 22 +++++++--- src/Linkifier.ts | 31 ++++++++------ src/Terminal.ts | 4 +- src/Types.ts | 16 ++++++- src/input/Interfaces.ts | 5 ++- src/input/MouseZoneManager.ts | 60 ++++++++++++++++++--------- src/renderer/BaseRenderLayer.ts | 4 +- src/renderer/CursorRenderLayer.ts | 2 +- src/renderer/ForegroundRenderLayer.ts | 2 +- src/renderer/LinkRenderLayer.ts | 44 ++++++++++++++++++++ src/renderer/Renderer.ts | 4 +- src/utils/TestUtils.test.ts | 3 +- src/xterm.css | 2 +- typings/xterm.d.ts | 9 ++-- 14 files changed, 153 insertions(+), 55 deletions(-) create mode 100644 src/renderer/LinkRenderLayer.ts diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 790bf419..b7779688 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -5,6 +5,7 @@ import { ILinkMatcherOptions } from './Interfaces'; import { LinkMatcherHandler, LinkMatcherValidationCallback, Charset, LineData } from './Types'; import { IColorSet } from './renderer/Interfaces'; +import { IMouseZoneManager } from './input/Interfaces'; export interface IBrowser { isNode: boolean; @@ -22,8 +23,15 @@ export interface IBufferAccessor { buffer: IBuffer; } -export interface ITerminal extends IBufferAccessor, IEventEmitter { +export interface IElementAccessor { element: HTMLElement; +} + +export interface ILinkifierAccessor { + linkifier: ILinkifier; +} + +export interface ITerminal extends ILinkifierAccessor, IBufferAccessor, IElementAccessor, IEventEmitter { selectionManager: ISelectionManager; charMeasure: ICharMeasure; textarea: HTMLTextAreaElement; @@ -197,9 +205,11 @@ export interface ICharMeasure { measure(options: ITerminalOptions): void; } -export interface ILinkifier { - linkifyRow(rowIndex: number): void; - attachHypertextLinkHandler(handler: LinkMatcherHandler): 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; } @@ -243,11 +253,11 @@ export interface ILinkMatcherOptions { /** * A callback that fires when the mouse hovers over a link. */ - hoverStartCallback?: LinkMatcherHandler; + tooltipCallback?: LinkMatcherHandler; /** * A callback that fires when the mouse leaves a link that was hovered. */ - hoverEndCallback?: () => void; + 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 diff --git a/src/Linkifier.ts b/src/Linkifier.ts index 2adb6f63..55d8d68d 100644 --- a/src/Linkifier.ts +++ b/src/Linkifier.ts @@ -2,12 +2,11 @@ * @license MIT */ -import { ILinkMatcherOptions, ITerminal, IBufferAccessor } from './Interfaces'; -import { LinkMatcher, LinkMatcherHandler, LinkMatcherValidationCallback, LineData } from './Types'; +import { ILinkMatcherOptions, ITerminal, IBufferAccessor, ILinkifier, IElementAccessor } from './Interfaces'; +import { LinkMatcher, LinkMatcherHandler, LinkMatcherValidationCallback, LineData, LinkHoverEvent, LinkHoverEventTypes } from './Types'; import { IMouseZoneManager } from './input/Interfaces'; import { MouseZone } from './input/MouseZoneManager'; - -const INVALID_LINK_CLASS = 'xterm-invalid-link'; +import { EventEmitter } from './EventEmitter'; const protocolClause = '(https?:\\/\\/)'; const domainCharacterSet = '[\\da-z\\.-]+'; @@ -36,7 +35,7 @@ const HYPERTEXT_LINK_MATCHER_ID = 0; /** * The Linkifier applies links to rows shortly after they have been refreshed. */ -export class Linkifier { +export class Linkifier extends EventEmitter implements ILinkifier { /** * The time to wait after a row is changed before it is linkified. This prevents * the costly operation of searching every row multiple times, potentially a @@ -51,8 +50,9 @@ export class Linkifier { private _nextLinkMatcherId = HYPERTEXT_LINK_MATCHER_ID; constructor( - private _terminal: IBufferAccessor + private _terminal: IBufferAccessor & IElementAccessor ) { + super(); this.registerLinkMatcher(strictUrlRegex, null, { matchIndex: 1 }); } @@ -133,8 +133,8 @@ export class Linkifier { handler, matchIndex: options.matchIndex, validationCallback: options.validationCallback, - hoverStartCallback: options.hoverStartCallback, - hoverEndCallback: options.hoverEndCallback, + hoverTooltipCallback: options.tooltipCallback, + hoverLeaveCallback: options.leaveCallback, priority: options.priority || 0 }; this._addLinkMatcherToList(matcher); @@ -260,13 +260,20 @@ export class Linkifier { window.open(uri, '_blank'); }, e => { - if (matcher.hoverStartCallback) { - matcher.hoverStartCallback(e, uri); + this.emit(LinkHoverEventTypes.HOVER, { x, y, length: uri.length}); + this._terminal.element.style.cursor = 'pointer'; + }, + e => { + this.emit(LinkHoverEventTypes.TOOLTIP, { x, y, length: uri.length}); + if (matcher.hoverTooltipCallback) { + matcher.hoverTooltipCallback(e, uri); } }, () => { - if (matcher.hoverEndCallback) { - matcher.hoverEndCallback(); + this.emit(LinkHoverEventTypes.LEAVE, { x, y, length: uri.length}); + this._terminal.element.style.cursor = ''; + if (matcher.hoverLeaveCallback) { + matcher.hoverLeaveCallback(); } } )); diff --git a/src/Terminal.ts b/src/Terminal.ts index d42978f7..1a180cc1 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -38,7 +38,7 @@ import * as Mouse from './utils/Mouse'; import { CHARSETS } from './Charsets'; import { getRawByteCoords } from './utils/Mouse'; import { CustomKeyEventHandler, Charset, LinkMatcherHandler, LinkMatcherValidationCallback, CharData, LineData } from './Types'; -import { ITerminal, IBrowser, ITerminalOptions, IInputHandlingTerminal, ILinkMatcherOptions, IViewport, ICompositionHelper, ITheme } from './Interfaces'; +import { ITerminal, IBrowser, ITerminalOptions, IInputHandlingTerminal, ILinkMatcherOptions, IViewport, ICompositionHelper, ITheme, ILinkifier } from './Interfaces'; import { BellSound } from './utils/Sounds'; import { DEFAULT_ANSI_COLORS } from './renderer/ColorManager'; import { IMouseZoneManager } from './input/Interfaces'; @@ -188,7 +188,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT private parser: Parser; private renderer: IRenderer; public selectionManager: SelectionManager; - private linkifier: Linkifier; + public linkifier: ILinkifier; public buffers: BufferSet; public buffer: Buffer; public viewport: IViewport; diff --git a/src/Types.ts b/src/Types.ts index 1af21e78..06d49502 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -6,8 +6,8 @@ export type LinkMatcher = { id: number, regex: RegExp, handler: LinkMatcherHandler, - hoverStartCallback?: LinkMatcherHandler, - hoverEndCallback?: () => void, + hoverTooltipCallback?: LinkMatcherHandler, + hoverLeaveCallback?: () => void, matchIndex?: number, validationCallback?: LinkMatcherValidationCallback, priority?: number @@ -20,3 +20,15 @@ export type Charset = {[key: string]: string}; export type CharData = [number, string, number, number]; export type LineData = CharData[]; + +export type LinkHoverEvent = { + x: number, + y: number, + length: number +}; + +export enum LinkHoverEventTypes { + HOVER = 'linkhover', + TOOLTIP = 'linktooltip', + LEAVE = 'linkleave' +}; diff --git a/src/input/Interfaces.ts b/src/input/Interfaces.ts index 8756e540..19125d08 100644 --- a/src/input/Interfaces.ts +++ b/src/input/Interfaces.ts @@ -8,6 +8,7 @@ export interface IMouseZone { x2: number; y: number; clickCallback: (e: MouseEvent) => any; - hoverStartCallback?: (e: MouseEvent) => any; - hoverEndCallback?: () => any; + hoverCallback?: (e: MouseEvent) => any; + tooltipCallback?: (e: MouseEvent) => any; + leaveCallback?: () => any; } diff --git a/src/input/MouseZoneManager.ts b/src/input/MouseZoneManager.ts index 7789082e..1a6cedad 100644 --- a/src/input/MouseZoneManager.ts +++ b/src/input/MouseZoneManager.ts @@ -20,7 +20,7 @@ export class MouseZoneManager implements IMouseZoneManager { private _mouseDownListener: (e: MouseEvent) => any; private _clickListener: (e: MouseEvent) => any; - private _hoverTimeout: number = null; + private _tooltipTimeout: number = null; private _currentZone: IMouseZone = null; private _lastHoverCoords: [number, number] = [null, null]; @@ -63,30 +63,49 @@ export class MouseZoneManager implements IMouseZoneManager { private _onMouseMove(e: MouseEvent): void { // TODO: Ideally this would only clear the hover state when the mouse moves // outside of the mouse zone - if (this._lastHoverCoords[0] !== e.pageX && this._lastHoverCoords[1] !== e.pageY) { - // Restart the timeout - if (this._hoverTimeout) { - clearTimeout(this._hoverTimeout); - } - this._hoverTimeout = setTimeout(() => this._onHover(e), HOVER_DURATION); - - // Fire the hover end callback if a zone was being hovered - if (this._currentZone) { - this._currentZone.hoverEndCallback(); - this._currentZone = null; - } - + if (this._lastHoverCoords[0] !== e.pageX || this._lastHoverCoords[1] !== e.pageY) { + this._onHover(e); // Record the current coordinates this._lastHoverCoords = [e.pageX, e.pageY]; } } private _onHover(e: MouseEvent): void { - const coords = getCoords(e, this._terminal.element, this._terminal.charMeasure, this._terminal.options.lineHeight, this._terminal.cols, this._terminal.rows); const zone = this._findZoneEventAt(e); - if (zone && zone.hoverStartCallback) { - this._currentZone = zone; - zone.hoverStartCallback(e); + + // Do nothing if the zone is the same + if (zone === this._currentZone) { + return; + } + + // Fire the hover end callback if a zone was being hovered + if (this._currentZone) { + this._currentZone.leaveCallback(); + this._currentZone = null; + } + + // Exit if there is not zone + if (!zone) { + return; + } + this._currentZone = zone; + + // Trigger the hover callback + if (zone.hoverCallback) { + zone.hoverCallback(e); + } + + // Restart the timeout + if (this._tooltipTimeout) { + clearTimeout(this._tooltipTimeout); + } + this._tooltipTimeout = setTimeout(() => this._onTooltip(e), HOVER_DURATION); + } + + private _onTooltip(e: MouseEvent): void { + const zone = this._findZoneEventAt(e); + if (zone && zone.tooltipCallback) { + zone.tooltipCallback(e); } } @@ -116,8 +135,9 @@ export class MouseZone implements IMouseZone { public x2: number, public y: number, public clickCallback: (e: MouseEvent) => any, - public hoverStartCallback?: (e: MouseEvent) => any, - public hoverEndCallback?: () => void + public hoverCallback?: (e: MouseEvent) => any, + public tooltipCallback?: (e: MouseEvent) => any, + public leaveCallback?: () => void ) { } } diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 0e657ae7..4efc5481 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -109,11 +109,11 @@ export abstract class BaseRenderLayer implements IRenderLayer { * @param x The column to fill. * @param y The row to fill. */ - protected fillBottomLineAtCell(x: number, y: number): void { + protected fillBottomLineAtCells(x: number, y: number, width: number = 1): void { this._ctx.fillRect( x * this.scaledCharWidth, (y + 1) * this.scaledLineHeight - window.devicePixelRatio - 1 /* Ensure it's drawn within the cell */, - this.scaledCharWidth, + width * this.scaledCharWidth, window.devicePixelRatio); } diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index 9f5136ff..a888d54f 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -202,7 +202,7 @@ export class CursorRenderLayer extends BaseRenderLayer { private _renderUnderlineCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void { this._ctx.save(); this._ctx.fillStyle = this.colors.cursor; - this.fillBottomLineAtCell(x, y); + this.fillBottomLineAtCells(x, y); this._ctx.restore(); } diff --git a/src/renderer/ForegroundRenderLayer.ts b/src/renderer/ForegroundRenderLayer.ts index 05141aed..bef57a00 100644 --- a/src/renderer/ForegroundRenderLayer.ts +++ b/src/renderer/ForegroundRenderLayer.ts @@ -139,7 +139,7 @@ export class ForegroundRenderLayer extends BaseRenderLayer { } else { this._ctx.fillStyle = this.colors.foreground; } - this.fillBottomLineAtCell(x, y); + this.fillBottomLineAtCells(x, y); } this.drawChar(terminal, char, code, width, x, y, fg, !!(flags & FLAGS.BOLD)); diff --git a/src/renderer/LinkRenderLayer.ts b/src/renderer/LinkRenderLayer.ts new file mode 100644 index 00000000..b948b70d --- /dev/null +++ b/src/renderer/LinkRenderLayer.ts @@ -0,0 +1,44 @@ +import { IColorSet } from './Interfaces'; +import { IBuffer, ICharMeasure, ITerminal, ILinkifierAccessor } from '../Interfaces'; +import { CHAR_DATA_ATTR_INDEX } from '../Buffer'; +import { GridCache } from './GridCache'; +import { FLAGS } from './Types'; +import { BaseRenderLayer, INVERTED_DEFAULT_COLOR } from './BaseRenderLayer'; +import { LinkHoverEvent, LinkHoverEventTypes } from '../Types'; + +export class LinkRenderLayer extends BaseRenderLayer { + private _state: LinkHoverEvent = null; + + constructor(container: HTMLElement, zIndex: number, colors: IColorSet, terminal: ILinkifierAccessor) { + super(container, 'link', zIndex, colors); + terminal.linkifier.on(LinkHoverEventTypes.HOVER, (e: LinkHoverEvent) => this._onLinkHover(e)); + terminal.linkifier.on(LinkHoverEventTypes.LEAVE, (e: LinkHoverEvent) => this._onLinkLeave(e)); + } + + public resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void { + super.resize(terminal, canvasWidth, canvasHeight, charSizeChanged); + // Resizing the canvas discards the contents of the canvas so clear state + this._state = null; + } + + public reset(terminal: ITerminal): void { + this._clearCurrentLink(); + } + + private _clearCurrentLink(): void { + if (this._state) { + this.clearCells(this._state.x, this._state.y, this._state.length, 1); + this._state = null; + } + } + + private _onLinkHover(e: LinkHoverEvent): void { + this._ctx.fillStyle = this.colors.foreground; + this.fillBottomLineAtCells(e.x, e.y, e.length); + this._state = e; + } + + private _onLinkLeave(e: LinkHoverEvent): void { + this._clearCurrentLink(); + } +} diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index c032bc8f..ebdd0bdb 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -11,6 +11,7 @@ import { CursorRenderLayer } from './CursorRenderLayer'; import { ColorManager } from './ColorManager'; import { BaseRenderLayer } from './BaseRenderLayer'; import { IRenderLayer, IColorSet, IRenderer } from './Interfaces'; +import { LinkRenderLayer } from './LinkRenderLayer'; export class Renderer implements IRenderer { /** A queue of the rows to be refreshed */ @@ -28,7 +29,8 @@ export class Renderer implements IRenderer { new BackgroundRenderLayer(this._terminal.element, 0, this._colorManager.colors), new SelectionRenderLayer(this._terminal.element, 1, this._colorManager.colors), new ForegroundRenderLayer(this._terminal.element, 2, this._colorManager.colors), - new CursorRenderLayer(this._terminal.element, 3, this._colorManager.colors) + new LinkRenderLayer(this._terminal.element, 3, this._colorManager.colors, this._terminal), + new CursorRenderLayer(this._terminal.element, 4, this._colorManager.colors) ]; this._devicePixelRatio = window.devicePixelRatio; } diff --git a/src/utils/TestUtils.test.ts b/src/utils/TestUtils.test.ts index 652c3aa3..e229fc87 100644 --- a/src/utils/TestUtils.test.ts +++ b/src/utils/TestUtils.test.ts @@ -2,13 +2,14 @@ * @license MIT */ -import { ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager, ITerminalOptions, IListenerType, IInputHandlingTerminal, IViewport, ICircularList, ICompositionHelper, ITheme } from '../Interfaces'; +import { ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager, ITerminalOptions, IListenerType, IInputHandlingTerminal, IViewport, ICircularList, ICompositionHelper, ITheme, ILinkifier } from '../Interfaces'; import { LineData } from '../Types'; import { Buffer } from '../Buffer'; import * as Browser from './Browser'; import { IColorSet, IRenderer } from '../renderer/Interfaces'; export class MockTerminal implements ITerminal { + linkifier: ILinkifier; isFocused: boolean; options: ITerminalOptions = {}; element: HTMLElement; diff --git a/src/xterm.css b/src/xterm.css index df3aff59..c26d459d 100644 --- a/src/xterm.css +++ b/src/xterm.css @@ -120,5 +120,5 @@ } .terminal:not(.enable-mouse-events) { - cursor: text; + cursor: text; } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index b219732a..7a4d6940 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -141,14 +141,15 @@ interface ILinkMatcherOptions { validationCallback?: (uri: string, callback: (isValid: boolean) => void) => void; /** - * A callback that fires when the mouse hovers over a link. + * A callback that fires when the mouse hovers over a link for a moment. */ - hoverStartCallback?: (event: MouseEvent, uri: string) => boolean | void; + tooltipCallback?: (event: MouseEvent, uri: string) => boolean | void; /** - * A callback that fires when the mouse leaves a link that was hovered. + * A callback that fires when the mouse leaves a link. Note that this can + * happen even when tooltipCallback hasn't fired for the link yet. */ - hoverEndCallback?: (event: MouseEvent, uri: string) => boolean | void; + leaveCallback?: (event: MouseEvent, uri: string) => boolean | void; /** * The priority of the link matcher, this defines the order in which the link From e3051ea1dc82717df1f31dee2d0bc8dabcb29d3f Mon Sep 17 00:00:00 2001 From: Marc Dumais Date: Fri, 8 Sep 2017 11:20:01 -0400 Subject: [PATCH 215/378] Add Theia as a "Real World" use of xterm.js --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index edfa1169..41a38bcc 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,8 @@ Xterm.js is used in several world-class applications to provide great terminal e - [**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 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. + 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 75b91c618e880845d984fba1eb1e492ac38596d1 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 8 Sep 2017 07:50:43 -0700 Subject: [PATCH 216/378] Update license text --- src/Buffer.test.ts | 1 + src/Buffer.ts | 1 + src/BufferSet.test.ts | 1 + src/BufferSet.ts | 1 + src/Charsets.ts | 1 + src/CompositionHelper.test.ts | 1 + src/CompositionHelper.ts | 1 + src/EscapeSequences.ts | 1 + src/EventEmitter.test.ts | 1 + src/EventEmitter.ts | 1 + src/InputHandler.test.ts | 1 + src/InputHandler.ts | 2 ++ src/Interfaces.ts | 1 + src/Linkifier.test.ts | 1 + src/Linkifier.ts | 1 + src/Parser.ts | 2 ++ src/SelectionManager.test.ts | 1 + src/SelectionManager.ts | 1 + src/SelectionModel.test.ts | 1 + src/SelectionModel.ts | 1 + src/Terminal.integration.ts | 1 + src/Terminal.test.ts | 7 ++++++- src/Terminal.ts | 6 ++++-- src/Types.ts | 1 + src/Viewport.test.ts | 1 + src/Viewport.ts | 1 + src/addons/attach/attach.js | 5 +++-- src/addons/fit/fit.js | 14 ++++++++------ src/addons/fullscreen/fullscreen.js | 4 ++-- src/addons/search/SearchHelper.ts | 1 + src/addons/search/search.ts | 1 + src/addons/terminado/terminado.js | 7 ++++--- src/handlers/Clipboard.test.ts | 5 +++++ src/handlers/Clipboard.ts | 4 +--- src/input/Interfaces.ts | 5 +++++ src/input/MouseZoneManager.ts | 5 +++++ src/renderer/BackgroundRenderLayer.ts | 5 +++++ src/renderer/BaseRenderLayer.ts | 5 +++++ src/renderer/CharAtlas.ts | 5 +++++ src/renderer/ColorManager.test.ts | 1 + src/renderer/ColorManager.ts | 5 +++++ src/renderer/CursorRenderLayer.ts | 5 +++++ src/renderer/ForegroundRenderLayer.ts | 5 +++++ src/renderer/GridCache.test.ts | 1 + src/renderer/GridCache.ts | 5 +++++ src/renderer/Interfaces.ts | 5 +++++ src/renderer/LinkRenderLayer.ts | 5 +++++ src/renderer/Renderer.ts | 1 + src/renderer/SelectionRenderLayer.ts | 5 +++++ src/renderer/Types.ts | 5 +++++ src/utils/Browser.ts | 3 +-- src/utils/CharMeasure.test.ts | 1 + src/utils/CharMeasure.ts | 2 +- src/utils/CircularList.test.ts | 1 + src/utils/CircularList.ts | 8 +++++--- src/utils/Generic.ts | 3 +-- src/utils/Mouse.test.ts | 1 + src/utils/Mouse.ts | 1 + src/utils/Sounds.ts | 1 + src/utils/TestUtils.test.ts | 1 + src/xterm.css | 4 ++-- src/xterm.ts | 1 + 62 files changed, 146 insertions(+), 29 deletions(-) diff --git a/src/Buffer.test.ts b/src/Buffer.test.ts index 6baa230d..210d2971 100644 --- a/src/Buffer.test.ts +++ b/src/Buffer.test.ts @@ -1,4 +1,5 @@ /** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. * @license MIT */ diff --git a/src/Buffer.ts b/src/Buffer.ts index 65b7d58e..2d23980d 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -1,4 +1,5 @@ /** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. * @license MIT */ diff --git a/src/BufferSet.test.ts b/src/BufferSet.test.ts index 320241ca..b9c1824d 100644 --- a/src/BufferSet.test.ts +++ b/src/BufferSet.test.ts @@ -1,4 +1,5 @@ /** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. * @license MIT */ diff --git a/src/BufferSet.ts b/src/BufferSet.ts index 64b364e9..e31d2278 100644 --- a/src/BufferSet.ts +++ b/src/BufferSet.ts @@ -1,4 +1,5 @@ /** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. * @license MIT */ diff --git a/src/Charsets.ts b/src/Charsets.ts index c2c92e8d..0ee91d32 100644 --- a/src/Charsets.ts +++ b/src/Charsets.ts @@ -1,4 +1,5 @@ /** + * Copyright (c) 2016 The xterm.js authors. All rights reserved. * @license MIT */ diff --git a/src/CompositionHelper.test.ts b/src/CompositionHelper.test.ts index 7d78a259..09a59f72 100644 --- a/src/CompositionHelper.test.ts +++ b/src/CompositionHelper.test.ts @@ -1,4 +1,5 @@ /** + * Copyright (c) 2016 The xterm.js authors. All rights reserved. * @license MIT */ diff --git a/src/CompositionHelper.ts b/src/CompositionHelper.ts index 345041d6..cec8fb37 100644 --- a/src/CompositionHelper.ts +++ b/src/CompositionHelper.ts @@ -1,4 +1,5 @@ /** + * Copyright (c) 2016 The xterm.js authors. All rights reserved. * @license MIT */ diff --git a/src/EscapeSequences.ts b/src/EscapeSequences.ts index 9542658f..cb97c368 100644 --- a/src/EscapeSequences.ts +++ b/src/EscapeSequences.ts @@ -1,4 +1,5 @@ /** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. * @license MIT */ diff --git a/src/EventEmitter.test.ts b/src/EventEmitter.test.ts index d61f994a..c1f0a0ab 100644 --- a/src/EventEmitter.test.ts +++ b/src/EventEmitter.test.ts @@ -1,4 +1,5 @@ /** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. * @license MIT */ diff --git a/src/EventEmitter.ts b/src/EventEmitter.ts index a1768d2d..414eac89 100644 --- a/src/EventEmitter.ts +++ b/src/EventEmitter.ts @@ -1,4 +1,5 @@ /** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. * @license MIT */ diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index f6e07f7e..a8b2ff12 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -1,4 +1,5 @@ /** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. * @license MIT */ diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 3bf16a1c..88a86a44 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -1,4 +1,6 @@ /** + * Copyright (c) 2014 The xterm.js authors. All rights reserved. + * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) * @license MIT */ diff --git a/src/Interfaces.ts b/src/Interfaces.ts index b7779688..d022dc3e 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -1,4 +1,5 @@ /** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. * @license MIT */ diff --git a/src/Linkifier.test.ts b/src/Linkifier.test.ts index f165590f..17793fca 100644 --- a/src/Linkifier.test.ts +++ b/src/Linkifier.test.ts @@ -1,4 +1,5 @@ /** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. * @license MIT */ diff --git a/src/Linkifier.ts b/src/Linkifier.ts index 55d8d68d..d3839cb5 100644 --- a/src/Linkifier.ts +++ b/src/Linkifier.ts @@ -1,4 +1,5 @@ /** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. * @license MIT */ diff --git a/src/Parser.ts b/src/Parser.ts index b047f91f..b6493fea 100644 --- a/src/Parser.ts +++ b/src/Parser.ts @@ -1,4 +1,6 @@ /** + * Copyright (c) 2014 The xterm.js authors. All rights reserved. + * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) * @license MIT */ diff --git a/src/SelectionManager.test.ts b/src/SelectionManager.test.ts index 8c90693e..e5bdc117 100644 --- a/src/SelectionManager.test.ts +++ b/src/SelectionManager.test.ts @@ -1,4 +1,5 @@ /** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. * @license MIT */ diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 4fe09c8c..29f12169 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -1,4 +1,5 @@ /** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. * @license MIT */ diff --git a/src/SelectionModel.test.ts b/src/SelectionModel.test.ts index 6c750ed2..e195d6bd 100644 --- a/src/SelectionModel.test.ts +++ b/src/SelectionModel.test.ts @@ -1,4 +1,5 @@ /** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. * @license MIT */ diff --git a/src/SelectionModel.ts b/src/SelectionModel.ts index 8c599fd7..1fbf40fe 100644 --- a/src/SelectionModel.ts +++ b/src/SelectionModel.ts @@ -1,4 +1,5 @@ /** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. * @license MIT */ diff --git a/src/Terminal.integration.ts b/src/Terminal.integration.ts index 124e89cb..00db4dd6 100644 --- a/src/Terminal.integration.ts +++ b/src/Terminal.integration.ts @@ -1,4 +1,5 @@ /** + * Copyright (c) 2016 The xterm.js authors. All rights reserved. * @license MIT * * This file contains integration tests for xterm.js. diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index 7a44ee81..fd173831 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -1,4 +1,9 @@ -import { assert, expect } from 'chai'; +/** + * Copyright (c) 2016 The xterm.js authors. All rights reserved. + * @license MIT + */ + + import { assert, expect } from 'chai'; import { Terminal } from './Terminal'; import { MockViewport, MockCompositionHelper, MockRenderer } from './utils/TestUtils.test'; import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from './Buffer'; diff --git a/src/Terminal.ts b/src/Terminal.ts index 1a180cc1..e19887cb 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1,5 +1,8 @@ /** - * xterm.js: xterm, in the browser + * Copyright (c) 2014 The xterm.js authors. All rights reserved. + * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) + * @license MIT + * * Originally forked from (with the author's permission): * Fabrice Bellard's javascript vt100 for jslinux: * http://bellard.org/jslinux/ @@ -7,7 +10,6 @@ * The original design remains. The terminal itself * has been extended to include xterm CSI codes, among * other features. - * @license MIT * * Terminal Emulation References: * http://vt100.net/ diff --git a/src/Types.ts b/src/Types.ts index 06d49502..a0c03fdd 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -1,4 +1,5 @@ /** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. * @license MIT */ diff --git a/src/Viewport.test.ts b/src/Viewport.test.ts index 6d23e19a..6ce798d3 100644 --- a/src/Viewport.test.ts +++ b/src/Viewport.test.ts @@ -1,4 +1,5 @@ /** + * Copyright (c) 2016 The xterm.js authors. All rights reserved. * @license MIT */ diff --git a/src/Viewport.ts b/src/Viewport.ts index 2de698a0..1f693cfe 100644 --- a/src/Viewport.ts +++ b/src/Viewport.ts @@ -1,4 +1,5 @@ /** + * Copyright (c) 2016 The xterm.js authors. All rights reserved. * @license MIT */ diff --git a/src/addons/attach/attach.js b/src/addons/attach/attach.js index 34dc4f68..834ccd3c 100644 --- a/src/addons/attach/attach.js +++ b/src/addons/attach/attach.js @@ -1,7 +1,8 @@ /** - * Implements the attach method, that attaches the terminal to a WebSocket stream. - * @module xterm/addons/attach/attach + * Copyright (c) 2014 The xterm.js authors. All rights reserved. * @license MIT + * + * Implements the attach method, that attaches the terminal to a WebSocket stream. */ (function (attach) { diff --git a/src/addons/fit/fit.js b/src/addons/fit/fit.js index 60214dad..59f0fd52 100644 --- a/src/addons/fit/fit.js +++ b/src/addons/fit/fit.js @@ -1,14 +1,16 @@ /** + * Copyright (c) 2014 The xterm.js authors. All rights reserved. + * @license MIT + * * Fit terminal columns and rows to the dimensions of its DOM element. * * ## Approach - * - Rows: Truncate the division of the terminal parent element height by the terminal row height. * - * - Columns: Truncate the division of the terminal parent element width by the terminal character - * width (apply display: inline at the terminal row and truncate its width with the current - * number of columns). - * @module xterm/addons/fit/fit - * @license MIT + * Rows: Truncate the division of the terminal parent element height by the + * terminal row height. + * Columns: Truncate the division of the terminal parent element width by the + * terminal character width (apply display: inline at the terminal + * row and truncate its width with the current number of columns). */ (function (fit) { diff --git a/src/addons/fullscreen/fullscreen.js b/src/addons/fullscreen/fullscreen.js index 344669f6..1474d2db 100644 --- a/src/addons/fullscreen/fullscreen.js +++ b/src/addons/fullscreen/fullscreen.js @@ -1,8 +1,8 @@ /** - * Fullscreen addon for xterm.js - * @module xterm/addons/fullscreen/fullscreen + * Copyright (c) 2014 The xterm.js authors. All rights reserved. * @license MIT */ + (function (fullscreen) { if (typeof exports === 'object' && typeof module === 'object') { /* diff --git a/src/addons/search/SearchHelper.ts b/src/addons/search/SearchHelper.ts index 26e4e8a7..1747e747 100644 --- a/src/addons/search/SearchHelper.ts +++ b/src/addons/search/SearchHelper.ts @@ -1,4 +1,5 @@ /** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. * @license MIT */ diff --git a/src/addons/search/search.ts b/src/addons/search/search.ts index 34223c4e..69c5737b 100644 --- a/src/addons/search/search.ts +++ b/src/addons/search/search.ts @@ -1,4 +1,5 @@ /** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. * @license MIT */ diff --git a/src/addons/terminado/terminado.js b/src/addons/terminado/terminado.js index d1414c86..54af7633 100644 --- a/src/addons/terminado/terminado.js +++ b/src/addons/terminado/terminado.js @@ -1,8 +1,9 @@ /** - * This module provides methods for attaching a terminal to a terminado WebSocket stream. - * - * @module xterm/addons/terminado/terminado + * Copyright (c) 2016 The xterm.js authors. All rights reserved. * @license MIT + * + * This module provides methods for attaching a terminal to a terminado + * WebSocket stream. */ (function (attach) { diff --git a/src/handlers/Clipboard.test.ts b/src/handlers/Clipboard.test.ts index bd2cea03..6901b730 100644 --- a/src/handlers/Clipboard.test.ts +++ b/src/handlers/Clipboard.test.ts @@ -1,3 +1,8 @@ +/** + * Copyright (c) 2016 The xterm.js authors. All rights reserved. + * @license MIT + */ + import { assert } from 'chai'; import * as Terminal from '../Terminal'; import * as Clipboard from './Clipboard'; diff --git a/src/handlers/Clipboard.ts b/src/handlers/Clipboard.ts index ac9a7b87..a9735352 100644 --- a/src/handlers/Clipboard.ts +++ b/src/handlers/Clipboard.ts @@ -1,7 +1,5 @@ /** - * Clipboard handler module: exports methods for handling all clipboard-related events in the - * terminal. - * @module xterm/handlers/Clipboard + * Copyright (c) 2016 The xterm.js authors. All rights reserved. * @license MIT */ diff --git a/src/input/Interfaces.ts b/src/input/Interfaces.ts index 19125d08..e3ed4eef 100644 --- a/src/input/Interfaces.ts +++ b/src/input/Interfaces.ts @@ -1,3 +1,8 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + export interface IMouseZoneManager { add(zone: IMouseZone): void; clearAll(): void; diff --git a/src/input/MouseZoneManager.ts b/src/input/MouseZoneManager.ts index 1a6cedad..d1bc8a75 100644 --- a/src/input/MouseZoneManager.ts +++ b/src/input/MouseZoneManager.ts @@ -1,3 +1,8 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + import { IMouseZoneManager, IMouseZone } from './Interfaces'; import { ITerminal } from '../Interfaces'; import { getCoords } from '../utils/Mouse'; diff --git a/src/renderer/BackgroundRenderLayer.ts b/src/renderer/BackgroundRenderLayer.ts index 292847c2..0c09727d 100644 --- a/src/renderer/BackgroundRenderLayer.ts +++ b/src/renderer/BackgroundRenderLayer.ts @@ -1,3 +1,8 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + import { IColorSet } from './Interfaces'; import { IBuffer, ICharMeasure, ITerminal } from '../Interfaces'; import { CHAR_DATA_ATTR_INDEX } from '../Buffer'; diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 4efc5481..cc02bcea 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -1,3 +1,8 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + import { IRenderLayer, IColorSet } from './Interfaces'; import { ITerminal, ITerminalOptions } from '../Interfaces'; import { acquireCharAtlas, CHAR_ATLAS_CELL_SPACING } from './CharAtlas'; diff --git a/src/renderer/CharAtlas.ts b/src/renderer/CharAtlas.ts index 434b263b..bf080cce 100644 --- a/src/renderer/CharAtlas.ts +++ b/src/renderer/CharAtlas.ts @@ -1,3 +1,8 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + import { ITerminal, ITheme } from '../Interfaces'; import { IColorSet } from '../renderer/Interfaces'; import { isFirefox } from '../utils/Browser'; diff --git a/src/renderer/ColorManager.test.ts b/src/renderer/ColorManager.test.ts index 6bd9c977..e00f6b6d 100644 --- a/src/renderer/ColorManager.test.ts +++ b/src/renderer/ColorManager.test.ts @@ -1,4 +1,5 @@ /** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. * @license MIT */ diff --git a/src/renderer/ColorManager.ts b/src/renderer/ColorManager.ts index 057292f7..b9afd0e1 100644 --- a/src/renderer/ColorManager.ts +++ b/src/renderer/ColorManager.ts @@ -1,3 +1,8 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + import { IColorSet } from './Interfaces'; import { ITheme } from '../Interfaces'; diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index a888d54f..b136a13a 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -1,3 +1,8 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + import { IColorSet } from './Interfaces'; import { IBuffer, ICharMeasure, ITerminal, ITerminalOptions } from '../Interfaces'; import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX } from '../Buffer'; diff --git a/src/renderer/ForegroundRenderLayer.ts b/src/renderer/ForegroundRenderLayer.ts index bef57a00..883be5d9 100644 --- a/src/renderer/ForegroundRenderLayer.ts +++ b/src/renderer/ForegroundRenderLayer.ts @@ -1,3 +1,8 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + import { IColorSet } from './Interfaces'; import { IBuffer, ICharMeasure, ITerminal } from '../Interfaces'; import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from '../Buffer'; diff --git a/src/renderer/GridCache.test.ts b/src/renderer/GridCache.test.ts index 37e55ee1..c4b1c220 100644 --- a/src/renderer/GridCache.test.ts +++ b/src/renderer/GridCache.test.ts @@ -1,4 +1,5 @@ /** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. * @license MIT */ diff --git a/src/renderer/GridCache.ts b/src/renderer/GridCache.ts index 3b3ae8df..dd188c62 100644 --- a/src/renderer/GridCache.ts +++ b/src/renderer/GridCache.ts @@ -1,3 +1,8 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + export class GridCache { public cache: T[][]; diff --git a/src/renderer/Interfaces.ts b/src/renderer/Interfaces.ts index e6b09843..05f98300 100644 --- a/src/renderer/Interfaces.ts +++ b/src/renderer/Interfaces.ts @@ -1,3 +1,8 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + import { ITerminal, ITerminalOptions, ITheme } from '../Interfaces'; export interface IRenderer { diff --git a/src/renderer/LinkRenderLayer.ts b/src/renderer/LinkRenderLayer.ts index b948b70d..9b2cabb3 100644 --- a/src/renderer/LinkRenderLayer.ts +++ b/src/renderer/LinkRenderLayer.ts @@ -1,3 +1,8 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + import { IColorSet } from './Interfaces'; import { IBuffer, ICharMeasure, ITerminal, ILinkifierAccessor } from '../Interfaces'; import { CHAR_DATA_ATTR_INDEX } from '../Buffer'; diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index ebdd0bdb..131a8b78 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -1,4 +1,5 @@ /** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. * @license MIT */ diff --git a/src/renderer/SelectionRenderLayer.ts b/src/renderer/SelectionRenderLayer.ts index 91e62a0c..39569c9b 100644 --- a/src/renderer/SelectionRenderLayer.ts +++ b/src/renderer/SelectionRenderLayer.ts @@ -1,3 +1,8 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + import { IColorSet } from './Interfaces'; import { IBuffer, ICharMeasure, ITerminal } from '../Interfaces'; import { CHAR_DATA_ATTR_INDEX } from '../Buffer'; diff --git a/src/renderer/Types.ts b/src/renderer/Types.ts index 09e74439..b1a930f9 100644 --- a/src/renderer/Types.ts +++ b/src/renderer/Types.ts @@ -1,4 +1,9 @@ /** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + + /** * Flags used to render terminal text properly. */ export enum FLAGS { diff --git a/src/utils/Browser.ts b/src/utils/Browser.ts index 68148aeb..48c0c374 100644 --- a/src/utils/Browser.ts +++ b/src/utils/Browser.ts @@ -1,6 +1,5 @@ /** - * Attributes and methods to help with identifying the current browser and platform. - * @module xterm/utils/Browser + * Copyright (c) 2016 The xterm.js authors. All rights reserved. * @license MIT */ diff --git a/src/utils/CharMeasure.test.ts b/src/utils/CharMeasure.test.ts index 02a8dae9..5d2358f4 100644 --- a/src/utils/CharMeasure.test.ts +++ b/src/utils/CharMeasure.test.ts @@ -1,4 +1,5 @@ /** + * Copyright (c) 2016 The xterm.js authors. All rights reserved. * @license MIT */ diff --git a/src/utils/CharMeasure.ts b/src/utils/CharMeasure.ts index 37d6697a..e8fcff86 100644 --- a/src/utils/CharMeasure.ts +++ b/src/utils/CharMeasure.ts @@ -1,5 +1,5 @@ /** - * @module xterm/utils/CharMeasure + * Copyright (c) 2016 The xterm.js authors. All rights reserved. * @license MIT */ diff --git a/src/utils/CircularList.test.ts b/src/utils/CircularList.test.ts index f404b6f2..6bb35113 100644 --- a/src/utils/CircularList.test.ts +++ b/src/utils/CircularList.test.ts @@ -1,4 +1,5 @@ /** + * Copyright (c) 2016 The xterm.js authors. All rights reserved. * @license MIT */ diff --git a/src/utils/CircularList.ts b/src/utils/CircularList.ts index e188fb5d..97a32b79 100644 --- a/src/utils/CircularList.ts +++ b/src/utils/CircularList.ts @@ -1,13 +1,15 @@ /** - * Represents a circular list; a list with a maximum size that wraps around when push is called, - * overriding values at the start of the list. - * @module xterm/utils/CircularList + * Copyright (c) 2016 The xterm.js authors. All rights reserved. * @license MIT */ import { EventEmitter } from '../EventEmitter'; import { ICircularList } from '../Interfaces'; +/** + * Represents a circular list; a list with a maximum size that wraps around when push is called, + * overriding values at the start of the list. + */ export class CircularList extends EventEmitter implements ICircularList { protected _array: T[]; private _startIndex: number; diff --git a/src/utils/Generic.ts b/src/utils/Generic.ts index 98072326..82373617 100644 --- a/src/utils/Generic.ts +++ b/src/utils/Generic.ts @@ -1,6 +1,5 @@ /** - * Generic utilities module with methods that can be helpful at different parts of the code base. - * @module xterm/utils/Generic + * Copyright (c) 2016 The xterm.js authors. All rights reserved. * @license MIT */ diff --git a/src/utils/Mouse.test.ts b/src/utils/Mouse.test.ts index 15b68ef2..4163263b 100644 --- a/src/utils/Mouse.test.ts +++ b/src/utils/Mouse.test.ts @@ -1,4 +1,5 @@ /** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. * @license MIT */ diff --git a/src/utils/Mouse.ts b/src/utils/Mouse.ts index 3bbad855..aef76d5b 100644 --- a/src/utils/Mouse.ts +++ b/src/utils/Mouse.ts @@ -1,4 +1,5 @@ /** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. * @license MIT */ diff --git a/src/utils/Sounds.ts b/src/utils/Sounds.ts index 6d20c671..03f36f42 100644 --- a/src/utils/Sounds.ts +++ b/src/utils/Sounds.ts @@ -1,4 +1,5 @@ /** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. * @license MIT */ diff --git a/src/utils/TestUtils.test.ts b/src/utils/TestUtils.test.ts index e229fc87..eb2f2501 100644 --- a/src/utils/TestUtils.test.ts +++ b/src/utils/TestUtils.test.ts @@ -1,4 +1,5 @@ /** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. * @license MIT */ diff --git a/src/xterm.css b/src/xterm.css index c26d459d..399f56ce 100644 --- a/src/xterm.css +++ b/src/xterm.css @@ -1,8 +1,8 @@ /** - * xterm.js: xterm, in the browser - * Copyright (c) 2014-2016, SourceLair Private Company (www.sourcelair.com (MIT License) + * Copyright (c) 2014 The xterm.js authors. All rights reserved. * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) * https://github.com/chjj/term.js + * @license MIT * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/src/xterm.ts b/src/xterm.ts index 50ac5592..6df3a4c0 100644 --- a/src/xterm.ts +++ b/src/xterm.ts @@ -1,4 +1,5 @@ /** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. * @license MIT * * This file is the entry point for browserify. From 0c19bc9c2812b4564b554e048a993598622b6d67 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 8 Sep 2017 11:06:15 -0700 Subject: [PATCH 217/378] Fix tests --- fixtures/typings-test/typings-test.ts | 5 +++-- src/Linkifier.test.ts | 27 +++++++++++++++------------ src/Linkifier.ts | 2 +- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/fixtures/typings-test/typings-test.ts b/fixtures/typings-test/typings-test.ts index df79d2d0..928ca238 100644 --- a/fixtures/typings-test/typings-test.ts +++ b/fixtures/typings-test/typings-test.ts @@ -217,9 +217,10 @@ namespace methods_experimental { validationCallback: (uri: string, callback: (isValid: boolean) => void) => { console.log(uri, callback); }, - hoverCallback: (e: MouseEvent, uri: string) => { + tooltipCallback: (e: MouseEvent, uri: string) => { console.log(e, uri); - } + }, + leaveCallback: () => {} }); t.deregisterLinkMatcher(1); } diff --git a/src/Linkifier.test.ts b/src/Linkifier.test.ts index 17793fca..df9e459c 100644 --- a/src/Linkifier.test.ts +++ b/src/Linkifier.test.ts @@ -4,7 +4,7 @@ */ import { assert } from 'chai'; -import { ITerminal, ILinkifier, IBuffer, IBufferAccessor } from './Interfaces'; +import { ITerminal, ILinkifier, IBuffer, IBufferAccessor, IElementAccessor } from './Interfaces'; import { Linkifier } from './Linkifier'; import { LinkMatcher, LineData } from './Types'; import { IMouseZoneManager, IMouseZone } from './input/Interfaces'; @@ -12,13 +12,13 @@ import { MockBuffer } from './utils/TestUtils.test'; import { CircularList } from './utils/CircularList'; class TestLinkifier extends Linkifier { - constructor(private _bufferAccessor: IBufferAccessor) { - super(_bufferAccessor); + constructor(_terminal: IBufferAccessor & IElementAccessor) { + super(_terminal); Linkifier.TIME_BEFORE_LINKIFY = 0; } public get linkMatchers(): LinkMatcher[] { return this._linkMatchers; } - public linkifyRows(): void { super.linkifyRows(0, this._bufferAccessor.buffer.lines.length - 1); } + public linkifyRows(): void { super.linkifyRows(0, this._terminal.buffer.lines.length - 1); } } class TestMouseZoneManager implements IMouseZoneManager { @@ -33,15 +33,18 @@ class TestMouseZoneManager implements IMouseZoneManager { } describe('Linkifier', () => { - let bufferAccessor: IBufferAccessor; + let terminal: IBufferAccessor & IElementAccessor; let linkifier: TestLinkifier; let mouseZoneManager: TestMouseZoneManager; beforeEach(() => { - bufferAccessor = { buffer: new MockBuffer() }; - bufferAccessor.buffer.lines = new CircularList(20); - bufferAccessor.buffer.ydisp = 0; - linkifier = new TestLinkifier(bufferAccessor); + terminal = { + buffer: new MockBuffer(), + element: {} + }; + terminal.buffer.lines = new CircularList(20); + terminal.buffer.ydisp = 0; + linkifier = new TestLinkifier(terminal); mouseZoneManager = new TestMouseZoneManager(); }); @@ -54,7 +57,7 @@ describe('Linkifier', () => { } function addRow(text: string): void { - bufferAccessor.buffer.lines.push(stringToRow(text)); + terminal.buffer.lines.push(stringToRow(text)); } function assertLinkifiesEntireRow(uri: string, done: MochaDone): void { @@ -63,7 +66,7 @@ describe('Linkifier', () => { setTimeout(() => { assert.equal(mouseZoneManager.zones[0].x1, 1); assert.equal(mouseZoneManager.zones[0].x2, uri.length + 1); - assert.equal(mouseZoneManager.zones[0].y, bufferAccessor.buffer.lines.length); + assert.equal(mouseZoneManager.zones[0].y, terminal.buffer.lines.length); done(); }, 0); } @@ -78,7 +81,7 @@ describe('Linkifier', () => { links.forEach((l, i) => { assert.equal(mouseZoneManager.zones[i].x1, l.x + 1); assert.equal(mouseZoneManager.zones[i].x2, l.x + l.length + 1); - assert.equal(mouseZoneManager.zones[i].y, bufferAccessor.buffer.lines.length); + assert.equal(mouseZoneManager.zones[i].y, terminal.buffer.lines.length); }); done(); }, 0); diff --git a/src/Linkifier.ts b/src/Linkifier.ts index d3839cb5..94795429 100644 --- a/src/Linkifier.ts +++ b/src/Linkifier.ts @@ -51,7 +51,7 @@ export class Linkifier extends EventEmitter implements ILinkifier { private _nextLinkMatcherId = HYPERTEXT_LINK_MATCHER_ID; constructor( - private _terminal: IBufferAccessor & IElementAccessor + protected _terminal: IBufferAccessor & IElementAccessor ) { super(); this.registerLinkMatcher(strictUrlRegex, null, { matchIndex: 1 }); From 0e3518409e8d2ec20b2e3fbc24bef8ebb4b34990 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 9 Sep 2017 19:06:30 -0700 Subject: [PATCH 218/378] Fix issues with link state - Scrolling will now clear the link renderer - Refreshing a single line will now recalculate only that line Fixes #959 Fixes #960 --- src/Linkifier.ts | 31 +++++++++++++++++++++++-------- src/Terminal.ts | 8 ++++++-- src/input/Interfaces.ts | 2 +- src/input/MouseZoneManager.ts | 25 ++++++++++++++++++++++--- 4 files changed, 52 insertions(+), 14 deletions(-) diff --git a/src/Linkifier.ts b/src/Linkifier.ts index 94795429..fa47271c 100644 --- a/src/Linkifier.ts +++ b/src/Linkifier.ts @@ -49,11 +49,16 @@ export class Linkifier extends EventEmitter implements ILinkifier { private _mouseZoneManager: IMouseZoneManager; private _rowsTimeoutId: number; private _nextLinkMatcherId = HYPERTEXT_LINK_MATCHER_ID; + private _rowsToLinkify: {start: number, end: number}; constructor( protected _terminal: IBufferAccessor & IElementAccessor ) { super(); + this._rowsToLinkify = { + start: null, + end: null + }; this.registerLinkMatcher(strictUrlRegex, null, { matchIndex: 1 }); } @@ -76,25 +81,35 @@ export class Linkifier extends EventEmitter implements ILinkifier { return; } - // Clear out any existing links - this._mouseZoneManager.clearAll(); + // Increase range to linkify + if (!this._rowsToLinkify.start) { + this._rowsToLinkify.start = start; + this._rowsToLinkify.end = end; + } else { + this._rowsToLinkify.start = this._rowsToLinkify.start < start ? this._rowsToLinkify.start : start; + this._rowsToLinkify.end = this._rowsToLinkify.end < end ? this._rowsToLinkify.end : end; + } + // Clear out any existing links on this row range + this._mouseZoneManager.clearAll(start, end); + + // Restart timer if (this._rowsTimeoutId) { clearTimeout(this._rowsTimeoutId); } - this._rowsTimeoutId = setTimeout(this._linkifyRows.bind(this, start, end), Linkifier.TIME_BEFORE_LINKIFY); + this._rowsTimeoutId = setTimeout(() => this._linkifyRows(), Linkifier.TIME_BEFORE_LINKIFY); } /** - * Linkifies - * @param start The row to start at. - * @param end The row to end at. + * Linkifies the rows requested. */ - private _linkifyRows(start: number, end: number): void { + private _linkifyRows(): void { this._rowsTimeoutId = null; - for (let i = start; i <= end; i++) { + for (let i = this._rowsToLinkify.start; i <= this._rowsToLinkify.end; i++) { this._linkifyRow(i); } + this._rowsToLinkify.start = null; + this._rowsToLinkify.end = null; } /** diff --git a/src/Terminal.ts b/src/Terminal.ts index e19887cb..817eed40 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -48,8 +48,11 @@ import { MouseZoneManager } from './input/MouseZoneManager'; import { initialize as initializeCharAtlas } from './renderer/CharAtlas'; import { IRenderer } from './renderer/Interfaces'; -// Declare for RequireJS in loadAddon +// Declares required for loadAddon +declare var exports: any; +declare var module: any; declare var define: any; +declare var require: any; // Let it work inside Node.js for automated testing purposes. const document = (typeof window !== 'undefined') ? window.document : null; @@ -588,6 +591,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.syncBellSound(); this._mouseZoneManager = new MouseZoneManager(this); + this.on('scroll', () => this._mouseZoneManager.clearAll()); this.linkifier.attachToDom(this._mouseZoneManager); // Create the container that will hold helpers like the textarea for @@ -1034,7 +1038,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT */ private queueLinkification(start: number, end: number): void { if (this.linkifier) { - this.linkifier.linkifyRows(0, this.rows); + this.linkifier.linkifyRows(start, end); } } diff --git a/src/input/Interfaces.ts b/src/input/Interfaces.ts index e3ed4eef..21514a53 100644 --- a/src/input/Interfaces.ts +++ b/src/input/Interfaces.ts @@ -5,7 +5,7 @@ export interface IMouseZoneManager { add(zone: IMouseZone): void; - clearAll(): void; + clearAll(start?: number, end?: number): void; } export interface IMouseZone { diff --git a/src/input/MouseZoneManager.ts b/src/input/MouseZoneManager.ts index d1bc8a75..f3ba90c1 100644 --- a/src/input/MouseZoneManager.ts +++ b/src/input/MouseZoneManager.ts @@ -44,9 +44,28 @@ export class MouseZoneManager implements IMouseZoneManager { } } - public clearAll(): void { - this._zones.length = 0; - this._deactivate(); + public clearAll(start?: number, end?: number): void { + // Exit if there's nothing to clear + if (this._zones.length === 0) { + return; + } + + // Iterate through zones and clear them out if they're within the range + for (let i = 0; i < this._zones.length; i++) { + const zone = this._zones[i]; + if (zone.y >= start && zone.y <= end) { + if (this._currentZone && this._currentZone === zone) { + this._currentZone.leaveCallback(); + this._currentZone = null; + } + this._zones.splice(i--, 1); + } + } + + // Deactivate the mouse zone manager if all the zones have been removed + if (this._zones.length === 0) { + this._deactivate(); + } } private _activate(): void { From e4425120870adc53acba0ad4cb472e0592d25f1d Mon Sep 17 00:00:00 2001 From: Felipe Gasper Date: Sat, 9 Sep 2017 23:12:01 -0400 Subject: [PATCH 219/378] trying to make it work --- .gitmodules | 3 + demo/app.js | 5 ++ demo/index.html | 48 +++++++++++++++ demo/main.js | 151 +++++++++++++++++++++++++++++++++++++++++++++++- demo/zmodemjs | 1 + 5 files changed, 206 insertions(+), 2 deletions(-) create mode 100644 .gitmodules create mode 160000 demo/zmodemjs diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..b4a5a6c1 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "zmodemjs"] + path = demo/zmodemjs + url = https://github.com/FGasper/zmodemjs.git diff --git a/demo/app.js b/demo/app.js index 8637dad8..089ccf6e 100644 --- a/demo/app.js +++ b/demo/app.js @@ -21,6 +21,10 @@ app.get('/main.js', function(req, res){ res.sendFile(__dirname + '/main.js'); }); +app.get('/zmodemjs/*.js', function(req, res){ + res.sendFile(__dirname + req.url); +}); + app.post('/terminals', function (req, res) { var cols = parseInt(req.query.cols), rows = parseInt(req.query.rows), @@ -59,6 +63,7 @@ app.ws('/terminals/:pid', function (ws, req) { ws.send(logs[term.pid]); term.on('data', function(data) { +console.log("From PTY: ", escape(data) ); try { ws.send(data); } catch (ex) { diff --git a/demo/index.html b/demo/index.html index daa60b6f..60d0b376 100644 --- a/demo/index.html +++ b/demo/index.html @@ -12,9 +12,57 @@ + + + + + + + + + +

xterm.js: xterm, in the browser

+ +
+ + + + + + + + + +
+

Actions

diff --git a/demo/main.js b/demo/main.js index f4fb0a2a..c1d4d4e9 100644 --- a/demo/main.js +++ b/demo/main.js @@ -90,7 +90,7 @@ function createTerminal() { socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/terminals/'; term.open(terminalContainer); - term.fit(); + //term.fit(); // fit is called within a setTimeout, cols and rows need this. setTimeout(() => { @@ -109,13 +109,160 @@ function createTerminal() { socket.onopen = runRealTerminal; socket.onclose = runFakeTerminal; socket.onerror = runFakeTerminal; + + socket.addEventListener("message", handleWSMessage); }); }); }, 0); } +var text_encoder = new TextEncoder(); +var zsentry = new Zmodem.Sentry(); + +var zsession; +function handleWSMessage(evt) { + var binary = text_encoder.encode(evt.data); + var input = Array.prototype.slice.call( new Uint8Array(binary) ); +console.log("from WS", evt.data, input, evt.data.charCodeAt( evt.data.length - 2 )); + + if (zsession) { + zsession.consume(input); + if (zsession.has_ended()) { + input = zsession.get_trailing_bytes(); + zsession = null; + } + } + else { + let termbytes; + [termbytes, zsession] = zsentry.parse(input); + input = termbytes; + + if (zsession) { + + zsession.set_sender( (octets) => { + socket.send( String.fromCharCode.apply(String, octets) ); + } ); + + var start_form = document.getElementById("zm_start"); + start_form.style.display = ""; + start_form.onsubmit = function(e) { + start_form.style.display = "none"; + + if (zsession.type === "receive") { + zsession.on("offer", function(xfer) { + _show_file_info(xfer); + + var offer_form = document.getElementById("zm_offer"); + offer_form.style.display = ""; + offer_form.onsubmit = function(e) { + offer_form.style.display = "none"; + + if (offer_form.zmaccept) { + var FILE_BUFFER = []; + xfer.on("input", (payload) => { + _update_progress(xfer); + FILE_BUFFER.push.apply(FILE_BUFFER, payload); + }); + xfer.accept().then( () => { + _download(xfer, FILE_BUFFER); + _hide_file_info(); + } ); + } + else { + _hide_file_info(); + xfer.skip(); + } + }; + } ); + + zsession.start(); + } + else { + var choose_form = document.getElementById("zm_choose"); + choose_form.style.display = ""; + choose_form.onsubmit = function(e) { + choose_form.style.display = "none"; + + var file_el = document.getElementById("zm_files"); + var batch = []; + var total_size = 0; + for (var f=0; f { + if (xfer === undefined) { + return promise_callback(); //skipped + } + + return new Promise( (res) => { + var reader = new FileReader(); + reader.onerror = function(e) { + console.error("file read error", e); + throw("File read error: " + e); + }; + + var offset = xfer.get_offset(); + reader.onprogress = function(e) { + xfer.send( + new Uint8Array(e.target.result, offset) + ); + offset = e.loaded; + }; + + reader.onload = function(e) { + xfer.end( + new Uint8Array(e.target.result, offset) + ).then(res).then(promise_callback); + }; + + reader.readAsArrayBuffer(cur_b.obj); + } ); + } ) + } + else { + zsession.close(); + choose_form.style.display = "none"; + } + } + + promise_callback(); + }; + + } + }; + } + } + + if (input.length) { + term.write( + String.fromCharCode.apply(String, input) + ); + } +} + +//var text_encoder = new TextEncoder(); + function runRealTerminal() { - term.attach(socket); + //term.attach(socket); + + term.on("data", (d) => { + //socket.send( text_encoder.encode(d) ); + socket.send(d); + }); + term._initialized = true; } diff --git a/demo/zmodemjs b/demo/zmodemjs new file mode 160000 index 00000000..eefc61c2 --- /dev/null +++ b/demo/zmodemjs @@ -0,0 +1 @@ +Subproject commit eefc61c2c4cbad743dea53b417a4948a08b7b652 From 6c06d83525908d12d0c0ba245cfbfba0115f1868 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 10 Sep 2017 08:01:16 -0700 Subject: [PATCH 220/378] Support theming cursor accent color Fixes #963 --- src/Interfaces.ts | 1 + src/renderer/CharAtlas.ts | 1 + src/renderer/ColorManager.ts | 3 +++ src/renderer/CursorRenderLayer.ts | 2 +- src/renderer/Interfaces.ts | 1 + typings/xterm.d.ts | 2 ++ 6 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Interfaces.ts b/src/Interfaces.ts index d022dc3e..f6c20018 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -323,6 +323,7 @@ export interface ITheme { foreground?: string; background?: string; cursor?: string; + cursorAccent?: string; selection?: string; black?: string; red?: string; diff --git a/src/renderer/CharAtlas.ts b/src/renderer/CharAtlas.ts index bf080cce..45562a25 100644 --- a/src/renderer/CharAtlas.ts +++ b/src/renderer/CharAtlas.ts @@ -77,6 +77,7 @@ function generateConfig(scaledCharWidth: number, scaledCharHeight: number, termi foreground: colors.foreground, background: null, cursor: null, + cursorAccent: null, selection: null, ansi: colors.ansi.slice(0, 16) }; diff --git a/src/renderer/ColorManager.ts b/src/renderer/ColorManager.ts index b9afd0e1..afd5c243 100644 --- a/src/renderer/ColorManager.ts +++ b/src/renderer/ColorManager.ts @@ -9,6 +9,7 @@ import { ITheme } from '../Interfaces'; const DEFAULT_FOREGROUND = '#ffffff'; const DEFAULT_BACKGROUND = '#000000'; const DEFAULT_CURSOR = '#ffffff'; +const DEFAULT_CURSOR_ACCENT = '#000000'; const DEFAULT_SELECTION = 'rgba(255, 255, 255, 0.3)'; export const DEFAULT_ANSI_COLORS = [ // dark: @@ -72,6 +73,7 @@ export class ColorManager { foreground: DEFAULT_FOREGROUND, background: DEFAULT_BACKGROUND, cursor: DEFAULT_CURSOR, + cursorAccent: DEFAULT_CURSOR_ACCENT, selection: DEFAULT_SELECTION, ansi: generate256Colors(DEFAULT_ANSI_COLORS) }; @@ -86,6 +88,7 @@ export class ColorManager { this.colors.foreground = theme.foreground || DEFAULT_FOREGROUND; this.colors.background = theme.background || DEFAULT_BACKGROUND; this.colors.cursor = theme.cursor || DEFAULT_CURSOR; + this.colors.cursorAccent = theme.cursorAccent || DEFAULT_CURSOR_ACCENT; this.colors.selection = theme.selection || DEFAULT_SELECTION; this.colors.ansi[0] = theme.black || DEFAULT_ANSI_COLORS[0]; this.colors.ansi[1] = theme.red || DEFAULT_ANSI_COLORS[1]; diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index b136a13a..bc6fbcb1 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -199,7 +199,7 @@ export class CursorRenderLayer extends BaseRenderLayer { this._ctx.save(); this._ctx.fillStyle = this.colors.cursor; this.fillCells(x, y, charData[CHAR_DATA_WIDTH_INDEX], 1); - this._ctx.fillStyle = this.colors.background; + this._ctx.fillStyle = this.colors.cursorAccent; this.fillCharTrueColor(terminal, charData, x, y); this._ctx.restore(); } diff --git a/src/renderer/Interfaces.ts b/src/renderer/Interfaces.ts index 05f98300..4b0028b8 100644 --- a/src/renderer/Interfaces.ts +++ b/src/renderer/Interfaces.ts @@ -72,6 +72,7 @@ export interface IColorSet { foreground: string; background: string; cursor: string; + cursorAccent: string; selection: string; ansi: string[]; } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 7a4d6940..de2a261b 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -88,6 +88,8 @@ interface ITheme { background?: string, /** The cursor color */ cursor?: string, + /** The accent color of the cursor (used as the foreground color for a block cursor) */ + cursorAccent?: string, /** The selection color (can be transparent) */ selection?: string, /** ANSI black (eg. `\x1b[30m`) */ From ae741348a635839ec04f7c65fc02594804f6d0c4 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 10 Sep 2017 10:31:17 -0700 Subject: [PATCH 221/378] Fix several issues with line height Fixes #966 Fixes #967 --- src/Interfaces.ts | 3 +- src/Terminal.ts | 9 +++-- src/Viewport.ts | 21 +++++----- src/addons/fit/fit.js | 2 +- src/renderer/BackgroundRenderLayer.ts | 6 +-- src/renderer/BaseRenderLayer.ts | 39 +++++------------- src/renderer/CursorRenderLayer.ts | 6 +-- src/renderer/ForegroundRenderLayer.ts | 6 +-- src/renderer/Interfaces.ts | 19 +++++++-- src/renderer/LinkRenderLayer.ts | 6 +-- src/renderer/Renderer.ts | 57 ++++++++++++++++++++++++--- src/renderer/SelectionRenderLayer.ts | 6 +-- src/utils/TestUtils.test.ts | 13 +++++- 13 files changed, 125 insertions(+), 68 deletions(-) diff --git a/src/Interfaces.ts b/src/Interfaces.ts index d022dc3e..97fa3fd8 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -5,7 +5,7 @@ import { ILinkMatcherOptions } from './Interfaces'; import { LinkMatcherHandler, LinkMatcherValidationCallback, Charset, LineData } from './Types'; -import { IColorSet } from './renderer/Interfaces'; +import { IColorSet, IRenderer } from './renderer/Interfaces'; import { IMouseZoneManager } from './input/Interfaces'; export interface IBrowser { @@ -36,6 +36,7 @@ export interface ITerminal extends ILinkifierAccessor, IBufferAccessor, IElement selectionManager: ISelectionManager; charMeasure: ICharMeasure; textarea: HTMLTextAreaElement; + renderer: IRenderer; rows: number; cols: number; browser: IBrowser; diff --git a/src/Terminal.ts b/src/Terminal.ts index e19887cb..67026ab0 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -188,7 +188,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT private inputHandler: InputHandler; private parser: Parser; - private renderer: IRenderer; + public renderer: IRenderer; public selectionManager: SelectionManager; public linkifier: ILinkifier; public buffers: BufferSet; @@ -619,7 +619,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.charMeasure = new CharMeasure(document, this.helperContainer); this.viewport = new Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasure); - this.charMeasure.on('charsizechanged', () => this.viewport.syncScrollArea()); this.renderer = new Renderer(this); this.on('cursormove', () => this.renderer.onCursorMove()); this.on('resize', () => this.renderer.onResize(this.cols, this.rows, false)); @@ -627,6 +626,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.on('focus', () => this.renderer.onFocus()); 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()); this.selectionManager = new SelectionManager(this, this.buffer, this.charMeasure); this.element.addEventListener('mousedown', (e: MouseEvent) => this.selectionManager.onMouseDown(e)); @@ -639,7 +639,10 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.textarea.focus(); this.textarea.select(); }); - this.on('scroll', () => this.selectionManager.refresh()); + this.on('scroll', () => { + this.viewport.syncScrollArea(); + this.selectionManager.refresh(); + }); this.viewportElement.addEventListener('scroll', () => this.selectionManager.refresh()); // Measure the character size diff --git a/src/Viewport.ts b/src/Viewport.ts index 1f693cfe..b7622ba7 100644 --- a/src/Viewport.ts +++ b/src/Viewport.ts @@ -34,8 +34,6 @@ export class Viewport implements IViewport { this.lastRecordedBufferLength = 0; this.lastRecordedViewportHeight = 0; - this.terminal.on('scroll', this.syncScrollArea.bind(this)); - this.terminal.on('resize', this.syncScrollArea.bind(this)); this.viewportElement.addEventListener('scroll', this.onScroll.bind(this)); // Perform this async to ensure the CharMeasure is ready. @@ -52,18 +50,20 @@ export class Viewport implements IViewport { */ private refresh(): void { if (this.charMeasure.height > 0) { - const lineHeight = Math.ceil(this.charMeasure.height * this.terminal.options.lineHeight); + const lineHeight = (this.terminal).renderer.dimensions.scaledLineHeight / window.devicePixelRatio; const rowHeightChanged = lineHeight !== this.currentRowHeight; + // TODO: Do we need lineHeight anymore?? if (rowHeightChanged) { this.currentRowHeight = lineHeight; this.viewportElement.style.lineHeight = lineHeight + 'px'; } - const viewportHeightChanged = this.lastRecordedViewportHeight !== this.terminal.rows; - if (rowHeightChanged || viewportHeightChanged) { - this.lastRecordedViewportHeight = this.terminal.rows; - this.viewportElement.style.height = lineHeight * this.terminal.rows + 'px'; + // const viewportHeightChanged = this.lastRecordedViewportHeight !== this.terminal.rows; + const viewportHeightChanged = this.lastRecordedViewportHeight !== this.terminal.renderer.dimensions.canvasHeight; + if (viewportHeightChanged) { + this.lastRecordedViewportHeight = this.terminal.renderer.dimensions.canvasHeight; + this.viewportElement.style.height = this.lastRecordedViewportHeight + 'px'; } - this.scrollArea.style.height = (lineHeight * this.lastRecordedBufferLength) + 'px'; + this.scrollArea.style.height = Math.round(lineHeight * this.lastRecordedBufferLength) + 'px'; } } @@ -75,12 +75,13 @@ export class Viewport implements IViewport { // If buffer height changed this.lastRecordedBufferLength = this.terminal.buffer.lines.length; this.refresh(); - } else if (this.lastRecordedViewportHeight !== this.terminal.rows) { + } else if (this.lastRecordedViewportHeight !== (this.terminal).renderer.dimensions.canvasHeight) { // If viewport height changed this.refresh(); } else { // If size has changed, refresh viewport - if (Math.ceil(this.charMeasure.height * this.terminal.options.lineHeight) !== this.currentRowHeight) { + console.log(this.terminal.renderer.dimensions.scaledLineHeight / window.devicePixelRatio); + if (this.terminal.renderer.dimensions.scaledLineHeight / window.devicePixelRatio !== this.currentRowHeight) { this.refresh(); } } diff --git a/src/addons/fit/fit.js b/src/addons/fit/fit.js index 59f0fd52..3e61a7ef 100644 --- a/src/addons/fit/fit.js +++ b/src/addons/fit/fit.js @@ -47,7 +47,7 @@ var availableWidth = parentElementWidth - elementPaddingHor; var geometry = { cols: Math.floor(availableWidth / term.charMeasure.width), - rows: Math.floor(availableHeight / Math.ceil(term.charMeasure.height * term.getOption('lineHeight'))) + rows: Math.floor(availableHeight / Math.floor(term.charMeasure.height * term.getOption('lineHeight'))) }; return geometry; diff --git a/src/renderer/BackgroundRenderLayer.ts b/src/renderer/BackgroundRenderLayer.ts index 0c09727d..279aee6d 100644 --- a/src/renderer/BackgroundRenderLayer.ts +++ b/src/renderer/BackgroundRenderLayer.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IColorSet } from './Interfaces'; +import { IColorSet, IRenderDimensions } from './Interfaces'; import { IBuffer, ICharMeasure, ITerminal } from '../Interfaces'; import { CHAR_DATA_ATTR_INDEX } from '../Buffer'; import { GridCache } from './GridCache'; @@ -18,8 +18,8 @@ export class BackgroundRenderLayer extends BaseRenderLayer { this._state = new GridCache(); } - public resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void { - super.resize(terminal, canvasWidth, canvasHeight, charSizeChanged); + public resize(terminal: ITerminal, dim: IRenderDimensions, charSizeChanged: boolean): void { + super.resize(terminal, dim, charSizeChanged); // Resizing the canvas discards the contents of the canvas so clear state this._state.clear(); this._state.resize(terminal.cols, terminal.rows); diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index cc02bcea..38457e13 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IRenderLayer, IColorSet } from './Interfaces'; +import { IRenderLayer, IColorSet, IRenderDimensions } from './Interfaces'; import { ITerminal, ITerminalOptions } from '../Interfaces'; import { acquireCharAtlas, CHAR_ATLAS_CELL_SPACING } from './CharAtlas'; import { CharData } from '../Types'; @@ -61,34 +61,15 @@ export abstract class BaseRenderLayer implements IRenderLayer { } } - public resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void { - // Calculate the scaled character dimensions, if devicePixelRatio is a - // floating point number then the value is ceiled to ensure there is enough - // space to draw the character to the cell - this.scaledCharWidth = Math.ceil(terminal.charMeasure.width * window.devicePixelRatio); - this.scaledCharHeight = Math.ceil(terminal.charMeasure.height * window.devicePixelRatio); - - // Calculate the scaled line height, if lineHeight is not 1 then the value - // will be floored because since lineHeight can never be lower then 1, there - // is a guarentee that the scaled line height will always be larger than - // scaled char height. - this.scaledLineHeight = Math.floor(this.scaledCharHeight * terminal.options.lineHeight); - - // Calculate the y coordinate within a cell that text should draw from in - // order to draw in the center of a cell. - this.scaledLineDrawY = terminal.options.lineHeight === 1 ? 0 : Math.round((this.scaledLineHeight - this.scaledCharHeight) / 2); - - // Recalcualte the canvas dimensions; width/height define the actual number - // of pixels in the canvas, style.width/height define the size of the canvas - // on the page. It's very important that this rounds to nearest integer and - // not ceils as browsers often set window.devicePixelRatio as something like - // 1.100000023841858, when it's actually 1.1. Ceiling causes blurriness as - // the backing canvas image is 1 pixel too large for the canvas element - // size. - this._canvas.width = Math.round(canvasWidth * window.devicePixelRatio); - this._canvas.height = Math.round(canvasHeight * window.devicePixelRatio); - this._canvas.style.width = `${canvasWidth}px`; - this._canvas.style.height = `${canvasHeight}px`; + public resize(terminal: ITerminal, dim: IRenderDimensions, charSizeChanged: boolean): void { + this.scaledCharWidth = dim.scaledCharWidth; + this.scaledCharHeight = dim.scaledCharHeight; + this.scaledLineHeight = dim.scaledLineHeight; + this.scaledLineDrawY = dim.scaledLineDrawY; + this._canvas.width = dim.scaledCanvasWidth; + this._canvas.height = dim.scaledCanvasHeight; + this._canvas.style.width = `${dim.canvasWidth}px`; + this._canvas.style.height = `${dim.canvasHeight}px`; if (charSizeChanged) { this._refreshCharAtlas(terminal, this.colors); diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index b136a13a..8e25d7c1 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IColorSet } from './Interfaces'; +import { IColorSet, IRenderDimensions } from './Interfaces'; import { IBuffer, ICharMeasure, ITerminal, ITerminalOptions } from '../Interfaces'; import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX } from '../Buffer'; import { GridCache } from './GridCache'; @@ -47,8 +47,8 @@ export class CursorRenderLayer extends BaseRenderLayer { // TODO: Consider initial options? Maybe onOptionsChanged should be called at the end of open? } - public resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void { - super.resize(terminal, canvasWidth, canvasHeight, charSizeChanged); + public resize(terminal: ITerminal, dim: IRenderDimensions, charSizeChanged: boolean): void { + super.resize(terminal, dim, charSizeChanged); // Resizing the canvas discards the contents of the canvas so clear state this._state = { x: null, diff --git a/src/renderer/ForegroundRenderLayer.ts b/src/renderer/ForegroundRenderLayer.ts index 883be5d9..8e9e23fc 100644 --- a/src/renderer/ForegroundRenderLayer.ts +++ b/src/renderer/ForegroundRenderLayer.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IColorSet } from './Interfaces'; +import { IColorSet, IRenderDimensions } from './Interfaces'; import { IBuffer, ICharMeasure, ITerminal } from '../Interfaces'; import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from '../Buffer'; import { FLAGS } from './Types'; @@ -26,8 +26,8 @@ export class ForegroundRenderLayer extends BaseRenderLayer { this._state = new GridCache(); } - public resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void { - super.resize(terminal, canvasWidth, canvasHeight, charSizeChanged); + public resize(terminal: ITerminal, dim: IRenderDimensions, charSizeChanged: boolean): void { + super.resize(terminal, dim, charSizeChanged); // Resizing the canvas discards the contents of the canvas so clear state this._state.clear(); this._state.resize(terminal.cols, terminal.rows); diff --git a/src/renderer/Interfaces.ts b/src/renderer/Interfaces.ts index 05f98300..890b01f5 100644 --- a/src/renderer/Interfaces.ts +++ b/src/renderer/Interfaces.ts @@ -3,9 +3,11 @@ * @license MIT */ -import { ITerminal, ITerminalOptions, ITheme } from '../Interfaces'; +import { ITerminal, ITerminalOptions, ITheme, IEventEmitter } from '../Interfaces'; + +export interface IRenderer extends IEventEmitter { + dimensions: IRenderDimensions; -export interface IRenderer { setTheme(theme: ITheme): IColorSet; onWindowResize(devicePixelRatio: number): void; onResize(cols: number, rows: number, didCharSizeChange: boolean): void; @@ -59,7 +61,7 @@ export interface IRenderLayer { /** * Resize the render layer. */ - resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void; + resize(terminal: ITerminal, dim: IRenderDimensions, charSizeChanged: boolean): void; /** * Clear the state of the render layer. @@ -75,3 +77,14 @@ export interface IColorSet { selection: string; ansi: string[]; } + +export interface IRenderDimensions { + scaledCharWidth: number; + scaledCharHeight: number; + scaledLineHeight: number; + scaledLineDrawY: number; + scaledCanvasWidth: number; + scaledCanvasHeight: number; + canvasWidth: number; + canvasHeight: number; +} diff --git a/src/renderer/LinkRenderLayer.ts b/src/renderer/LinkRenderLayer.ts index 9b2cabb3..ad178933 100644 --- a/src/renderer/LinkRenderLayer.ts +++ b/src/renderer/LinkRenderLayer.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IColorSet } from './Interfaces'; +import { IColorSet, IRenderDimensions } from './Interfaces'; import { IBuffer, ICharMeasure, ITerminal, ILinkifierAccessor } from '../Interfaces'; import { CHAR_DATA_ATTR_INDEX } from '../Buffer'; import { GridCache } from './GridCache'; @@ -20,8 +20,8 @@ export class LinkRenderLayer extends BaseRenderLayer { terminal.linkifier.on(LinkHoverEventTypes.LEAVE, (e: LinkHoverEvent) => this._onLinkLeave(e)); } - public resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void { - super.resize(terminal, canvasWidth, canvasHeight, charSizeChanged); + public resize(terminal: ITerminal, dim: IRenderDimensions, charSizeChanged: boolean): void { + super.resize(terminal, dim, charSizeChanged); // Resizing the canvas discards the contents of the canvas so clear state this._state = null; } diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 131a8b78..b610fd69 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -11,10 +11,11 @@ import { SelectionRenderLayer } from './SelectionRenderLayer'; import { CursorRenderLayer } from './CursorRenderLayer'; import { ColorManager } from './ColorManager'; import { BaseRenderLayer } from './BaseRenderLayer'; -import { IRenderLayer, IColorSet, IRenderer } from './Interfaces'; +import { IRenderLayer, IColorSet, IRenderer, IRenderDimensions } from './Interfaces'; import { LinkRenderLayer } from './LinkRenderLayer'; +import { EventEmitter } from '../EventEmitter'; -export class Renderer implements IRenderer { +export class Renderer extends EventEmitter implements IRenderer { /** A queue of the rows to be refreshed */ private _refreshRowsQueue: {start: number, end: number}[] = []; private _refreshAnimationFrame = null; @@ -23,8 +24,10 @@ export class Renderer implements IRenderer { private _devicePixelRatio: number; private _colorManager: ColorManager; + public dimensions: IRenderDimensions; constructor(private _terminal: ITerminal) { + super(); this._colorManager = new ColorManager(); this._renderLayers = [ new BackgroundRenderLayer(this._terminal.element, 0, this._colorManager.colors), @@ -33,6 +36,16 @@ export class Renderer implements IRenderer { new LinkRenderLayer(this._terminal.element, 3, this._colorManager.colors, this._terminal), new CursorRenderLayer(this._terminal.element, 4, this._colorManager.colors) ]; + this.dimensions = { + scaledCharWidth: null, + scaledCharHeight: null, + scaledLineHeight: null, + scaledLineDrawY: null, + scaledCanvasWidth: null, + scaledCanvasHeight: null, + canvasWidth: null, + canvasHeight: null + }; this._devicePixelRatio = window.devicePixelRatio; } @@ -63,12 +76,46 @@ export class Renderer implements IRenderer { if (!this._terminal.charMeasure.width || !this._terminal.charMeasure.height) { return; } - const width = this._terminal.charMeasure.width * cols; - const height = Math.floor(this._terminal.charMeasure.height * this._terminal.options.lineHeight) * rows; + + // Calculate the scaled character dimensions, if devicePixelRatio is a + // floating point number then the value is ceiled to ensure there is enough + // space to draw the character to the cell + this.dimensions.scaledCharWidth = Math.ceil(this._terminal.charMeasure.width * window.devicePixelRatio); + this.dimensions.scaledCharHeight = Math.ceil(this._terminal.charMeasure.height * window.devicePixelRatio); + + // Calculate the scaled line height, if lineHeight is not 1 then the value + // will be floored because since lineHeight can never be lower then 1, there + // is a guarentee that the scaled line height will always be larger than + // scaled char height. + this.dimensions.scaledLineHeight = Math.floor(this.dimensions.scaledCharHeight * this._terminal.options.lineHeight); + + // Calculate the y coordinate within a cell that text should draw from in + // order to draw in the center of a cell. + this.dimensions.scaledLineDrawY = this._terminal.options.lineHeight === 1 ? 0 : Math.round((this.dimensions.scaledLineHeight - this.dimensions.scaledCharHeight) / 2); + + // Recalculate the canvas dimensions; scaled* define the actual number of + // pixel in the canvas + this.dimensions.scaledCanvasHeight = this._terminal.rows * this.dimensions.scaledLineHeight; + this.dimensions.scaledCanvasWidth = this._terminal.cols * this.dimensions.scaledCharWidth; + + // The the size of the canvas on the page. It's very important that this + // rounds to nearest integer and not ceils as browsers often set + // window.devicePixelRatio as something like 1.100000023841858, when it's + // actually 1.1. Ceiling causes blurriness as the backing canvas image is 1 + // pixel too large for the canvas element size. + this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / window.devicePixelRatio); + this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / window.devicePixelRatio); + // Resize all render layers - this._renderLayers.forEach(l => l.resize(this._terminal, width, height, didCharSizeChange)); + this._renderLayers.forEach(l => l.resize(this._terminal, this.dimensions, didCharSizeChange)); + // Force a refresh this._terminal.refresh(0, this._terminal.rows - 1); + + this.emit('resize', { + width: this.dimensions.canvasWidth, + height: this.dimensions.canvasHeight + }); } public onCharSizeChanged(): void { diff --git a/src/renderer/SelectionRenderLayer.ts b/src/renderer/SelectionRenderLayer.ts index 39569c9b..5339c7a4 100644 --- a/src/renderer/SelectionRenderLayer.ts +++ b/src/renderer/SelectionRenderLayer.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IColorSet } from './Interfaces'; +import { IColorSet, IRenderDimensions } from './Interfaces'; import { IBuffer, ICharMeasure, ITerminal } from '../Interfaces'; import { CHAR_DATA_ATTR_INDEX } from '../Buffer'; import { GridCache } from './GridCache'; @@ -21,8 +21,8 @@ export class SelectionRenderLayer extends BaseRenderLayer { }; } - public resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void { - super.resize(terminal, canvasWidth, canvasHeight, charSizeChanged); + public resize(terminal: ITerminal, dim: IRenderDimensions, charSizeChanged: boolean): void { + super.resize(terminal, dim, charSizeChanged); // Resizing the canvas discards the contents of the canvas so clear state this._state = { start: null, diff --git a/src/utils/TestUtils.test.ts b/src/utils/TestUtils.test.ts index eb2f2501..45cf169a 100644 --- a/src/utils/TestUtils.test.ts +++ b/src/utils/TestUtils.test.ts @@ -7,9 +7,10 @@ import { ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManag import { LineData } from '../Types'; import { Buffer } from '../Buffer'; import * as Browser from './Browser'; -import { IColorSet, IRenderer } from '../renderer/Interfaces'; +import { IColorSet, IRenderer, IRenderDimensions } from '../renderer/Interfaces'; export class MockTerminal implements ITerminal { + renderer: IRenderer; linkifier: ILinkifier; isFocused: boolean; options: ITerminalOptions = {}; @@ -215,6 +216,16 @@ export class MockBuffer implements IBuffer { } export class MockRenderer implements IRenderer { + on(type: string, listener: IListenerType): void { + throw new Error('Method not implemented.'); + } + off(type: string, listener: IListenerType): void { + throw new Error('Method not implemented.'); + } + emit(type: string, data?: any): void { + throw new Error('Method not implemented.'); + } + dimensions: IRenderDimensions; setTheme(theme: ITheme): IColorSet { return {}; } onResize(cols: number, rows: number, didCharSizeChange: boolean): void {} onCharSizeChanged(): void {} From ac23be134b1b5926aee87654e92493cb691cd375 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 10 Sep 2017 10:38:08 -0700 Subject: [PATCH 222/378] Simplify viewport --- src/Viewport.ts | 32 +++++++++++++------------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/src/Viewport.ts b/src/Viewport.ts index b7622ba7..064076a2 100644 --- a/src/Viewport.ts +++ b/src/Viewport.ts @@ -12,9 +12,10 @@ import { IColorSet } from './renderer/Interfaces'; * Logic for the virtual scroll bar is included in this object. */ export class Viewport implements IViewport { - private currentRowHeight: number; - private lastRecordedBufferLength: number; - private lastRecordedViewportHeight: number; + private currentRowHeight: number = 0; + private lastRecordedBufferLength: number = 0; + private lastRecordedViewportHeight: number = 0; + private lastRecordedBufferHeight: number = 0; private lastTouchY: number; /** @@ -30,10 +31,6 @@ export class Viewport implements IViewport { private scrollArea: HTMLElement, private charMeasure: CharMeasure ) { - this.currentRowHeight = 0; - this.lastRecordedBufferLength = 0; - this.lastRecordedViewportHeight = 0; - this.viewportElement.addEventListener('scroll', this.onScroll.bind(this)); // Perform this async to ensure the CharMeasure is ready. @@ -50,20 +47,18 @@ export class Viewport implements IViewport { */ private refresh(): void { if (this.charMeasure.height > 0) { - const lineHeight = (this.terminal).renderer.dimensions.scaledLineHeight / window.devicePixelRatio; - const rowHeightChanged = lineHeight !== this.currentRowHeight; - // TODO: Do we need lineHeight anymore?? - if (rowHeightChanged) { - this.currentRowHeight = lineHeight; - this.viewportElement.style.lineHeight = lineHeight + 'px'; - } - // const viewportHeightChanged = this.lastRecordedViewportHeight !== this.terminal.rows; - const viewportHeightChanged = this.lastRecordedViewportHeight !== this.terminal.renderer.dimensions.canvasHeight; - if (viewportHeightChanged) { + this.currentRowHeight = this.terminal.renderer.dimensions.scaledLineHeight / window.devicePixelRatio; + + if (this.lastRecordedViewportHeight !== this.terminal.renderer.dimensions.canvasHeight) { this.lastRecordedViewportHeight = this.terminal.renderer.dimensions.canvasHeight; this.viewportElement.style.height = this.lastRecordedViewportHeight + 'px'; } - this.scrollArea.style.height = Math.round(lineHeight * this.lastRecordedBufferLength) + 'px'; + + const newBufferHeight = Math.round(this.currentRowHeight * this.lastRecordedBufferLength); + if (this.lastRecordedBufferHeight !== newBufferHeight) { + this.lastRecordedBufferHeight = newBufferHeight; + this.scrollArea.style.height = this.lastRecordedBufferHeight + 'px'; + } } } @@ -80,7 +75,6 @@ export class Viewport implements IViewport { this.refresh(); } else { // If size has changed, refresh viewport - console.log(this.terminal.renderer.dimensions.scaledLineHeight / window.devicePixelRatio); if (this.terminal.renderer.dimensions.scaledLineHeight / window.devicePixelRatio !== this.currentRowHeight) { this.refresh(); } From 903ed1917e38c52c372d6b340d6a65ac0dcbef78 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 10 Sep 2017 10:43:07 -0700 Subject: [PATCH 223/378] Remove viewport test It's not tightly linked to renderer and depends on devicePixelRatio. --- src/Viewport.test.ts | 90 -------------------------------------------- 1 file changed, 90 deletions(-) delete mode 100644 src/Viewport.test.ts diff --git a/src/Viewport.test.ts b/src/Viewport.test.ts deleted file mode 100644 index 6ce798d3..00000000 --- a/src/Viewport.test.ts +++ /dev/null @@ -1,90 +0,0 @@ -/** - * Copyright (c) 2016 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { assert } from 'chai'; -import { Viewport } from './Viewport'; -import { BufferSet } from './BufferSet'; - -describe('Viewport', () => { - let terminal; - let viewportElement; - let charMeasure; - let viewport; - let scrollAreaElement; - - const CHARACTER_HEIGHT = 10; - - beforeEach(() => { - terminal = { - rows: 0, - ydisp: 0, - on: () => {}, - rowContainer: { - style: { - lineHeight: 0 - } - }, - selectionContainer: { - style: { - height: 0 - } - }, - options: { - scrollback: 10, - lineHeight: 1 - } - }; - terminal.buffers = new BufferSet(terminal); - terminal.buffer = terminal.buffers.active; - viewportElement = { - addEventListener: () => {}, - style: { - height: 0, - lineHeight: 0 - } - }; - scrollAreaElement = { - style: { - height: 0 - } - }; - charMeasure = { - height: CHARACTER_HEIGHT - }; - viewport = new Viewport(terminal, viewportElement, scrollAreaElement, charMeasure); - }); - - describe('refresh', () => { - it('should set the height of the viewport when the line-height changed', () => { - terminal.buffer.lines.push(''); - terminal.buffer.lines.push(''); - terminal.rows = 1; - viewport.refresh(); - assert.equal(viewportElement.style.height, 1 * CHARACTER_HEIGHT + 'px'); - charMeasure.height = 2 * CHARACTER_HEIGHT; - viewport.refresh(); - assert.equal(viewportElement.style.height, 2 * CHARACTER_HEIGHT + 'px'); - }); - }); - - describe('syncScrollArea', () => { - it('should sync the scroll area', done => { - // Allow CharMeasure to be initialized - setTimeout(() => { - terminal.buffer.lines.push(''); - terminal.rows = 1; - assert.equal(scrollAreaElement.style.height, 0 * CHARACTER_HEIGHT + 'px'); - viewport.syncScrollArea(); - assert.equal(viewportElement.style.height, 1 * CHARACTER_HEIGHT + 'px'); - assert.equal(scrollAreaElement.style.height, 1 * CHARACTER_HEIGHT + 'px'); - terminal.buffer.lines.push(''); - viewport.syncScrollArea(); - assert.equal(viewportElement.style.height, 1 * CHARACTER_HEIGHT + 'px'); - assert.equal(scrollAreaElement.style.height, 2 * CHARACTER_HEIGHT + 'px'); - done(); - }, 0); - }); - }); -}); From 91aa8c51fc3bdf04e4cb0f7712ceed5719686f46 Mon Sep 17 00:00:00 2001 From: Felipe Gasper Date: Mon, 11 Sep 2017 00:01:07 -0400 Subject: [PATCH 224/378] got it --- demo/app.js | 2 +- demo/main.js | 70 +++++++++++++++++++++++++++++++++++++++++++++++---- demo/zmodemjs | 2 +- package.json | 2 +- 4 files changed, 68 insertions(+), 8 deletions(-) diff --git a/demo/app.js b/demo/app.js index 089ccf6e..0c137a1d 100644 --- a/demo/app.js +++ b/demo/app.js @@ -29,6 +29,7 @@ app.post('/terminals', function (req, res) { var cols = parseInt(req.query.cols), rows = parseInt(req.query.rows), term = pty.spawn(process.platform === 'win32' ? 'cmd.exe' : 'bash', [], { + encoding: null, name: 'xterm-color', cols: cols || 80, rows: rows || 24, @@ -63,7 +64,6 @@ app.ws('/terminals/:pid', function (ws, req) { ws.send(logs[term.pid]); term.on('data', function(data) { -console.log("From PTY: ", escape(data) ); try { ws.send(data); } catch (ex) { diff --git a/demo/main.js b/demo/main.js index c1d4d4e9..38124de7 100644 --- a/demo/main.js +++ b/demo/main.js @@ -106,6 +106,7 @@ function createTerminal() { window.pid = pid; socketURL += pid; socket = new WebSocket(socketURL); + socket.binaryType = "arraybuffer"; socket.onopen = runRealTerminal; socket.onclose = runFakeTerminal; socket.onerror = runFakeTerminal; @@ -116,21 +117,79 @@ function createTerminal() { }, 0); } +//---------------------------------------------------------------------- +// UI STUFF + +function _show_file_info(xfer) { + var file_info = xfer.get_details(); + + document.getElementById("name").textContent = file_info.name; + document.getElementById("size").textContent = file_info.size; + document.getElementById("mtime").textContent = file_info.mtime; + document.getElementById("files_remaining").textContent = file_info.files_remaining; + document.getElementById("bytes_remaining").textContent = file_info.bytes_remaining; + + document.getElementById("mode").textContent = "0" + file_info.mode.toString(8); + + document.getElementById("zm_file").style.display = ""; +} +function _hide_file_info() { + document.getElementById("zm_file").style.display = "none"; +} + +function _download(xfer, buffer) { + var uint8array = new Uint8Array(buffer); + var blob = new Blob([uint8array]); + var url = URL.createObjectURL(blob); + + var el = document.createElement("a"); + el.style.display = "none"; + el.href = url; + el.download = xfer.get_details().name; + document.body.appendChild(el); + + //It seems like a security problem that this actually works. + //But, hey. + el.click(); +} + +function _update_progress(xfer) { + document.getElementById("zm_progress").style.display = ""; + + var total_in = xfer.get_offset(); + + document.getElementById("bytes_received").textContent = total_in; + + var percent_received = 100 * total_in / xfer.get_details().size; + document.getElementById("percent_received").textContent = percent_received; +} + +// END UI STUFF +//---------------------------------------------------------------------- + var text_encoder = new TextEncoder(); var zsentry = new Zmodem.Sentry(); var zsession; function handleWSMessage(evt) { - var binary = text_encoder.encode(evt.data); - var input = Array.prototype.slice.call( new Uint8Array(binary) ); -console.log("from WS", evt.data, input, evt.data.charCodeAt( evt.data.length - 2 )); + var input = Array.prototype.slice.call( + new Uint8Array( + (typeof evt.data === "string") ? text_encoder.encode(evt.data) : evt.data + ) + ); if (zsession) { zsession.consume(input); if (zsession.has_ended()) { - input = zsession.get_trailing_bytes(); + if (zsession.type === "receive") { + input = zsession.get_trailing_bytes(); + } + else { + input = []; + } zsession = null; } + else input = []; //keep ZMODEM from going to the terminal } else { let termbytes; @@ -140,7 +199,8 @@ console.log("from WS", evt.data, input, evt.data.charCodeAt( evt.data.length - 2 if (zsession) { zsession.set_sender( (octets) => { - socket.send( String.fromCharCode.apply(String, octets) ); + //socket.send( String.fromCharCode.apply(String, octets) ); + socket.send( new Uint8Array(octets) ); } ); var start_form = document.getElementById("zm_start"); diff --git a/demo/zmodemjs b/demo/zmodemjs index eefc61c2..7220230d 160000 --- a/demo/zmodemjs +++ b/demo/zmodemjs @@ -1 +1 @@ -Subproject commit eefc61c2c4cbad743dea53b417a4948a08b7b652 +Subproject commit 7220230d12ce5eb4c42c59442a5924d98abc1c1f diff --git a/package.json b/package.json index f32918df..dc1e47ae 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "jsdoc": "3.4.3", "jsdom": "^11.1.0", "merge-stream": "^1.0.1", - "node-pty": "^0.4.1", + "node-pty": "0.6.4", "nodemon": "1.10.2", "sorcery": "^0.10.0", "tslint": "^4.0.2", From 2114bc4837b6cf7dd8b362e5791fc4c0ed762008 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 11 Sep 2017 11:12:51 -0700 Subject: [PATCH 225/378] Ensure leave is called when leaving a MouseZone Fixes #975 --- src/input/MouseZoneManager.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/input/MouseZoneManager.ts b/src/input/MouseZoneManager.ts index d1bc8a75..d84b7218 100644 --- a/src/input/MouseZoneManager.ts +++ b/src/input/MouseZoneManager.ts @@ -83,10 +83,14 @@ export class MouseZoneManager implements IMouseZoneManager { return; } - // Fire the hover end callback if a zone was being hovered + // Fire the hover end callback and cancel any existing timer if a new zone + // is being hovered if (this._currentZone) { this._currentZone.leaveCallback(); this._currentZone = null; + if (this._tooltipTimeout) { + clearTimeout(this._tooltipTimeout); + } } // Exit if there is not zone @@ -100,14 +104,12 @@ export class MouseZoneManager implements IMouseZoneManager { zone.hoverCallback(e); } - // Restart the timeout - if (this._tooltipTimeout) { - clearTimeout(this._tooltipTimeout); - } + // Restart the tooltip timeout this._tooltipTimeout = setTimeout(() => this._onTooltip(e), HOVER_DURATION); } private _onTooltip(e: MouseEvent): void { + this._tooltipTimeout = null; const zone = this._findZoneEventAt(e); if (zone && zone.tooltipCallback) { zone.tooltipCallback(e); From 6181262c76e53fefe2f1e5eb370ab6f9f36c68c3 Mon Sep 17 00:00:00 2001 From: Felipe Gasper Date: Mon, 11 Sep 2017 22:39:13 -0400 Subject: [PATCH 226/378] remove debug changes --- demo/main.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/demo/main.js b/demo/main.js index 38124de7..f1245cb0 100644 --- a/demo/main.js +++ b/demo/main.js @@ -90,7 +90,7 @@ function createTerminal() { socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/terminals/'; term.open(terminalContainer); - //term.fit(); + term.fit(); // fit is called within a setTimeout, cols and rows need this. setTimeout(() => { @@ -199,7 +199,6 @@ function handleWSMessage(evt) { if (zsession) { zsession.set_sender( (octets) => { - //socket.send( String.fromCharCode.apply(String, octets) ); socket.send( new Uint8Array(octets) ); } ); From 6899e01fc1e5c0b5639faefddbe6f1c63f5f1ede Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 12 Sep 2017 15:05:47 -0700 Subject: [PATCH 227/378] Improve scrolling while user has scrolled up Fixes #953 --- src/Terminal.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index e19887cb..f4d10dc4 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1071,7 +1071,16 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT // Only adjust ybase and ydisp when the buffer is not trimmed if (!willBufferBeTrimmed) { this.buffer.ybase++; - this.buffer.ydisp++; + // Only scroll the ydisp with ybase if the user has not scrolled up + if (!this.userScrolling) { + this.buffer.ydisp++; + } + } else { + // When the buffer is full and the user has scrolled up, keep the text + // stable unless ydisp is right at the top + if (this.userScrolling) { + this.buffer.ydisp = Math.max(this.buffer.ydisp - 1, 0); + } } } else { // scrollTop is non-zero which means no line will be going to the From 403fd3cd4f205d74fcd51513433d9dbf8ba50921 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 12 Sep 2017 18:55:59 -0700 Subject: [PATCH 228/378] Draw text using decimal numbers Fixes #972 --- src/renderer/BaseRenderLayer.ts | 36 ++++++++++++++++++++++++++------- src/renderer/Renderer.ts | 17 +++++++++++----- src/utils/CharMeasure.ts | 2 +- 3 files changed, 42 insertions(+), 13 deletions(-) diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 38457e13..3bc0a917 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -78,6 +78,16 @@ export abstract class BaseRenderLayer implements IRenderLayer { public abstract reset(terminal: ITerminal): void; + /** + * Gets the left position of a cell. Since character width is stored as a + * float in order to prevent bad letter spacing, drawing shapes in the cell + * need to be rounded. + * @param x The column of the cell. + */ + private _getCellLeft(x: number): number { + return Math.round(x * this.scaledCharWidth); + } + /** * Fills 1+ cells completely. This uses the existing fillStyle on the context. * @param x The column to start at. @@ -86,7 +96,12 @@ export abstract class BaseRenderLayer implements IRenderLayer { * @param height The number of rows to fill. */ protected fillCells(x: number, y: number, width: number, height: number): void { - this._ctx.fillRect(x * this.scaledCharWidth, y * this.scaledLineHeight, width * this.scaledCharWidth, height * this.scaledLineHeight); + const cellLeft = this._getCellLeft(x); + this._ctx.fillRect( + cellLeft, + y * this.scaledLineHeight, + this._getCellLeft(x + width) - cellLeft, + height * this.scaledLineHeight); } /** @@ -96,10 +111,11 @@ export abstract class BaseRenderLayer implements IRenderLayer { * @param y The row to fill. */ protected fillBottomLineAtCells(x: number, y: number, width: number = 1): void { + const cellLeft = this._getCellLeft(x); this._ctx.fillRect( - x * this.scaledCharWidth, + cellLeft, (y + 1) * this.scaledLineHeight - window.devicePixelRatio - 1 /* Ensure it's drawn within the cell */, - width * this.scaledCharWidth, + this._getCellLeft(x + width) - cellLeft, window.devicePixelRatio); } @@ -111,7 +127,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { */ protected fillLeftLineAtCell(x: number, y: number): void { this._ctx.fillRect( - x * this.scaledCharWidth, + this._getCellLeft(x), y * this.scaledLineHeight, window.devicePixelRatio, this.scaledLineHeight); @@ -124,11 +140,12 @@ export abstract class BaseRenderLayer implements IRenderLayer { * @param y The row to fill. */ protected strokeRectAtCell(x: number, y: number, width: number, height: number): void { + const cellLeft = this._getCellLeft(x); this._ctx.lineWidth = window.devicePixelRatio; this._ctx.strokeRect( - x * this.scaledCharWidth + window.devicePixelRatio / 2, + cellLeft + window.devicePixelRatio / 2, y * this.scaledLineHeight + (window.devicePixelRatio / 2), - (width * this.scaledCharWidth) - window.devicePixelRatio, + this._getCellLeft(x + width) - cellLeft - window.devicePixelRatio, (height * this.scaledLineHeight) - window.devicePixelRatio); } @@ -147,7 +164,12 @@ export abstract class BaseRenderLayer implements IRenderLayer { * @param height The number of rows to clear. */ protected clearCells(x: number, y: number, width: number, height: number): void { - this._ctx.clearRect(x * this.scaledCharWidth, y * this.scaledLineHeight, width * this.scaledCharWidth, height * this.scaledLineHeight); + const cellLeft = this._getCellLeft(x); + this._ctx.clearRect( + cellLeft, + y * this.scaledLineHeight, + this._getCellLeft(x + width) - cellLeft, + height * this.scaledLineHeight); } /** diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index b610fd69..a2b8a7fe 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -77,10 +77,17 @@ export class Renderer extends EventEmitter implements IRenderer { return; } - // Calculate the scaled character dimensions, if devicePixelRatio is a - // floating point number then the value is ceiled to ensure there is enough - // space to draw the character to the cell - this.dimensions.scaledCharWidth = Math.ceil(this._terminal.charMeasure.width * window.devicePixelRatio); + // Calculate the scaled character width. Width is kept as a decimal to + // provide better letter spacing, otherwise the text can look odd. + // Characters drawn using this decimal number do have the potential to + // overlap, but only by a single pixel. As such, it's not a big deal when + // they do as that pixel is always cleared as necessary before drawing the + // character. + this.dimensions.scaledCharWidth = this._terminal.charMeasure.width * window.devicePixelRatio; + + // Calculate the scaled character height. Height is ceiled in case + // devicePixelRatio is a floating point number in order to ensure there is + // enough space to draw the character to the cell. this.dimensions.scaledCharHeight = Math.ceil(this._terminal.charMeasure.height * window.devicePixelRatio); // Calculate the scaled line height, if lineHeight is not 1 then the value @@ -96,7 +103,7 @@ export class Renderer extends EventEmitter implements IRenderer { // Recalculate the canvas dimensions; scaled* define the actual number of // pixel in the canvas this.dimensions.scaledCanvasHeight = this._terminal.rows * this.dimensions.scaledLineHeight; - this.dimensions.scaledCanvasWidth = this._terminal.cols * this.dimensions.scaledCharWidth; + this.dimensions.scaledCanvasWidth = Math.round(this._terminal.cols * this.dimensions.scaledCharWidth); // The the size of the canvas on the page. It's very important that this // rounds to nearest integer and not ceils as browsers often set diff --git a/src/utils/CharMeasure.ts b/src/utils/CharMeasure.ts index e8fcff86..e5b03f87 100644 --- a/src/utils/CharMeasure.ts +++ b/src/utils/CharMeasure.ts @@ -60,7 +60,7 @@ export class CharMeasure extends EventEmitter implements ICharMeasure { return; } if (this._width !== geometry.width || this._height !== geometry.height) { - this._width = Math.ceil(geometry.width); + this._width = geometry.width; this._height = Math.ceil(geometry.height); this.emit('charsizechanged'); } From cf324529175712873522b85ceb5834d5cd4d00d6 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 12 Sep 2017 19:23:26 -0700 Subject: [PATCH 229/378] Treat \u279C arrow as ambiguous width char (like emoji) Fixes #974 --- src/renderer/ForegroundRenderLayer.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/renderer/ForegroundRenderLayer.ts b/src/renderer/ForegroundRenderLayer.ts index 8e9e23fc..9334f109 100644 --- a/src/renderer/ForegroundRenderLayer.ts +++ b/src/renderer/ForegroundRenderLayer.ts @@ -159,6 +159,11 @@ export class ForegroundRenderLayer extends BaseRenderLayer { * @param char The character to search. */ private _isEmoji(char: string): boolean { + // Check special ambiguous width characters + if (char === '➜') { + return true; + } + // Check emoji unicode range return char.search(/([\uD800-\uDBFF][\uDC00-\uDFFF])/g) >= 0; } From 73d316442637c627e3cee4e54f48cdcef296f534 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 12 Sep 2017 20:00:25 -0700 Subject: [PATCH 230/378] Fix alt+selection while mouse events are enabled Fixes #970 --- src/SelectionManager.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 29f12169..28ed1522 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -454,6 +454,11 @@ export class SelectionManager extends EventEmitter implements ISelectionManager * @param event The mousemove event. */ private _onMouseMove(event: MouseEvent): void { + // If the mousemove listener is active it means that a selection is + // currently being made, we should stop propogation to prevent mouse events + // to be sent to the pty. + event.stopImmediatePropagation(); + // Record the previous position so we know whether to redraw the selection // at the end. const previousSelectionEnd = this._model.selectionEnd ? [this._model.selectionEnd[0], this._model.selectionEnd[1]] : null; From 30cac210036278150ecf7d1a8a62e79737bdd73e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 13 Sep 2017 10:34:58 -0700 Subject: [PATCH 231/378] Remove log --- src/CompositionHelper.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/CompositionHelper.ts b/src/CompositionHelper.ts index cec8fb37..ca5b4f4b 100644 --- a/src/CompositionHelper.ts +++ b/src/CompositionHelper.ts @@ -64,7 +64,6 @@ export class CompositionHelper { * @param {CompositionEvent} ev The event. */ public compositionupdate(ev: CompositionEvent): void { - console.log('compositionupdate'); this.compositionView.textContent = ev.data; this.updateCompositionElements(); setTimeout(() => { From ead45fe4d027eabc01e580006e4751d0b3ded091 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 13 Sep 2017 10:59:55 -0700 Subject: [PATCH 232/378] Add a TODO --- src/renderer/ForegroundRenderLayer.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/renderer/ForegroundRenderLayer.ts b/src/renderer/ForegroundRenderLayer.ts index 9334f109..19625466 100644 --- a/src/renderer/ForegroundRenderLayer.ts +++ b/src/renderer/ForegroundRenderLayer.ts @@ -159,6 +159,7 @@ export class ForegroundRenderLayer extends BaseRenderLayer { * @param char The character to search. */ private _isEmoji(char: string): boolean { + // TODO: We need a generic solution for handling characters like this // Check special ambiguous width characters if (char === '➜') { return true; From a52b9feeea7d3ba877b30f1aa1601ed41317d66c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 13 Sep 2017 14:03:40 -0700 Subject: [PATCH 233/378] Ensure mouse coordinates are always within rows/cols Fixes #986 --- src/utils/Mouse.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/Mouse.ts b/src/utils/Mouse.ts index aef76d5b..7bc39b7f 100644 --- a/src/utils/Mouse.ts +++ b/src/utils/Mouse.ts @@ -53,8 +53,8 @@ export function getCoords(event: {pageX: number, pageY: number}, element: HTMLEl coords[1] = Math.ceil(coords[1] / Math.ceil(charMeasure.height * lineHeight)); // Ensure coordinates are within the terminal viewport. - coords[0] = Math.min(Math.max(coords[0], 1), colCount + 1); - coords[1] = Math.min(Math.max(coords[1], 1), rowCount + 1); + coords[0] = Math.min(Math.max(coords[0], 1), colCount); + coords[1] = Math.min(Math.max(coords[1], 1), rowCount); return coords; } From 5538481833fe7c0add2ded86eec757267f2bf8de Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 13 Sep 2017 15:00:36 -0700 Subject: [PATCH 234/378] Draw characters to a grid --- src/renderer/Renderer.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index a2b8a7fe..63782afd 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -77,13 +77,11 @@ export class Renderer extends EventEmitter implements IRenderer { return; } - // Calculate the scaled character width. Width is kept as a decimal to - // provide better letter spacing, otherwise the text can look odd. - // Characters drawn using this decimal number do have the potential to - // overlap, but only by a single pixel. As such, it's not a big deal when - // they do as that pixel is always cleared as necessary before drawing the - // character. - this.dimensions.scaledCharWidth = this._terminal.charMeasure.width * window.devicePixelRatio; + // Calculate the scaled character width. Width is floored as it must be + // drawn to an integer grid in order for the CharAtlas "stamps" to not be + // blurry. When text is drawn to the grid not using the CharAtlas, it is + // clipped to ensure there is no overlap with the next cell. + this.dimensions.scaledCharWidth = Math.floor(this._terminal.charMeasure.width * window.devicePixelRatio); // Calculate the scaled character height. Height is ceiled in case // devicePixelRatio is a floating point number in order to ensure there is @@ -103,7 +101,7 @@ export class Renderer extends EventEmitter implements IRenderer { // Recalculate the canvas dimensions; scaled* define the actual number of // pixel in the canvas this.dimensions.scaledCanvasHeight = this._terminal.rows * this.dimensions.scaledLineHeight; - this.dimensions.scaledCanvasWidth = Math.round(this._terminal.cols * this.dimensions.scaledCharWidth); + this.dimensions.scaledCanvasWidth = this._terminal.cols * this.dimensions.scaledCharWidth; // The the size of the canvas on the page. It's very important that this // rounds to nearest integer and not ceils as browsers often set From 365661704424f9a33b3a2cb4b85ad144948a86a9 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 13 Sep 2017 15:21:40 -0700 Subject: [PATCH 235/378] Start merging fg/bg layers, draw using SP-AA --- src/renderer/BaseRenderLayer.ts | 3 +- src/renderer/CharAtlas.ts | 2 +- src/renderer/CursorRenderLayer.ts | 2 +- src/renderer/LinkRenderLayer.ts | 2 +- src/renderer/Renderer.ts | 10 +++--- src/renderer/SelectionRenderLayer.ts | 2 +- ...roundRenderLayer.ts => TextRenderLayer.ts} | 35 +++++++++++-------- 7 files changed, 30 insertions(+), 26 deletions(-) rename src/renderer/{ForegroundRenderLayer.ts => TextRenderLayer.ts} (86%) diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 3bc0a917..22d42407 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -25,12 +25,13 @@ export abstract class BaseRenderLayer implements IRenderLayer { container: HTMLElement, id: string, zIndex: number, + alpha: boolean, protected colors: IColorSet ) { this._canvas = document.createElement('canvas'); this._canvas.id = `xterm-${id}-layer`; this._canvas.style.zIndex = zIndex.toString(); - this._ctx = this._canvas.getContext('2d'); + this._ctx = this._canvas.getContext('2d', {alpha}); this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); container.appendChild(this._canvas); } diff --git a/src/renderer/CharAtlas.ts b/src/renderer/CharAtlas.ts index bf080cce..45835a27 100644 --- a/src/renderer/CharAtlas.ts +++ b/src/renderer/CharAtlas.ts @@ -120,7 +120,7 @@ class CharAtlasGenerator { constructor(private _document: Document) { this._canvas = this._document.createElement('canvas'); - this._ctx = this._canvas.getContext('2d'); + this._ctx = this._canvas.getContext('2d', {alpha: false}); this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); } diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index 8e25d7c1..faed9bfe 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -31,7 +31,7 @@ export class CursorRenderLayer extends BaseRenderLayer { private _isFocused: boolean; constructor(container: HTMLElement, zIndex: number, colors: IColorSet) { - super(container, 'cursor', zIndex, colors); + super(container, 'cursor', zIndex, true, colors); this._state = { x: null, y: null, diff --git a/src/renderer/LinkRenderLayer.ts b/src/renderer/LinkRenderLayer.ts index ad178933..98084034 100644 --- a/src/renderer/LinkRenderLayer.ts +++ b/src/renderer/LinkRenderLayer.ts @@ -15,7 +15,7 @@ export class LinkRenderLayer extends BaseRenderLayer { private _state: LinkHoverEvent = null; constructor(container: HTMLElement, zIndex: number, colors: IColorSet, terminal: ILinkifierAccessor) { - super(container, 'link', zIndex, colors); + super(container, 'link', zIndex, true, colors); terminal.linkifier.on(LinkHoverEventTypes.HOVER, (e: LinkHoverEvent) => this._onLinkHover(e)); terminal.linkifier.on(LinkHoverEventTypes.LEAVE, (e: LinkHoverEvent) => this._onLinkLeave(e)); } diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 63782afd..8e8934d6 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -5,8 +5,7 @@ import { ITerminal, ITheme } from '../Interfaces'; import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from '../Buffer'; -import { BackgroundRenderLayer } from './BackgroundRenderLayer'; -import { ForegroundRenderLayer } from './ForegroundRenderLayer'; +import { TextRenderLayer } from './TextRenderLayer'; import { SelectionRenderLayer } from './SelectionRenderLayer'; import { CursorRenderLayer } from './CursorRenderLayer'; import { ColorManager } from './ColorManager'; @@ -30,11 +29,10 @@ export class Renderer extends EventEmitter implements IRenderer { super(); this._colorManager = new ColorManager(); this._renderLayers = [ - new BackgroundRenderLayer(this._terminal.element, 0, this._colorManager.colors), + new TextRenderLayer(this._terminal.element, 0, this._colorManager.colors), new SelectionRenderLayer(this._terminal.element, 1, this._colorManager.colors), - new ForegroundRenderLayer(this._terminal.element, 2, this._colorManager.colors), - new LinkRenderLayer(this._terminal.element, 3, this._colorManager.colors, this._terminal), - new CursorRenderLayer(this._terminal.element, 4, this._colorManager.colors) + new LinkRenderLayer(this._terminal.element, 2, this._colorManager.colors, this._terminal), + new CursorRenderLayer(this._terminal.element, 3, this._colorManager.colors) ]; this.dimensions = { scaledCharWidth: null, diff --git a/src/renderer/SelectionRenderLayer.ts b/src/renderer/SelectionRenderLayer.ts index 5339c7a4..7a0aff83 100644 --- a/src/renderer/SelectionRenderLayer.ts +++ b/src/renderer/SelectionRenderLayer.ts @@ -14,7 +14,7 @@ export class SelectionRenderLayer extends BaseRenderLayer { private _state: {start: [number, number], end: [number, number]}; constructor(container: HTMLElement, zIndex: number, colors: IColorSet) { - super(container, 'selection', zIndex, colors); + super(container, 'selection', zIndex, true, colors); this._state = { start: null, end: null diff --git a/src/renderer/ForegroundRenderLayer.ts b/src/renderer/TextRenderLayer.ts similarity index 86% rename from src/renderer/ForegroundRenderLayer.ts rename to src/renderer/TextRenderLayer.ts index 19625466..4b06ef42 100644 --- a/src/renderer/ForegroundRenderLayer.ts +++ b/src/renderer/TextRenderLayer.ts @@ -18,29 +18,34 @@ import { BaseRenderLayer, INVERTED_DEFAULT_COLOR } from './BaseRenderLayer'; */ const EMOJI_OWNED_CHAR_DATA: CharData = [null, '', 0, -1]; -export class ForegroundRenderLayer extends BaseRenderLayer { - private _state: GridCache; +export class TextRenderLayer extends BaseRenderLayer { + private _fgState: GridCache; + private _bgState: GridCache; constructor(container: HTMLElement, zIndex: number, colors: IColorSet) { - super(container, 'fg', zIndex, colors); - this._state = new GridCache(); + super(container, 'text', zIndex, false, colors); + this._fgState = new GridCache(); + this._bgState = new GridCache(); } public resize(terminal: ITerminal, dim: IRenderDimensions, charSizeChanged: boolean): void { super.resize(terminal, dim, charSizeChanged); // Resizing the canvas discards the contents of the canvas so clear state - this._state.clear(); - this._state.resize(terminal.cols, terminal.rows); + this._fgState.clear(); + this._bgState.clear(); + this._fgState.resize(terminal.cols, terminal.rows); + this._bgState.resize(terminal.cols, terminal.rows); } public reset(terminal: ITerminal): void { - this._state.clear(); + this._fgState.clear(); + this._bgState.clear(); this.clearAll(); } public onGridChanged(terminal: ITerminal, startRow: number, endRow: number): void { // Resize has not been called yet - if (this._state.cache.length === 0) { + if (this._fgState.cache.length === 0) { return; } @@ -58,7 +63,7 @@ export class ForegroundRenderLayer extends BaseRenderLayer { // The character to the left is a wide character, drawing is owned by // the char at x-1 if (width === 0) { - this._state.cache[x][y] = null; + this._fgState.cache[x][y] = null; continue; } @@ -75,10 +80,10 @@ export class ForegroundRenderLayer extends BaseRenderLayer { } // Skip rendering if the character is identical - const state = this._state.cache[x][y]; + const state = this._fgState.cache[x][y]; if (state && state[CHAR_DATA_CHAR_INDEX] === char && state[CHAR_DATA_ATTR_INDEX] === attr) { // Skip render, contents are identical - this._state.cache[x][y] = charData; + this._fgState.cache[x][y] = charData; continue; } @@ -86,7 +91,7 @@ export class ForegroundRenderLayer extends BaseRenderLayer { if (state && state[CHAR_DATA_CODE_INDEX] !== 32 /*' '*/) { this._clearChar(x, y); } - this._state.cache[x][y] = charData; + this._fgState.cache[x][y] = charData; const flags = attr >> 18; @@ -104,14 +109,14 @@ export class ForegroundRenderLayer extends BaseRenderLayer { // space is added. Without this, the first half of `b` would never // get removed, and `a` would not re-render because it thinks it's // already in the correct state. - this._state.cache[x][y] = EMOJI_OWNED_CHAR_DATA; + this._fgState.cache[x][y] = EMOJI_OWNED_CHAR_DATA; if (x < line.length && line[x + 1][CHAR_DATA_CODE_INDEX] === 32 /*' '*/) { width = 2; this._clearChar(x + 1, y); // The emoji owned char data will force a clear and render when the // emoji is no longer to the left of the character and also when the // space changes to another character. - this._state.cache[x + 1][y] = EMOJI_OWNED_CHAR_DATA; + this._fgState.cache[x + 1][y] = EMOJI_OWNED_CHAR_DATA; } } @@ -176,7 +181,7 @@ export class ForegroundRenderLayer extends BaseRenderLayer { private _clearChar(x: number, y: number): void { let colsToClear = 1; // Clear the adjacent character if it was wide - const state = this._state.cache[x][y]; + const state = this._fgState.cache[x][y]; if (state && state[CHAR_DATA_WIDTH_INDEX] === 2) { colsToClear = 2; } From 402cebc76ce45f1784a57521530c110abfeaa0ff Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 13 Sep 2017 22:50:48 -0700 Subject: [PATCH 236/378] Support bold in non cached text drawing Fixes #987 --- src/renderer/BaseRenderLayer.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 22d42407..3f5b2bb6 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -235,7 +235,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { code * charAtlasCellWidth, colorIndex * charAtlasCellHeight, this.scaledCharWidth, this.scaledCharHeight, x * this.scaledCharWidth, y * this.scaledLineHeight + this.scaledLineDrawY, this.scaledCharWidth, this.scaledCharHeight); } else { - this._drawUncachedChar(terminal, char, width, fg, x, y); + this._drawUncachedChar(terminal, char, width, fg, x, y, bold); } // This draws the atlas (for debugging purposes) // this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); @@ -253,9 +253,12 @@ export abstract class BaseRenderLayer implements IRenderLayer { * @param x The column to draw at. * @param y The row to draw at. */ - private _drawUncachedChar(terminal: ITerminal, char: string, width: number, fg: number, x: number, y: number): void { + private _drawUncachedChar(terminal: ITerminal, char: string, width: number, fg: number, x: number, y: number, bold: boolean): void { this._ctx.save(); this._ctx.font = `${terminal.options.fontSize * window.devicePixelRatio}px ${terminal.options.fontFamily}`; + if (bold) { + this._ctx.font = `bold ${this._ctx.font}`; + } this._ctx.textBaseline = 'top'; if (fg === INVERTED_DEFAULT_COLOR) { From 872c007a295a92add19f2b9f7ed5510e8a7fffe8 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 13 Sep 2017 23:15:21 -0700 Subject: [PATCH 237/378] Finish merging bg and fg layers --- src/renderer/BackgroundRenderLayer.ts | 71 --------------------------- src/renderer/BaseRenderLayer.ts | 7 ++- src/renderer/TextRenderLayer.ts | 58 +++++++++++++--------- 3 files changed, 39 insertions(+), 97 deletions(-) delete mode 100644 src/renderer/BackgroundRenderLayer.ts diff --git a/src/renderer/BackgroundRenderLayer.ts b/src/renderer/BackgroundRenderLayer.ts deleted file mode 100644 index 279aee6d..00000000 --- a/src/renderer/BackgroundRenderLayer.ts +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { IColorSet, IRenderDimensions } from './Interfaces'; -import { IBuffer, ICharMeasure, ITerminal } from '../Interfaces'; -import { CHAR_DATA_ATTR_INDEX } from '../Buffer'; -import { GridCache } from './GridCache'; -import { FLAGS } from './Types'; -import { BaseRenderLayer, INVERTED_DEFAULT_COLOR } from './BaseRenderLayer'; - -export class BackgroundRenderLayer extends BaseRenderLayer { - private _state: GridCache; - - constructor(container: HTMLElement, zIndex: number, colors: IColorSet) { - super(container, 'bg', zIndex, colors); - this._state = new GridCache(); - } - - public resize(terminal: ITerminal, dim: IRenderDimensions, charSizeChanged: boolean): void { - super.resize(terminal, dim, charSizeChanged); - // Resizing the canvas discards the contents of the canvas so clear state - this._state.clear(); - this._state.resize(terminal.cols, terminal.rows); - } - - public reset(terminal: ITerminal): void { - this._state.clear(); - this.clearAll(); - } - - public onGridChanged(terminal: ITerminal, startRow: number, endRow: number): void { - // Resize has not been called yet - if (this._state.cache.length === 0) { - return; - } - for (let y = startRow; y <= endRow; y++) { - let row = y + terminal.buffer.ydisp; - let line = terminal.buffer.lines.get(row); - for (let x = 0; x < terminal.cols; x++) { - const attr: number = line[x][CHAR_DATA_ATTR_INDEX]; - let bg = attr & 0x1ff; - const flags = attr >> 18; - - // If inverse flag is on, the background should become the foreground. - if (flags & FLAGS.INVERSE) { - bg = (attr >> 9) & 0x1ff; - if (bg === 257) { - bg = INVERTED_DEFAULT_COLOR; - } - } - - const cellState = this._state.cache[x][y]; - const needsRefresh = (bg < 256 && cellState !== bg) || cellState !== null; - if (needsRefresh) { - if (bg < 256) { - this._ctx.save(); - this._ctx.fillStyle = (bg === INVERTED_DEFAULT_COLOR ? this.colors.foreground : this.colors.ansi[bg]); - this.fillCells(x, y, 1, 1); - this._ctx.restore(); - this._state.cache[x][y] = bg; - } else { - this.clearCells(x, y, 1, 1); - this._state.cache[x][y] = null; - } - } - } - } - } -} diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 3f5b2bb6..38e7b9d4 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -207,9 +207,11 @@ export abstract class BaseRenderLayer implements IRenderLayer { * @param x The column to draw at. * @param y The row to draw at. * @param fg The foreground color, in the format stored within the attributes. + * @param bg The background color, in the format stored within the attributes. + * This is used to validate whether a cached image can be used. * @param bold Whether the text is bold. */ - protected drawChar(terminal: ITerminal, char: string, code: number, width: number, x: number, y: number, fg: number, bold: boolean): void { + protected drawChar(terminal: ITerminal, char: string, code: number, width: number, x: number, y: number, fg: number, bg: number, bold: boolean): void { // Clear the cell next to this character if it's wide if (width === 2) { this.clearCells(x + 1, y, 1, 1); @@ -227,7 +229,8 @@ export abstract class BaseRenderLayer implements IRenderLayer { const isAscii = code < 256; const isBasicColor = (colorIndex > 1 && fg < 16); const isDefaultColor = fg >= 256; - if (isAscii && (isBasicColor || isDefaultColor)) { + const isDefaultBackground = bg >= 256; + if (isAscii && (isBasicColor || isDefaultColor) && isDefaultBackground) { // ImageBitmap's draw about twice as fast as from a canvas const charAtlasCellWidth = this.scaledCharWidth + CHAR_ATLAS_CELL_SPACING; const charAtlasCellHeight = this.scaledCharHeight + CHAR_ATLAS_CELL_SPACING; diff --git a/src/renderer/TextRenderLayer.ts b/src/renderer/TextRenderLayer.ts index 4b06ef42..41fcdeee 100644 --- a/src/renderer/TextRenderLayer.ts +++ b/src/renderer/TextRenderLayer.ts @@ -19,33 +19,28 @@ import { BaseRenderLayer, INVERTED_DEFAULT_COLOR } from './BaseRenderLayer'; const EMOJI_OWNED_CHAR_DATA: CharData = [null, '', 0, -1]; export class TextRenderLayer extends BaseRenderLayer { - private _fgState: GridCache; - private _bgState: GridCache; + private _state: GridCache; constructor(container: HTMLElement, zIndex: number, colors: IColorSet) { super(container, 'text', zIndex, false, colors); - this._fgState = new GridCache(); - this._bgState = new GridCache(); + this._state = new GridCache(); } public resize(terminal: ITerminal, dim: IRenderDimensions, charSizeChanged: boolean): void { super.resize(terminal, dim, charSizeChanged); // Resizing the canvas discards the contents of the canvas so clear state - this._fgState.clear(); - this._bgState.clear(); - this._fgState.resize(terminal.cols, terminal.rows); - this._bgState.resize(terminal.cols, terminal.rows); + this._state.clear(); + this._state.resize(terminal.cols, terminal.rows); } public reset(terminal: ITerminal): void { - this._fgState.clear(); - this._bgState.clear(); + this._state.clear(); this.clearAll(); } public onGridChanged(terminal: ITerminal, startRow: number, endRow: number): void { // Resize has not been called yet - if (this._fgState.cache.length === 0) { + if (this._state.cache.length === 0) { return; } @@ -63,7 +58,7 @@ export class TextRenderLayer extends BaseRenderLayer { // The character to the left is a wide character, drawing is owned by // the char at x-1 if (width === 0) { - this._fgState.cache[x][y] = null; + this._state.cache[x][y] = null; continue; } @@ -80,23 +75,26 @@ export class TextRenderLayer extends BaseRenderLayer { } // Skip rendering if the character is identical - const state = this._fgState.cache[x][y]; + const state = this._state.cache[x][y]; if (state && state[CHAR_DATA_CHAR_INDEX] === char && state[CHAR_DATA_ATTR_INDEX] === attr) { // Skip render, contents are identical - this._fgState.cache[x][y] = charData; + this._state.cache[x][y] = charData; continue; } - // Clear the old character if present - if (state && state[CHAR_DATA_CODE_INDEX] !== 32 /*' '*/) { + // Clear the old character was not a space with the default background + if (state && !(state[CHAR_DATA_CODE_INDEX] === 32 /*' '*/ && (state[CHAR_DATA_ATTR_INDEX] & 0x1ff) >= 256)) { this._clearChar(x, y); } - this._fgState.cache[x][y] = charData; + this._state.cache[x][y] = charData; const flags = attr >> 18; + let bg = attr & 0x1ff; // Skip rendering if the character is invisible - if (!code || code === 32 /*' '*/ || (flags & FLAGS.INVISIBLE)) { + const isDefaultBackground = bg >= 256; + const isInvisible = flags & FLAGS.INVISIBLE; + if (!code || (code === 32 /*' '*/ && isDefaultBackground) || isInvisible) { continue; } @@ -109,14 +107,14 @@ export class TextRenderLayer extends BaseRenderLayer { // space is added. Without this, the first half of `b` would never // get removed, and `a` would not re-render because it thinks it's // already in the correct state. - this._fgState.cache[x][y] = EMOJI_OWNED_CHAR_DATA; + this._state.cache[x][y] = EMOJI_OWNED_CHAR_DATA; if (x < line.length && line[x + 1][CHAR_DATA_CODE_INDEX] === 32 /*' '*/) { width = 2; this._clearChar(x + 1, y); // The emoji owned char data will force a clear and render when the // emoji is no longer to the left of the character and also when the // space changes to another character. - this._fgState.cache[x + 1][y] = EMOJI_OWNED_CHAR_DATA; + this._state.cache[x + 1][y] = EMOJI_OWNED_CHAR_DATA; } } @@ -124,11 +122,23 @@ export class TextRenderLayer extends BaseRenderLayer { // If inverse flag is on, the foreground should become the background. if (flags & FLAGS.INVERSE) { - fg = attr & 0x1ff; - // TODO: Is this case still needed + const temp = bg; + bg = fg; + fg = bg; if (fg === 256) { fg = INVERTED_DEFAULT_COLOR; } + if (bg === 257) { + bg = INVERTED_DEFAULT_COLOR; + } + } + + // Draw background + if (bg < 256) { + this._ctx.save(); + this._ctx.fillStyle = (bg === INVERTED_DEFAULT_COLOR ? this.colors.foreground : this.colors.ansi[bg]); + this.fillCells(x, y, width, 1); + this._ctx.restore(); } this._ctx.save(); @@ -152,7 +162,7 @@ export class TextRenderLayer extends BaseRenderLayer { this.fillBottomLineAtCells(x, y); } - this.drawChar(terminal, char, code, width, x, y, fg, !!(flags & FLAGS.BOLD)); + this.drawChar(terminal, char, code, width, x, y, fg, bg, !!(flags & FLAGS.BOLD)); this._ctx.restore(); } @@ -181,7 +191,7 @@ export class TextRenderLayer extends BaseRenderLayer { private _clearChar(x: number, y: number): void { let colsToClear = 1; // Clear the adjacent character if it was wide - const state = this._fgState.cache[x][y]; + const state = this._state.cache[x][y]; if (state && state[CHAR_DATA_WIDTH_INDEX] === 2) { colsToClear = 2; } From b225866c270a554781ac2116844fc8dbb5acfce9 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 13 Sep 2017 23:21:24 -0700 Subject: [PATCH 238/378] Clear cells properly for text layer --- src/renderer/BaseRenderLayer.ts | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 38e7b9d4..88dfc26c 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -25,7 +25,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { container: HTMLElement, id: string, zIndex: number, - alpha: boolean, + private alpha: boolean, protected colors: IColorSet ) { this._canvas = document.createElement('canvas'); @@ -154,7 +154,12 @@ export abstract class BaseRenderLayer implements IRenderLayer { * Clears the entire canvas. */ protected clearAll(): void { - this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); + if (this.alpha) { + this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); + } else { + this._ctx.fillStyle = this.colors.background; + this._ctx.fillRect(0, 0, this._canvas.width, this._canvas.height); + } } /** @@ -166,11 +171,20 @@ export abstract class BaseRenderLayer implements IRenderLayer { */ protected clearCells(x: number, y: number, width: number, height: number): void { const cellLeft = this._getCellLeft(x); - this._ctx.clearRect( - cellLeft, - y * this.scaledLineHeight, - this._getCellLeft(x + width) - cellLeft, - height * this.scaledLineHeight); + if (this.alpha) { + this._ctx.clearRect( + cellLeft, + y * this.scaledLineHeight, + this._getCellLeft(x + width) - cellLeft, + height * this.scaledLineHeight); + } else { + this._ctx.fillStyle = this.colors.background; + this._ctx.fillRect( + cellLeft, + y * this.scaledLineHeight, + this._getCellLeft(x + width) - cellLeft, + height * this.scaledLineHeight); + } } /** From 0a58628f74b57953d7fec4feef55031f7acb5a5a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 13 Sep 2017 23:49:34 -0700 Subject: [PATCH 239/378] Clear text layer to the background color on creation Prevents a black flash before the first draw --- src/renderer/BaseRenderLayer.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 88dfc26c..d06e0ee7 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -33,6 +33,10 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._canvas.style.zIndex = zIndex.toString(); this._ctx = this._canvas.getContext('2d', {alpha}); this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); + // Draw the background if this is an opaque layer + if (!alpha) { + this.clearAll(); + } container.appendChild(this._canvas); } From 068c0743a1507f64f55a81dcf46e4aa3980882e4 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 13 Sep 2017 23:54:45 -0700 Subject: [PATCH 240/378] Support non-#000 background colors in char atlas --- src/renderer/CharAtlas.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/renderer/CharAtlas.ts b/src/renderer/CharAtlas.ts index 45835a27..34404e12 100644 --- a/src/renderer/CharAtlas.ts +++ b/src/renderer/CharAtlas.ts @@ -64,7 +64,7 @@ export function acquireCharAtlas(terminal: ITerminal, colors: IColorSet, scaledC } const newEntry: ICharAtlasCacheEntry = { - bitmap: generator.generate(scaledCharWidth, scaledCharHeight, terminal.options.fontSize, terminal.options.fontFamily, colors.foreground, colors.ansi), + bitmap: generator.generate(scaledCharWidth, scaledCharHeight, terminal.options.fontSize, terminal.options.fontFamily, colors.background, colors.foreground, colors.ansi), config: newConfig, ownedBy: [terminal] }; @@ -75,7 +75,7 @@ export function acquireCharAtlas(terminal: ITerminal, colors: IColorSet, scaledC function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: ITerminal, colors: IColorSet): ICharAtlasConfig { const clonedColors = { foreground: colors.foreground, - background: null, + background: colors.background, cursor: null, selection: null, ansi: colors.ansi.slice(0, 16) @@ -99,7 +99,8 @@ function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean { a.fontSize === b.fontSize && a.scaledCharWidth === b.scaledCharWidth && a.scaledCharHeight === b.scaledCharHeight && - a.colors.foreground === b.colors.foreground; + a.colors.foreground === b.colors.foreground && + a.colors.background === b.colors.background; } let generator: CharAtlasGenerator; @@ -124,12 +125,15 @@ class CharAtlasGenerator { this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); } - public generate(scaledCharWidth: number, scaledCharHeight: number, fontSize: number, fontFamily: string, foreground: string, ansiColors: string[]): HTMLCanvasElement | Promise { + public generate(scaledCharWidth: number, scaledCharHeight: number, fontSize: number, fontFamily: string, background: string, foreground: string, ansiColors: string[]): HTMLCanvasElement | Promise { const cellWidth = scaledCharWidth + CHAR_ATLAS_CELL_SPACING; const cellHeight = scaledCharHeight + CHAR_ATLAS_CELL_SPACING; this._canvas.width = 255 * cellWidth; this._canvas.height = (/*default+default bold*/2 + /*0-15*/16) * cellHeight; + this._ctx.fillStyle = background; + this._ctx.fillRect(0, 0, this._canvas.width, this._canvas.height); + this._ctx.save(); this._ctx.fillStyle = foreground; this._ctx.font = `${fontSize * window.devicePixelRatio}px ${fontFamily}`; From c2b765f3ffc39f11a1663abfd6c9ce67c2544d17 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 13 Sep 2017 23:56:35 -0700 Subject: [PATCH 241/378] Redraw background on text layer after a resize --- src/renderer/BaseRenderLayer.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index d06e0ee7..3f996545 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -76,6 +76,11 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._canvas.style.width = `${dim.canvasWidth}px`; this._canvas.style.height = `${dim.canvasHeight}px`; + // Draw the background if this is an opaque layer + if (!this.alpha) { + this.clearAll(); + } + if (charSizeChanged) { this._refreshCharAtlas(terminal, this.colors); } From aca7ab63e87899b1e8160f047b49812756eb3574 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 14 Sep 2017 00:22:57 -0700 Subject: [PATCH 242/378] Set background color on Renderer immediately Prevents black flash on init --- src/Terminal.ts | 14 ++--- src/renderer/BaseRenderLayer.ts | 86 ++++++++++++++-------------- src/renderer/ColorManager.ts | 4 +- src/renderer/CursorRenderLayer.ts | 12 ++-- src/renderer/Interfaces.ts | 4 ++ src/renderer/LinkRenderLayer.ts | 2 +- src/renderer/Renderer.ts | 23 ++++---- src/renderer/SelectionRenderLayer.ts | 2 +- src/renderer/TextRenderLayer.ts | 8 +-- 9 files changed, 79 insertions(+), 76 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index ef4b21eb..44320357 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -622,8 +622,11 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.charMeasure = new CharMeasure(document, this.helperContainer); + this.renderer = new Renderer(this, this.options.theme); + this.options.theme = null; this.viewport = new Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasure); - this.renderer = new Renderer(this); + this.viewport.onThemeChanged(this.renderer.colorManager.colors); + this.on('cursormove', () => this.renderer.onCursorMove()); this.on('resize', () => this.renderer.onResize(this.cols, this.rows, false)); this.on('blur', () => this.renderer.onBlur()); @@ -652,15 +655,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT // Measure the character size this.charMeasure.measure(this.options); - // Set the theme if it was set via setOption/constructor before open. This - // must be run after CharMeasure.measure as it depends on char dimensions. - setTimeout(() => { - if (this.options.theme) { - this._setTheme(this.options.theme); - this.options.theme = null; - } - }, 0); - // Setup loop that draws to screen this.refresh(0, this.rows - 1); diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 3f996545..b55b5aaa 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -14,10 +14,10 @@ export const INVERTED_DEFAULT_COLOR = -1; export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; protected _ctx: CanvasRenderingContext2D; - private scaledCharWidth: number; - private scaledCharHeight: number; - private scaledLineHeight: number; - private scaledLineDrawY: number; + private _scaledCharWidth: number; + private _scaledCharHeight: number; + private _scaledLineHeight: number; + private _scaledLineDrawY: number; private _charAtlas: HTMLCanvasElement | ImageBitmap; @@ -25,16 +25,17 @@ export abstract class BaseRenderLayer implements IRenderLayer { container: HTMLElement, id: string, zIndex: number, - private alpha: boolean, - protected colors: IColorSet + private _alpha: boolean, + protected _colors: IColorSet ) { this._canvas = document.createElement('canvas'); this._canvas.id = `xterm-${id}-layer`; this._canvas.style.zIndex = zIndex.toString(); - this._ctx = this._canvas.getContext('2d', {alpha}); + this._ctx = this._canvas.getContext('2d', {_alpha}); this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); // Draw the background if this is an opaque layer - if (!alpha) { + if (!_alpha) { + console.log('clearAll!'); this.clearAll(); } container.appendChild(this._canvas); @@ -58,7 +59,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { */ private _refreshCharAtlas(terminal: ITerminal, colorSet: IColorSet): void { this._charAtlas = null; - const result = acquireCharAtlas(terminal, this.colors, this.scaledCharWidth, this.scaledCharHeight); + const result = acquireCharAtlas(terminal, this._colors, this._scaledCharWidth, this._scaledCharHeight); if (result instanceof HTMLCanvasElement) { this._charAtlas = result; } else { @@ -67,22 +68,22 @@ export abstract class BaseRenderLayer implements IRenderLayer { } public resize(terminal: ITerminal, dim: IRenderDimensions, charSizeChanged: boolean): void { - this.scaledCharWidth = dim.scaledCharWidth; - this.scaledCharHeight = dim.scaledCharHeight; - this.scaledLineHeight = dim.scaledLineHeight; - this.scaledLineDrawY = dim.scaledLineDrawY; + this._scaledCharWidth = dim.scaledCharWidth; + this._scaledCharHeight = dim.scaledCharHeight; + this._scaledLineHeight = dim.scaledLineHeight; + this._scaledLineDrawY = dim.scaledLineDrawY; this._canvas.width = dim.scaledCanvasWidth; this._canvas.height = dim.scaledCanvasHeight; this._canvas.style.width = `${dim.canvasWidth}px`; this._canvas.style.height = `${dim.canvasHeight}px`; // Draw the background if this is an opaque layer - if (!this.alpha) { + if (!this._alpha) { this.clearAll(); } if (charSizeChanged) { - this._refreshCharAtlas(terminal, this.colors); + this._refreshCharAtlas(terminal, this._colors); } } @@ -95,7 +96,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { * @param x The column of the cell. */ private _getCellLeft(x: number): number { - return Math.round(x * this.scaledCharWidth); + return Math.round(x * this._scaledCharWidth); } /** @@ -109,9 +110,9 @@ export abstract class BaseRenderLayer implements IRenderLayer { const cellLeft = this._getCellLeft(x); this._ctx.fillRect( cellLeft, - y * this.scaledLineHeight, + y * this._scaledLineHeight, this._getCellLeft(x + width) - cellLeft, - height * this.scaledLineHeight); + height * this._scaledLineHeight); } /** @@ -124,7 +125,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { const cellLeft = this._getCellLeft(x); this._ctx.fillRect( cellLeft, - (y + 1) * this.scaledLineHeight - window.devicePixelRatio - 1 /* Ensure it's drawn within the cell */, + (y + 1) * this._scaledLineHeight - window.devicePixelRatio - 1 /* Ensure it's drawn within the cell */, this._getCellLeft(x + width) - cellLeft, window.devicePixelRatio); } @@ -138,9 +139,9 @@ export abstract class BaseRenderLayer implements IRenderLayer { protected fillLeftLineAtCell(x: number, y: number): void { this._ctx.fillRect( this._getCellLeft(x), - y * this.scaledLineHeight, + y * this._scaledLineHeight, window.devicePixelRatio, - this.scaledLineHeight); + this._scaledLineHeight); } /** @@ -154,19 +155,20 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._ctx.lineWidth = window.devicePixelRatio; this._ctx.strokeRect( cellLeft + window.devicePixelRatio / 2, - y * this.scaledLineHeight + (window.devicePixelRatio / 2), + y * this._scaledLineHeight + (window.devicePixelRatio / 2), this._getCellLeft(x + width) - cellLeft - window.devicePixelRatio, - (height * this.scaledLineHeight) - window.devicePixelRatio); + (height * this._scaledLineHeight) - window.devicePixelRatio); } /** * Clears the entire canvas. */ protected clearAll(): void { - if (this.alpha) { + if (this._alpha) { this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); } else { - this._ctx.fillStyle = this.colors.background; + console.log('fill with', this._colors.background); + this._ctx.fillStyle = this._colors.background; this._ctx.fillRect(0, 0, this._canvas.width, this._canvas.height); } } @@ -180,19 +182,19 @@ export abstract class BaseRenderLayer implements IRenderLayer { */ protected clearCells(x: number, y: number, width: number, height: number): void { const cellLeft = this._getCellLeft(x); - if (this.alpha) { + if (this._alpha) { this._ctx.clearRect( cellLeft, - y * this.scaledLineHeight, + y * this._scaledLineHeight, this._getCellLeft(x + width) - cellLeft, - height * this.scaledLineHeight); + height * this._scaledLineHeight); } else { - this._ctx.fillStyle = this.colors.background; + this._ctx.fillStyle = this._colors.background; this._ctx.fillRect( cellLeft, - y * this.scaledLineHeight, + y * this._scaledLineHeight, this._getCellLeft(x + width) - cellLeft, - height * this.scaledLineHeight); + height * this._scaledLineHeight); } } @@ -215,9 +217,9 @@ export abstract class BaseRenderLayer implements IRenderLayer { // can bleed into other cells. This code will clip the following fillText, // ensuring that its contents don't go beyond the cell bounds. this._ctx.beginPath(); - this._ctx.rect(x * this.scaledCharWidth, y * this.scaledLineHeight + this.scaledLineDrawY, charData[CHAR_DATA_WIDTH_INDEX] * this.scaledCharWidth, this.scaledCharHeight); + this._ctx.rect(x * this._scaledCharWidth, y * this._scaledLineHeight + this._scaledLineDrawY, charData[CHAR_DATA_WIDTH_INDEX] * this._scaledCharWidth, this._scaledCharHeight); this._ctx.clip(); - this._ctx.fillText(charData[CHAR_DATA_CHAR_INDEX], x * this.scaledCharWidth, y * this.scaledCharHeight); + this._ctx.fillText(charData[CHAR_DATA_CHAR_INDEX], x * this._scaledCharWidth, y * this._scaledCharHeight); } /** @@ -255,11 +257,11 @@ export abstract class BaseRenderLayer implements IRenderLayer { const isDefaultBackground = bg >= 256; if (isAscii && (isBasicColor || isDefaultColor) && isDefaultBackground) { // ImageBitmap's draw about twice as fast as from a canvas - const charAtlasCellWidth = this.scaledCharWidth + CHAR_ATLAS_CELL_SPACING; - const charAtlasCellHeight = this.scaledCharHeight + CHAR_ATLAS_CELL_SPACING; + const charAtlasCellWidth = this._scaledCharWidth + CHAR_ATLAS_CELL_SPACING; + const charAtlasCellHeight = this._scaledCharHeight + CHAR_ATLAS_CELL_SPACING; this._ctx.drawImage(this._charAtlas, - code * charAtlasCellWidth, colorIndex * charAtlasCellHeight, this.scaledCharWidth, this.scaledCharHeight, - x * this.scaledCharWidth, y * this.scaledLineHeight + this.scaledLineDrawY, this.scaledCharWidth, this.scaledCharHeight); + code * charAtlasCellWidth, colorIndex * charAtlasCellHeight, this._scaledCharWidth, this._scaledCharHeight, + x * this._scaledCharWidth, y * this._scaledLineHeight + this._scaledLineDrawY, this._scaledCharWidth, this._scaledCharHeight); } else { this._drawUncachedChar(terminal, char, width, fg, x, y, bold); } @@ -288,12 +290,12 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._ctx.textBaseline = 'top'; if (fg === INVERTED_DEFAULT_COLOR) { - this._ctx.fillStyle = this.colors.background; + this._ctx.fillStyle = this._colors.background; } else if (fg < 256) { // 256 color support - this._ctx.fillStyle = this.colors.ansi[fg]; + this._ctx.fillStyle = this._colors.ansi[fg]; } else { - this._ctx.fillStyle = this.colors.foreground; + this._ctx.fillStyle = this._colors.foreground; } // Since uncached characters are not coming off the char atlas with source @@ -301,11 +303,11 @@ export abstract class BaseRenderLayer implements IRenderLayer { // can bleed into other cells. This code will clip the following fillText, // ensuring that its contents don't go beyond the cell bounds. this._ctx.beginPath(); - this._ctx.rect(x * this.scaledCharWidth, y * this.scaledLineHeight + this.scaledLineDrawY, width * this.scaledCharWidth, this.scaledCharHeight); + this._ctx.rect(x * this._scaledCharWidth, y * this._scaledLineHeight + this._scaledLineDrawY, width * this._scaledCharWidth, this._scaledCharHeight); this._ctx.clip(); // Draw the character - this._ctx.fillText(char, x * this.scaledCharWidth, y * this.scaledLineHeight + this.scaledLineDrawY); + this._ctx.fillText(char, x * this._scaledCharWidth, y * this._scaledLineHeight + this._scaledLineDrawY); this._ctx.restore(); } } diff --git a/src/renderer/ColorManager.ts b/src/renderer/ColorManager.ts index b9afd0e1..746970bb 100644 --- a/src/renderer/ColorManager.ts +++ b/src/renderer/ColorManager.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IColorSet } from './Interfaces'; +import { IColorSet, IColorManager } from './Interfaces'; import { ITheme } from '../Interfaces'; const DEFAULT_FOREGROUND = '#ffffff'; @@ -64,7 +64,7 @@ function toPaddedHex(c: number): string { /** * Manages the source of truth for a terminal's colors. */ -export class ColorManager { +export class ColorManager implements IColorManager { public colors: IColorSet; constructor() { diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index faed9bfe..008b5662 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -135,7 +135,7 @@ export class CursorRenderLayer extends BaseRenderLayer { if (!terminal.isFocused) { this._clearCursor(); this._ctx.save(); - this._ctx.fillStyle = this.colors.cursor; + this._ctx.fillStyle = this._colors.cursor; this._renderBlurCursor(terminal, terminal.buffer.x, viewportRelativeCursorY, charData); this._ctx.restore(); this._state.x = terminal.buffer.x; @@ -190,30 +190,30 @@ export class CursorRenderLayer extends BaseRenderLayer { private _renderBarCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void { this._ctx.save(); - this._ctx.fillStyle = this.colors.cursor; + this._ctx.fillStyle = this._colors.cursor; this.fillLeftLineAtCell(x, y); this._ctx.restore(); } private _renderBlockCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void { this._ctx.save(); - this._ctx.fillStyle = this.colors.cursor; + this._ctx.fillStyle = this._colors.cursor; this.fillCells(x, y, charData[CHAR_DATA_WIDTH_INDEX], 1); - this._ctx.fillStyle = this.colors.background; + this._ctx.fillStyle = this._colors.background; this.fillCharTrueColor(terminal, charData, x, y); this._ctx.restore(); } private _renderUnderlineCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void { this._ctx.save(); - this._ctx.fillStyle = this.colors.cursor; + this._ctx.fillStyle = this._colors.cursor; this.fillBottomLineAtCells(x, y); this._ctx.restore(); } private _renderBlurCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void { this._ctx.save(); - this._ctx.strokeStyle = this.colors.cursor; + this._ctx.strokeStyle = this._colors.cursor; this.strokeRectAtCell(x, y, charData[CHAR_DATA_WIDTH_INDEX], 1); this._ctx.restore(); } diff --git a/src/renderer/Interfaces.ts b/src/renderer/Interfaces.ts index 890b01f5..719ca8cf 100644 --- a/src/renderer/Interfaces.ts +++ b/src/renderer/Interfaces.ts @@ -7,6 +7,7 @@ import { ITerminal, ITerminalOptions, ITheme, IEventEmitter } from '../Interface export interface IRenderer extends IEventEmitter { dimensions: IRenderDimensions; + colorManager: IColorManager; setTheme(theme: ITheme): IColorSet; onWindowResize(devicePixelRatio: number): void; @@ -69,6 +70,9 @@ export interface IRenderLayer { reset(terminal: ITerminal): void; } +export interface IColorManager { + colors: IColorSet; +} export interface IColorSet { foreground: string; diff --git a/src/renderer/LinkRenderLayer.ts b/src/renderer/LinkRenderLayer.ts index 98084034..8f3b63a5 100644 --- a/src/renderer/LinkRenderLayer.ts +++ b/src/renderer/LinkRenderLayer.ts @@ -38,7 +38,7 @@ export class LinkRenderLayer extends BaseRenderLayer { } private _onLinkHover(e: LinkHoverEvent): void { - this._ctx.fillStyle = this.colors.foreground; + this._ctx.fillStyle = this._colors.foreground; this.fillBottomLineAtCells(e.x, e.y, e.length); this._state = e; } diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 8e8934d6..1bdd3b4a 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -22,17 +22,20 @@ export class Renderer extends EventEmitter implements IRenderer { private _renderLayers: IRenderLayer[]; private _devicePixelRatio: number; - private _colorManager: ColorManager; + public colorManager: ColorManager; public dimensions: IRenderDimensions; - constructor(private _terminal: ITerminal) { + constructor(private _terminal: ITerminal, theme: ITheme) { super(); - this._colorManager = new ColorManager(); + this.colorManager = new ColorManager(); + if (theme) { + this.colorManager.setTheme(theme); + } this._renderLayers = [ - new TextRenderLayer(this._terminal.element, 0, this._colorManager.colors), - 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.element, 0, this.colorManager.colors), + 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) ]; this.dimensions = { scaledCharWidth: null, @@ -57,17 +60,17 @@ export class Renderer extends EventEmitter implements IRenderer { } public setTheme(theme: ITheme): IColorSet { - this._colorManager.setTheme(theme); + this.colorManager.setTheme(theme); // Clear layers and force a full render this._renderLayers.forEach(l => { - l.onThemeChanged(this._terminal, this._colorManager.colors); + l.onThemeChanged(this._terminal, this.colorManager.colors); l.reset(this._terminal); }); this._terminal.refresh(0, this._terminal.rows - 1); - return this._colorManager.colors; + return this.colorManager.colors; } public onResize(cols: number, rows: number, didCharSizeChange: boolean): void { diff --git a/src/renderer/SelectionRenderLayer.ts b/src/renderer/SelectionRenderLayer.ts index 7a0aff83..54eb7fc4 100644 --- a/src/renderer/SelectionRenderLayer.ts +++ b/src/renderer/SelectionRenderLayer.ts @@ -68,7 +68,7 @@ export class SelectionRenderLayer extends BaseRenderLayer { // Draw first row const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0; const startRowEndCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : terminal.cols; - this._ctx.fillStyle = this.colors.selection; + this._ctx.fillStyle = this._colors.selection; this.fillCells(startCol, viewportCappedStartRow, startRowEndCol - startCol, 1); // Draw middle rows diff --git a/src/renderer/TextRenderLayer.ts b/src/renderer/TextRenderLayer.ts index 41fcdeee..ced4ceaa 100644 --- a/src/renderer/TextRenderLayer.ts +++ b/src/renderer/TextRenderLayer.ts @@ -136,7 +136,7 @@ export class TextRenderLayer extends BaseRenderLayer { // Draw background if (bg < 256) { this._ctx.save(); - this._ctx.fillStyle = (bg === INVERTED_DEFAULT_COLOR ? this.colors.foreground : this.colors.ansi[bg]); + this._ctx.fillStyle = (bg === INVERTED_DEFAULT_COLOR ? this._colors.foreground : this._colors.ansi[bg]); this.fillCells(x, y, width, 1); this._ctx.restore(); } @@ -152,12 +152,12 @@ export class TextRenderLayer extends BaseRenderLayer { if (flags & FLAGS.UNDERLINE) { if (fg === INVERTED_DEFAULT_COLOR) { - this._ctx.fillStyle = this.colors.background; + this._ctx.fillStyle = this._colors.background; } else if (fg < 256) { // 256 color support - this._ctx.fillStyle = this.colors.ansi[fg]; + this._ctx.fillStyle = this._colors.ansi[fg]; } else { - this._ctx.fillStyle = this.colors.foreground; + this._ctx.fillStyle = this._colors.foreground; } this.fillBottomLineAtCells(x, y); } From 8bdc8fb7a22b0d16a8cfe5e54d9eee42d5216e0f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 14 Sep 2017 00:29:38 -0700 Subject: [PATCH 243/378] Remove log --- src/renderer/BaseRenderLayer.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index b55b5aaa..8f655de5 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -35,7 +35,6 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); // Draw the background if this is an opaque layer if (!_alpha) { - console.log('clearAll!'); this.clearAll(); } container.appendChild(this._canvas); @@ -167,7 +166,6 @@ export abstract class BaseRenderLayer implements IRenderLayer { if (this._alpha) { this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); } else { - console.log('fill with', this._colors.background); this._ctx.fillStyle = this._colors.background; this._ctx.fillRect(0, 0, this._canvas.width, this._canvas.height); } From bc824ad6a50d200009d02a882fbcc6876405a104 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 14 Sep 2017 09:03:41 -0700 Subject: [PATCH 244/378] Fix tests --- src/utils/TestUtils.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utils/TestUtils.test.ts b/src/utils/TestUtils.test.ts index 45cf169a..beed0aaa 100644 --- a/src/utils/TestUtils.test.ts +++ b/src/utils/TestUtils.test.ts @@ -7,7 +7,7 @@ import { ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManag import { LineData } from '../Types'; import { Buffer } from '../Buffer'; import * as Browser from './Browser'; -import { IColorSet, IRenderer, IRenderDimensions } from '../renderer/Interfaces'; +import { IColorSet, IRenderer, IRenderDimensions, IColorManager } from '../renderer/Interfaces'; export class MockTerminal implements ITerminal { renderer: IRenderer; @@ -216,6 +216,7 @@ export class MockBuffer implements IBuffer { } export class MockRenderer implements IRenderer { + colorManager: IColorManager; on(type: string, listener: IListenerType): void { throw new Error('Method not implemented.'); } From 0cf9f1d5364d93b3bca8ed1eb5d9942b902a6de7 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 14 Sep 2017 09:05:50 -0700 Subject: [PATCH 245/378] Fix tests --- src/utils/Mouse.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/Mouse.test.ts b/src/utils/Mouse.test.ts index 4163263b..b96dd68a 100644 --- a/src/utils/Mouse.test.ts +++ b/src/utils/Mouse.test.ts @@ -58,6 +58,6 @@ describe('getCoords', () => { assert.deepEqual(coords, [1, 1]); // Event are double the cols/rows coords = getCoords({ pageX: CHAR_WIDTH * 20, pageY: CHAR_HEIGHT * 20 }, document.createElement('div'), charMeasure, 1, 10, 10); - assert.deepEqual(coords, [11, 11], 'coordinates should never come back as larger than the terminal'); + assert.deepEqual(coords, [10, 10], 'coordinates should never come back as larger than the terminal'); }); }); From cdcedd6b34d799f7b48d75febaf300a5d0855b55 Mon Sep 17 00:00:00 2001 From: Felipe Gasper Date: Fri, 15 Sep 2017 02:31:32 -0400 Subject: [PATCH 246/378] updates to factor out more logic to library --- demo/index.html | 1 + demo/main.js | 251 ++++++++++++++++++++---------------------------- demo/zmodemjs | 2 +- 3 files changed, 105 insertions(+), 149 deletions(-) diff --git a/demo/index.html b/demo/index.html index 60d0b376..264e50ae 100644 --- a/demo/index.html +++ b/demo/index.html @@ -22,6 +22,7 @@ +

xterm.js: xterm, in the browser

diff --git a/demo/main.js b/demo/main.js index f1245cb0..ee4dca70 100644 --- a/demo/main.js +++ b/demo/main.js @@ -138,19 +138,7 @@ function _hide_file_info() { } function _download(xfer, buffer) { - var uint8array = new Uint8Array(buffer); - var blob = new Blob([uint8array]); - var url = URL.createObjectURL(blob); - - var el = document.createElement("a"); - el.style.display = "none"; - el.href = url; - el.download = xfer.get_details().name; - document.body.appendChild(el); - - //It seems like a security problem that this actually works. - //But, hey. - el.click(); + return Zmodem.Browser.save_to_disk(buffer, xfer.get_details().name); } function _update_progress(xfer) { @@ -164,151 +152,118 @@ function _update_progress(xfer) { document.getElementById("percent_received").textContent = percent_received; } +var start_form = document.getElementById("zm_start"); + // END UI STUFF //---------------------------------------------------------------------- -var text_encoder = new TextEncoder(); -var zsentry = new Zmodem.Sentry(); +function _handle_receive_session(zsession) { + zsession.on("offer", function(xfer) { + _show_file_info(xfer); -var zsession; -function handleWSMessage(evt) { - var input = Array.prototype.slice.call( - new Uint8Array( - (typeof evt.data === "string") ? text_encoder.encode(evt.data) : evt.data - ) - ); + var offer_form = document.getElementById("zm_offer"); + offer_form.style.display = ""; + offer_form.onsubmit = function(e) { + offer_form.style.display = "none"; - if (zsession) { - zsession.consume(input); - if (zsession.has_ended()) { - if (zsession.type === "receive") { - input = zsession.get_trailing_bytes(); + //START + if (offer_form.zmaccept.value) { + var FILE_BUFFER = []; + xfer.on("input", (payload) => { + _update_progress(xfer); + FILE_BUFFER.push.apply(FILE_BUFFER, payload); + }); + xfer.accept().then( () => { + _download(xfer, FILE_BUFFER); + _hide_file_info(); + } ); } else { - input = []; + _hide_file_info(); + xfer.skip(); } - zsession = null; - } - else input = []; //keep ZMODEM from going to the terminal + //END + }; + } ); + + zsession.start(); +} + +function _handle_send_session(zsession) { + var choose_form = document.getElementById("zm_choose"); + choose_form.style.display = ""; + choose_form.onsubmit = function(e) { + choose_form.style.display = "none"; + + var file_el = document.getElementById("zm_files"); + var files_obj = file_el.files; + + Zmodem.Browser.send_files( + zsession, + files_obj, + { + on_offer_response(obj, xfer) { + console.log("offer", xfer ? "accepted" : "skipped"); + }, + on_file_complete(obj) { + console.log("COMPLETE", obj); + }, + } + ).then( + zsession.close.bind(zsession), + console.error.bind(console) + ); + }; +} + +var text_encoder = new TextEncoder(); +var zsentry = new Zmodem.Sentry( { + to_terminal(octets) { + term.write( + String.fromCharCode.apply(String, octets) + ); + }, + + sender(octets) { + socket.send( new Uint8Array(octets) ); + }, + + on_retract() { + start_form.style.display = "none"; + start_form.onsubmit = null; + }, + + on_detect(detection) { + start_form.style.display = ""; + start_form.onsubmit = function(e) { + start_form.style.display = "none"; + + //Aborted + if (!e.currentTarget.zmstart.value) { + return; + } + + var zsession = detection.confirm(); + + //HERE + if (zsession.type === "receive") { + _handle_receive_session(zsession); + } + else { + _handle_send_session(zsession); + } + }; + }, +} ); + +function handleWSMessage(evt) { + + //For some reason the first message from the server is text. + if (typeof evt.data === "string") { + term.write(evt.data); } else { - let termbytes; - [termbytes, zsession] = zsentry.parse(input); - input = termbytes; - - if (zsession) { - - zsession.set_sender( (octets) => { - socket.send( new Uint8Array(octets) ); - } ); - - var start_form = document.getElementById("zm_start"); - start_form.style.display = ""; - start_form.onsubmit = function(e) { - start_form.style.display = "none"; - - if (zsession.type === "receive") { - zsession.on("offer", function(xfer) { - _show_file_info(xfer); - - var offer_form = document.getElementById("zm_offer"); - offer_form.style.display = ""; - offer_form.onsubmit = function(e) { - offer_form.style.display = "none"; - - if (offer_form.zmaccept) { - var FILE_BUFFER = []; - xfer.on("input", (payload) => { - _update_progress(xfer); - FILE_BUFFER.push.apply(FILE_BUFFER, payload); - }); - xfer.accept().then( () => { - _download(xfer, FILE_BUFFER); - _hide_file_info(); - } ); - } - else { - _hide_file_info(); - xfer.skip(); - } - }; - } ); - - zsession.start(); - } - else { - var choose_form = document.getElementById("zm_choose"); - choose_form.style.display = ""; - choose_form.onsubmit = function(e) { - choose_form.style.display = "none"; - - var file_el = document.getElementById("zm_files"); - var batch = []; - var total_size = 0; - for (var f=0; f { - if (xfer === undefined) { - return promise_callback(); //skipped - } - - return new Promise( (res) => { - var reader = new FileReader(); - reader.onerror = function(e) { - console.error("file read error", e); - throw("File read error: " + e); - }; - - var offset = xfer.get_offset(); - reader.onprogress = function(e) { - xfer.send( - new Uint8Array(e.target.result, offset) - ); - offset = e.loaded; - }; - - reader.onload = function(e) { - xfer.end( - new Uint8Array(e.target.result, offset) - ).then(res).then(promise_callback); - }; - - reader.readAsArrayBuffer(cur_b.obj); - } ); - } ) - } - else { - zsession.close(); - choose_form.style.display = "none"; - } - } - - promise_callback(); - }; - - } - }; - } - } - - if (input.length) { - term.write( - String.fromCharCode.apply(String, input) - ); + zsentry.consume(evt.data); } } diff --git a/demo/zmodemjs b/demo/zmodemjs index 7220230d..f19e3fcf 160000 --- a/demo/zmodemjs +++ b/demo/zmodemjs @@ -1 +1 @@ -Subproject commit 7220230d12ce5eb4c42c59442a5924d98abc1c1f +Subproject commit f19e3fcfc0bc80eaa25e4cbae1dedf26ca7d6b2b From ce681fe48165c9297b76ba8891c14f3b57cbf9a9 Mon Sep 17 00:00:00 2001 From: Felipe Gasper Date: Fri, 15 Sep 2017 03:26:54 -0400 Subject: [PATCH 247/378] demo: hide progress after receive --- demo/main.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/demo/main.js b/demo/main.js index ee4dca70..54150b15 100644 --- a/demo/main.js +++ b/demo/main.js @@ -152,6 +152,10 @@ function _update_progress(xfer) { document.getElementById("percent_received").textContent = percent_received; } +function _hide_progress() { + document.getElementById("zm_progress").style.display = "none"; +} + var start_form = document.getElementById("zm_start"); // END UI STUFF @@ -176,6 +180,7 @@ function _handle_receive_session(zsession) { xfer.accept().then( () => { _download(xfer, FILE_BUFFER); _hide_file_info(); + _hide_progress(); } ); } else { From 5f62c7607d4aac41c81b7da2f64981a2f5797772 Mon Sep 17 00:00:00 2001 From: Felipe Gasper Date: Fri, 15 Sep 2017 03:30:51 -0400 Subject: [PATCH 248/378] progress indicators for send --- demo/main.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/demo/main.js b/demo/main.js index 54150b15..69b86aba 100644 --- a/demo/main.js +++ b/demo/main.js @@ -210,11 +210,14 @@ function _handle_send_session(zsession) { on_offer_response(obj, xfer) { console.log("offer", xfer ? "accepted" : "skipped"); }, + on_progress(obj, xfer) { + _update_progress(xfer); + }, on_file_complete(obj) { console.log("COMPLETE", obj); }, } - ).then( + ).then(_hide_progress).then( zsession.close.bind(zsession), console.error.bind(console) ); From 94586b669f7310a39b6764aca6e6e50b668a4403 Mon Sep 17 00:00:00 2001 From: Felipe Gasper Date: Fri, 15 Sep 2017 03:45:23 -0400 Subject: [PATCH 249/378] update submodule --- demo/zmodemjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/zmodemjs b/demo/zmodemjs index f19e3fcf..a48daa36 160000 --- a/demo/zmodemjs +++ b/demo/zmodemjs @@ -1 +1 @@ -Subproject commit f19e3fcfc0bc80eaa25e4cbae1dedf26ca7d6b2b +Subproject commit a48daa360ad4c31ce6f8c77042b34d0b4f74e930 From 9b6c4e046f61c39470d9d7ed295df03d55dc68df Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 16 Sep 2017 10:57:49 -0700 Subject: [PATCH 250/378] Only refresh char atlas when char dimensions are valid See Microsoft/vscode#34493 --- src/renderer/BaseRenderLayer.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 8f655de5..4cc07472 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -57,6 +57,9 @@ export abstract class BaseRenderLayer implements IRenderLayer { * @param colorSet The color set to use for the char atlas. */ private _refreshCharAtlas(terminal: ITerminal, colorSet: IColorSet): void { + if (this._scaledCharWidth > 0 && this._scaledCharHeight > 0) { + return; + } this._charAtlas = null; const result = acquireCharAtlas(terminal, this._colors, this._scaledCharWidth, this._scaledCharHeight); if (result instanceof HTMLCanvasElement) { From c8153104184622a9a243a24110acf27d9be7665b Mon Sep 17 00:00:00 2001 From: Thomas Zilz Date: Sat, 16 Sep 2017 20:36:55 +0200 Subject: [PATCH 251/378] Fix inverted cells, make sure inverted cells are cleared correctly (#996) * Corretly inverse foreground and background. Be more conservative when clearing * add missing space * Re-enable optimisation with better checks * Fix insversed check --- src/renderer/TextRenderLayer.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/renderer/TextRenderLayer.ts b/src/renderer/TextRenderLayer.ts index ced4ceaa..74997866 100644 --- a/src/renderer/TextRenderLayer.ts +++ b/src/renderer/TextRenderLayer.ts @@ -83,7 +83,8 @@ export class TextRenderLayer extends BaseRenderLayer { } // Clear the old character was not a space with the default background - if (state && !(state[CHAR_DATA_CODE_INDEX] === 32 /*' '*/ && (state[CHAR_DATA_ATTR_INDEX] & 0x1ff) >= 256)) { + const wasInverted = !!(state && state[CHAR_DATA_ATTR_INDEX] && state[CHAR_DATA_ATTR_INDEX] >> 18 & FLAGS.INVERSE); + if (state && !(state[CHAR_DATA_CODE_INDEX] === 32 /*' '*/ && (state[CHAR_DATA_ATTR_INDEX] & 0x1ff) >= 256 && !wasInverted)) { this._clearChar(x, y); } this._state.cache[x][y] = charData; @@ -94,7 +95,8 @@ export class TextRenderLayer extends BaseRenderLayer { // Skip rendering if the character is invisible const isDefaultBackground = bg >= 256; const isInvisible = flags & FLAGS.INVISIBLE; - if (!code || (code === 32 /*' '*/ && isDefaultBackground) || isInvisible) { + const isInverted = flags & FLAGS.INVERSE; + if (!code || (code === 32 /*' '*/ && isDefaultBackground && !isInverted) || isInvisible) { continue; } @@ -121,10 +123,10 @@ export class TextRenderLayer extends BaseRenderLayer { let fg = (attr >> 9) & 0x1ff; // If inverse flag is on, the foreground should become the background. - if (flags & FLAGS.INVERSE) { + if (isInverted) { const temp = bg; bg = fg; - fg = bg; + fg = temp; if (fg === 256) { fg = INVERTED_DEFAULT_COLOR; } From c4da8325ace6e4bde77822377bf994d389a7c75a Mon Sep 17 00:00:00 2001 From: Thomas Zilz Date: Sun, 17 Sep 2017 10:39:09 +0200 Subject: [PATCH 252/378] Allow all overflowing characters to extend to the next cell if followed by a space (Emojis etc) (#997) * Measure characters and thread them if they are overlapping * Fix whitespace * Use charData and ctx.save/restore, code cleanup * Fix trailing whitespace --- src/renderer/TextRenderLayer.ts | 79 +++++++++++++++++++++++---------- 1 file changed, 55 insertions(+), 24 deletions(-) diff --git a/src/renderer/TextRenderLayer.ts b/src/renderer/TextRenderLayer.ts index 74997866..e319efe7 100644 --- a/src/renderer/TextRenderLayer.ts +++ b/src/renderer/TextRenderLayer.ts @@ -16,10 +16,13 @@ import { BaseRenderLayer, INVERTED_DEFAULT_COLOR } from './BaseRenderLayer'; * when the character changes (a regular space ' ' character may not as it's * drawn state is a cleared cell). */ -const EMOJI_OWNED_CHAR_DATA: CharData = [null, '', 0, -1]; +const OVERLAP_OWNED_CHAR_DATA: CharData = [null, '', 0, -1]; export class TextRenderLayer extends BaseRenderLayer { private _state: GridCache; + private _characterWidth: number; + private _characterFont: string; + private _characterOverlapCache: { [key: string]: boolean } = {}; constructor(container: HTMLElement, zIndex: number, colors: IColorSet) { super(container, 'text', zIndex, false, colors); @@ -28,6 +31,14 @@ export class TextRenderLayer extends BaseRenderLayer { public resize(terminal: ITerminal, dim: IRenderDimensions, charSizeChanged: boolean): void { super.resize(terminal, dim, charSizeChanged); + + // Clear the character width cache if the font or width has changed + const terminalFont = `${terminal.options.fontSize * window.devicePixelRatio}px ${terminal.options.fontFamily}`; + if (this._characterWidth !== dim.scaledCharWidth || this._characterFont !== terminalFont) { + this._characterWidth = dim.scaledCharWidth; + this._characterFont = terminalFont; + this._characterOverlapCache = {}; + } // Resizing the canvas discards the contents of the canvas so clear state this._state.clear(); this._state.resize(terminal.cols, terminal.rows); @@ -63,12 +74,12 @@ export class TextRenderLayer extends BaseRenderLayer { } // If the character is a space and the character to the left is an - // emoji, skip the character and allow the emoji char to take full - // control over this character's cell. + // overlapping character, skip the character and allow the overlapping + // char to take full control over this character's cell. if (code === 32 /*' '*/) { if (x > 0) { const previousChar: CharData = line[x - 1]; - if (this._isEmoji(previousChar[CHAR_DATA_CHAR_INDEX])) { + if (this._isOverlapping(previousChar)) { continue; } } @@ -100,23 +111,23 @@ export class TextRenderLayer extends BaseRenderLayer { continue; } - // If the character is an emoji and the character to the right is a + // If the character is an overlapping char and the character to the right is a // space, take ownership of the cell to the right. - if (this._isEmoji(char)) { - // If the character is an emoji, we want to force a re-render on every + if (width !== 0 && this._isOverlapping(charData)) { + // If the character is overlapping, we want to force a re-render on every // frame. This is specifically to work around the case where two - // emoji's `a` and `b` are adjacent, the cursor is moved to b and a + // overlaping chars `a` and `b` are adjacent, the cursor is moved to b and a // space is added. Without this, the first half of `b` would never // get removed, and `a` would not re-render because it thinks it's // already in the correct state. - this._state.cache[x][y] = EMOJI_OWNED_CHAR_DATA; + this._state.cache[x][y] = OVERLAP_OWNED_CHAR_DATA; if (x < line.length && line[x + 1][CHAR_DATA_CODE_INDEX] === 32 /*' '*/) { width = 2; this._clearChar(x + 1, y); - // The emoji owned char data will force a clear and render when the - // emoji is no longer to the left of the character and also when the - // space changes to another character. - this._state.cache[x + 1][y] = EMOJI_OWNED_CHAR_DATA; + // The overlapping char's char data will force a clear and render when the + // overlapping char is no longer to the left of the character and also when + // the space changes to another character. + this._state.cache[x + 1][y] = OVERLAP_OWNED_CHAR_DATA; } } @@ -171,18 +182,38 @@ export class TextRenderLayer extends BaseRenderLayer { } } - /** - * Whether the character is an emoji. - * @param char The character to search. - */ - private _isEmoji(char: string): boolean { - // TODO: We need a generic solution for handling characters like this - // Check special ambiguous width characters - if (char === '➜') { - return true; + /** + * Whether a character is overlapping to the + * next cell. + */ + private _isOverlapping(charData: CharData): boolean { + // We assume that any ascii character will not overlap + const code = charData[CHAR_DATA_CODE_INDEX]; + if (code < 256) { + return false; } - // Check emoji unicode range - return char.search(/([\uD800-\uDBFF][\uDC00-\uDFFF])/g) >= 0; + + // Deliver from cache if available + const char = charData[CHAR_DATA_CHAR_INDEX]; + if (this._characterOverlapCache.hasOwnProperty(char)) { + return this._characterOverlapCache[char]; + } + + // Setup the font + this._ctx.save(); + this._ctx.font = this._characterFont; + + // Measure the width of the character, but Math.floor it + // because that is what the renderer does when it calculates + // the character dimensions we are comparing against + const overlaps = Math.floor(this._ctx.measureText(char).width) > this._characterWidth; + + // Restore the original context + this._ctx.restore(); + + // Cache and return + this._characterOverlapCache[char] = overlaps; + return overlaps; } /** From 65f5f1c38c7dda72bf6b4befa89cd4654c859f56 Mon Sep 17 00:00:00 2001 From: Felipe Gasper Date: Mon, 18 Sep 2017 07:19:22 -0400 Subject: [PATCH 253/378] deny() handling --- demo/main.js | 19 +++++++++---------- demo/zmodemjs | 2 +- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/demo/main.js b/demo/main.js index 69b86aba..9fcdbda2 100644 --- a/demo/main.js +++ b/demo/main.js @@ -246,19 +246,18 @@ var zsentry = new Zmodem.Sentry( { start_form.onsubmit = function(e) { start_form.style.display = "none"; - //Aborted - if (!e.currentTarget.zmstart.value) { - return; - } + if (e.currentTarget.zmstart.value) { + let zsession = detection.confirm(); - var zsession = detection.confirm(); - - //HERE - if (zsession.type === "receive") { - _handle_receive_session(zsession); + if (zsession.type === "receive") { + _handle_receive_session(zsession); + } + else { + _handle_send_session(zsession); + } } else { - _handle_send_session(zsession); + detection.deny(); } }; }, diff --git a/demo/zmodemjs b/demo/zmodemjs index a48daa36..46c04daf 160000 --- a/demo/zmodemjs +++ b/demo/zmodemjs @@ -1 +1 @@ -Subproject commit a48daa360ad4c31ce6f8c77042b34d0b4f74e930 +Subproject commit 46c04daf223891ed850ef8cdb8327812df9c7a57 From 40fdd00b9e1334c94618a69e60bee6538ff7eb36 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 20 Sep 2017 09:02:21 +0900 Subject: [PATCH 254/378] Correct if statement, ensure charAtlas exists --- src/renderer/BaseRenderLayer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 4cc07472..2bf49e68 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -57,7 +57,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { * @param colorSet The color set to use for the char atlas. */ private _refreshCharAtlas(terminal: ITerminal, colorSet: IColorSet): void { - if (this._scaledCharWidth > 0 && this._scaledCharHeight > 0) { + if (this._scaledCharWidth <= 0 && this._scaledCharHeight <= 0) { return; } this._charAtlas = null; @@ -256,7 +256,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { const isBasicColor = (colorIndex > 1 && fg < 16); const isDefaultColor = fg >= 256; const isDefaultBackground = bg >= 256; - if (isAscii && (isBasicColor || isDefaultColor) && isDefaultBackground) { + if (this._charAtlas && isAscii && (isBasicColor || isDefaultColor) && isDefaultBackground) { // ImageBitmap's draw about twice as fast as from a canvas const charAtlasCellWidth = this._scaledCharWidth + CHAR_ATLAS_CELL_SPACING; const charAtlasCellHeight = this._scaledCharHeight + CHAR_ATLAS_CELL_SPACING; From 79c07d436e5f5d6a5cb1f875dbafb7b51137b1f9 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 20 Sep 2017 11:53:19 +0900 Subject: [PATCH 255/378] Fix selection horizontal offset Fixes #1000 --- src/utils/Mouse.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utils/Mouse.ts b/src/utils/Mouse.ts index 7bc39b7f..66e7f190 100644 --- a/src/utils/Mouse.ts +++ b/src/utils/Mouse.ts @@ -49,7 +49,8 @@ export function getCoords(event: {pageX: number, pageY: number}, element: HTMLEl } // Convert to cols/rows. - coords[0] = Math.ceil((coords[0] + (isSelection ? charMeasure.width / 2 : 0)) / charMeasure.width); + const flooredCharWidth = Math.floor(charMeasure.width); + coords[0] = Math.ceil((coords[0] + (isSelection ? flooredCharWidth / 2 : 0)) / flooredCharWidth); coords[1] = Math.ceil(coords[1] / Math.ceil(charMeasure.height * lineHeight)); // Ensure coordinates are within the terminal viewport. From 0b54df14e2cda3f1c160e8031e8238fe3a81d1c7 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 20 Sep 2017 12:16:27 +0900 Subject: [PATCH 256/378] Remove unnecessary rounding --- src/renderer/BaseRenderLayer.ts | 40 +++++++++++---------------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 8f655de5..16de826e 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -88,16 +88,6 @@ export abstract class BaseRenderLayer implements IRenderLayer { public abstract reset(terminal: ITerminal): void; - /** - * Gets the left position of a cell. Since character width is stored as a - * float in order to prevent bad letter spacing, drawing shapes in the cell - * need to be rounded. - * @param x The column of the cell. - */ - private _getCellLeft(x: number): number { - return Math.round(x * this._scaledCharWidth); - } - /** * Fills 1+ cells completely. This uses the existing fillStyle on the context. * @param x The column to start at. @@ -106,12 +96,11 @@ export abstract class BaseRenderLayer implements IRenderLayer { * @param height The number of rows to fill. */ protected fillCells(x: number, y: number, width: number, height: number): void { - const cellLeft = this._getCellLeft(x); this._ctx.fillRect( - cellLeft, - y * this._scaledLineHeight, - this._getCellLeft(x + width) - cellLeft, - height * this._scaledLineHeight); + x * this._scaledCharWidth, + y * this._scaledLineHeight, + width * this._scaledCharWidth, + height * this._scaledLineHeight); } /** @@ -121,11 +110,10 @@ export abstract class BaseRenderLayer implements IRenderLayer { * @param y The row to fill. */ protected fillBottomLineAtCells(x: number, y: number, width: number = 1): void { - const cellLeft = this._getCellLeft(x); this._ctx.fillRect( - cellLeft, + x * this._scaledCharWidth, (y + 1) * this._scaledLineHeight - window.devicePixelRatio - 1 /* Ensure it's drawn within the cell */, - this._getCellLeft(x + width) - cellLeft, + width * this._scaledCharWidth, window.devicePixelRatio); } @@ -137,7 +125,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { */ protected fillLeftLineAtCell(x: number, y: number): void { this._ctx.fillRect( - this._getCellLeft(x), + x * this._scaledCharWidth, y * this._scaledLineHeight, window.devicePixelRatio, this._scaledLineHeight); @@ -150,12 +138,11 @@ export abstract class BaseRenderLayer implements IRenderLayer { * @param y The row to fill. */ protected strokeRectAtCell(x: number, y: number, width: number, height: number): void { - const cellLeft = this._getCellLeft(x); this._ctx.lineWidth = window.devicePixelRatio; this._ctx.strokeRect( - cellLeft + window.devicePixelRatio / 2, + x * this._scaledCharWidth + window.devicePixelRatio / 2, y * this._scaledLineHeight + (window.devicePixelRatio / 2), - this._getCellLeft(x + width) - cellLeft - window.devicePixelRatio, + width * this._scaledCharWidth - window.devicePixelRatio, (height * this._scaledLineHeight) - window.devicePixelRatio); } @@ -179,19 +166,18 @@ export abstract class BaseRenderLayer implements IRenderLayer { * @param height The number of rows to clear. */ protected clearCells(x: number, y: number, width: number, height: number): void { - const cellLeft = this._getCellLeft(x); if (this._alpha) { this._ctx.clearRect( - cellLeft, + x * this._scaledCharWidth, y * this._scaledLineHeight, - this._getCellLeft(x + width) - cellLeft, + width * this._scaledCharWidth, height * this._scaledLineHeight); } else { this._ctx.fillStyle = this._colors.background; this._ctx.fillRect( - cellLeft, + x * this._scaledCharWidth, y * this._scaledLineHeight, - this._getCellLeft(x + width) - cellLeft, + width * this._scaledCharWidth, height * this._scaledLineHeight); } } From 8243eef67ca6bc81a77d76b95cc4fd78d3ba8e6d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 20 Sep 2017 12:54:20 +0900 Subject: [PATCH 257/378] Properly clear links MouzeZoneManager rows are not 0-based Fixes #990 --- src/input/MouseZoneManager.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/input/MouseZoneManager.ts b/src/input/MouseZoneManager.ts index c7feed2a..026e79c2 100644 --- a/src/input/MouseZoneManager.ts +++ b/src/input/MouseZoneManager.ts @@ -50,10 +50,16 @@ export class MouseZoneManager implements IMouseZoneManager { return; } + // Clear all if start/end weren't set + if (!end) { + start = 0; + end = this._terminal.rows - 1; + } + // Iterate through zones and clear them out if they're within the range for (let i = 0; i < this._zones.length; i++) { const zone = this._zones[i]; - if (zone.y >= start && zone.y <= end) { + if (zone.y > start && zone.y <= end + 1) { if (this._currentZone && this._currentZone === zone) { this._currentZone.leaveCallback(); this._currentZone = null; From a2389d5c5b46d14797345ae75ba0bcc5b884f521 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 24 Sep 2017 10:59:38 -0700 Subject: [PATCH 258/378] Fix validation callback to use the uri, not whole row --- src/Linkifier.test.ts | 11 +++++++++++ src/Linkifier.ts | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Linkifier.test.ts b/src/Linkifier.test.ts index df9e459c..0878aafc 100644 --- a/src/Linkifier.test.ts +++ b/src/Linkifier.test.ts @@ -156,6 +156,17 @@ describe('Linkifier', () => { linkifier.linkifyRows(); }); + it('should validate the uri, not the row', done => { + addRow('abc test abc'); + linkifier.registerLinkMatcher(/test/, () => done(), { + validationCallback: (uri, cb) => { + assert.equal(uri, 'test'); + done(); + } + }); + linkifier.linkifyRows(); + }); + it('should disable link if false', done => { addRow('test'); linkifier.registerLinkMatcher(/test/, () => assert.fail(), { diff --git a/src/Linkifier.ts b/src/Linkifier.ts index fa47271c..42d20f9c 100644 --- a/src/Linkifier.ts +++ b/src/Linkifier.ts @@ -236,7 +236,7 @@ export class Linkifier extends EventEmitter implements ILinkifier { // Ensure the link is valid before registering if (matcher.validationCallback) { - matcher.validationCallback(text, isValid => { + matcher.validationCallback(uri, isValid => { // Discard link if the line has already changed if (this._rowsTimeoutId) { return; From 3f467b33e4d7431c85a836d1e0a0601093759af6 Mon Sep 17 00:00:00 2001 From: Felipe Gasper Date: Tue, 26 Sep 2017 10:31:24 -0400 Subject: [PATCH 259/378] update demo to latest zmodem.js code --- demo/index.html | 2 ++ demo/main.js | 19 +++++++++++++++---- demo/zmodemjs | 2 +- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/demo/index.html b/demo/index.html index 264e50ae..cbcc8043 100644 --- a/demo/index.html +++ b/demo/index.html @@ -22,6 +22,7 @@ + @@ -56,6 +57,7 @@
+

ZMODEM file transfers are supported via an addon. To try it out, install lrzsz onto the remote peer, then type rz to send from your browser or sz <file> to send from the remote peer.

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.

From ed6e2e5055014a649efe418d14bfaf48f5972adf Mon Sep 17 00:00:00 2001 From: Felipe Gasper Date: Tue, 17 Oct 2017 09:28:41 +0200 Subject: [PATCH 310/378] Reorder demo components, ensure we hide at end of receive. --- demo/index.html | 24 +++++++++++++++--------- demo/main.js | 12 ++++++++++-- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/demo/index.html b/demo/index.html index f808ccbe..dea1e400 100644 --- a/demo/index.html +++ b/demo/index.html @@ -34,15 +34,6 @@ - - + +