diff options
author | Jacob Schatz <jschatz1@gmail.com> | 2016-10-03 15:54:08 -0400 |
---|---|---|
committer | Jacob Schatz <jschatz1@gmail.com> | 2016-10-03 15:54:08 -0400 |
commit | 10a2fb7ba9c2be14ecb5844701c109359ed60cb9 (patch) | |
tree | f5e8ae9fd0fb837e99cf2b2452cc0c329e524172 | |
parent | 1fed97530af84b7660e45a9a8d4e3dafbed026f7 (diff) | |
download | gitlab-ce-terminal.tar.gz |
Initial terminalterminal
-rw-r--r-- | app/assets/javascripts/terminal/terminal_bundle.js.es6 | 1 | ||||
-rw-r--r-- | app/controllers/projects/merge_requests_controller.rb | 4 | ||||
-rw-r--r-- | app/views/projects/merge_requests/_show.html.haml | 1 | ||||
-rw-r--r-- | app/views/projects/merge_requests/terminal.html.haml | 5 | ||||
-rw-r--r-- | config/application.rb | 1 | ||||
-rw-r--r-- | config/routes.rb | 1 | ||||
-rw-r--r-- | vendor/assets/javascripts/term.js | 5977 |
7 files changed, 5990 insertions, 0 deletions
diff --git a/app/assets/javascripts/terminal/terminal_bundle.js.es6 b/app/assets/javascripts/terminal/terminal_bundle.js.es6 new file mode 100644 index 00000000000..df213d912f2 --- /dev/null +++ b/app/assets/javascripts/terminal/terminal_bundle.js.es6 @@ -0,0 +1 @@ +//= require term
\ No newline at end of file diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 68bb4232f5b..5b9b14d7cbd 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -185,6 +185,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController end end + def terminal + return 'TERMINAL' + end + def builds respond_to do |format| format.html do diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index d03ff9ec7e8..f60b961340b 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -21,6 +21,7 @@ Run in IDE (Koding) = link_to "#modal_merge_info", class: "btn inline btn-grouped btn-sm", "data-toggle" => "modal" do Check out branch + = link_to "Terminal", terminal_namespace_project_merge_request_path, class: 'btn inline btn-sm' %span.dropdown.inline.prepend-left-5 %a.btn.btn-sm.dropdown-toggle{ data: {toggle: :dropdown} } diff --git a/app/views/projects/merge_requests/terminal.html.haml b/app/views/projects/merge_requests/terminal.html.haml new file mode 100644 index 00000000000..6348ed5017b --- /dev/null +++ b/app/views/projects/merge_requests/terminal.html.haml @@ -0,0 +1,5 @@ + +- content_for :page_specific_javascripts do + = page_specific_javascript_tag('terminal/terminal_bundle.js') + +%script{:src => 'https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular.js'}
\ No newline at end of file diff --git a/config/application.rb b/config/application.rb index 5dbe5a8120b..994f136d3e5 100644 --- a/config/application.rb +++ b/config/application.rb @@ -87,6 +87,7 @@ module Gitlab config.assets.precompile << "profile/profile_bundle.js" config.assets.precompile << "diff_notes/diff_notes_bundle.js" config.assets.precompile << "boards/boards_bundle.js" + config.assets.precompile << "terminal/terminal_bundle.js" config.assets.precompile << "boards/test_utils/simulate_drag.js" config.assets.precompile << "blob_edit/blob_edit_bundle.js" config.assets.precompile << "snippet/snippet_bundle.js" diff --git a/config/routes.rb b/config/routes.rb index ba3864b92be..869e4133ae4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -730,6 +730,7 @@ Rails.application.routes.draw do resources :merge_requests, concerns: :awardable, constraints: { id: /\d+/ } do member do + get :terminal get :commits get :diffs get :conflicts diff --git a/vendor/assets/javascripts/term.js b/vendor/assets/javascripts/term.js new file mode 100644 index 00000000000..68bc07343f5 --- /dev/null +++ b/vendor/assets/javascripts/term.js @@ -0,0 +1,5977 @@ +/** + * term.js - an xterm emulator + * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) + * https://github.com/chjj/term.js + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * 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. + */ + +;(function() { + +/** + * 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 + */ + +'use strict'; + +/** + * Shared + */ + +var window = this + , document = this.document; + +/** + * EventEmitter + */ + +function EventEmitter() { + this._events = this._events || {}; +} + +EventEmitter.prototype.addListener = function(type, listener) { + this._events[type] = this._events[type] || []; + this._events[type].push(listener); +}; + +EventEmitter.prototype.on = EventEmitter.prototype.addListener; + +EventEmitter.prototype.removeListener = function(type, listener) { + if (!this._events[type]) return; + + var obj = this._events[type] + , i = obj.length; + + while (i--) { + if (obj[i] === listener || obj[i].listener === listener) { + obj.splice(i, 1); + return; + } + } +}; + +EventEmitter.prototype.off = EventEmitter.prototype.removeListener; + +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.removeListener(type, on); + return listener.apply(this, args); + } + on.listener = listener; + return this.on(type, on); +}; + +EventEmitter.prototype.emit = function(type) { + if (!this._events[type]) return; + + var args = Array.prototype.slice.call(arguments, 1) + , obj = this._events[type] + , l = obj.length + , i = 0; + + for (; i < l; i++) { + obj[i].apply(this, args); + } +}; + +EventEmitter.prototype.listeners = function(type) { + return this._events[type] = this._events[type] || []; +}; + +/** + * Stream + */ + +function Stream() { + EventEmitter.call(this); +} + +inherits(Stream, EventEmitter); + +Stream.prototype.pipe = function(dest, options) { + var src = this + , ondata + , onerror + , onend; + + function unbind() { + src.removeListener('data', ondata); + src.removeListener('error', onerror); + src.removeListener('end', onend); + dest.removeListener('error', onerror); + dest.removeListener('close', unbind); + } + + src.on('data', ondata = function(data) { + dest.write(data); + }); + + src.on('error', onerror = function(err) { + unbind(); + if (!this.listeners('error').length) { + throw err; + } + }); + + src.on('end', onend = function() { + dest.end(); + unbind(); + }); + + dest.on('error', onerror); + dest.on('close', unbind); + + dest.emit('pipe', src); + + return dest; +}; + +/** + * States + */ + +var normal = 0 + , escaped = 1 + , csi = 2 + , osc = 3 + , charset = 4 + , dcs = 5 + , ignore = 6 + , UDK = { type: 'udk' }; + +/** + * Terminal + */ + +function Terminal(options) { + var self = this; + + if (!(this instanceof Terminal)) { + return new Terminal(arguments[0], arguments[1], arguments[2]); + } + + Stream.call(this); + + if (typeof options === 'number') { + options = { + cols: arguments[0], + rows: arguments[1], + handler: arguments[2] + }; + } + + options = options || {}; + + each(keys(Terminal.defaults), function(key) { + if (options[key] == null) { + options[key] = Terminal.options[key]; + // Legacy: + 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.slice(0, -2).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]; + + // Act as though we are a node TTY stream: + this.setRawMode; + this.isTTY = true; + this.isRaw = true; + this.columns = this.cols; + this.rows = this.rows; + + if (options.handler) { + this.on('data', options.handler); + } + + this.ybase = 0; + this.ydisp = 0; + this.x = 0; + this.y = 0; + this.cursorState = 0; + this.cursorHidden = false; + this.convertEol; + this.state = 0; + this.queue = ''; + this.scrollTop = 0; + this.scrollBottom = this.rows - 1; + + // modes + this.applicationKeypad = false; + this.applicationCursor = false; + this.originMode = false; + this.insertMode = false; + this.wraparoundMode = false; + this.normal = null; + + // select modes + this.prefixMode = false; + this.selectMode = false; + this.visualMode = false; + this.searchMode = false; + this.searchDown; + this.entry = ''; + this.entryPrefix = 'Search: '; + this._real; + this._selected; + this._textarea; + + // 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.lines = []; + var i = this.rows; + while (i--) { + this.lines.push(this.blankLine()); + } + + this.tabs; + this.setupStops(); +} + +inherits(Terminal, Stream); + +/** + * Colors + */ + +// Colors 0-15 +Terminal.tangoColors = [ + // dark: + '#2e3436', + '#cc0000', + '#4e9a06', + '#c4a000', + '#3465a4', + '#75507b', + '#06989a', + '#d3d7cf', + // bright: + '#555753', + '#ef2929', + '#8ae234', + '#fce94f', + '#729fcf', + '#ad7fa8', + '#34e2e2', + '#eeeeec' +]; + +Terminal.xtermColors = [ + // dark: + '#000000', // black + '#cd0000', // red3 + '#00cd00', // green3 + '#cdcd00', // yellow3 + '#0000ee', // blue2 + '#cd00cd', // magenta3 + '#00cdcd', // cyan3 + '#e5e5e5', // gray90 + // bright: + '#7f7f7f', // gray50 + '#ff0000', // red + '#00ff00', // green + '#ffff00', // yellow + '#5c5cff', // rgb:5c/5c/ff + '#ff00ff', // magenta + '#00ffff', // cyan + '#ffffff' // white +]; + +// 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; +})(); + +// Default BG/FG +Terminal.colors[256] = '#000000'; +Terminal.colors[257] = '#f0f0f0'; + +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, + convertEol: false, + termName: 'xterm', + geometry: [80, 24], + cursorBlink: true, + visualBell: false, + popOnBell: false, + scrollback: 1000, + screenKeys: false, + debug: false, + useStyle: false + // programFeatures: false, + // focusKeys: false, +}; + +Terminal.options = {}; + +each(keys(Terminal.defaults), function(key) { + Terminal[key] = Terminal.defaults[key]; + Terminal.options[key] = Terminal.defaults[key]; +}); + +/** + * Focused Terminal + */ + +Terminal.focus = null; + +Terminal.prototype.focus = function() { + if (Terminal.focus === this) return; + + if (Terminal.focus) { + Terminal.focus.blur(); + } + + if (this.sendFocus) this.send('\x1b[I'); + this.showCursor(); + + // try { + // this.element.focus(); + // } catch (e) { + // ; + // } + + // this.emit('focus'); + + Terminal.focus = this; +}; + +Terminal.prototype.blur = function() { + if (Terminal.focus !== this) return; + + this.cursorState = 0; + this.refresh(this.y, this.y); + if (this.sendFocus) this.send('\x1b[O'); + + // try { + // this.element.blur(); + // } catch (e) { + // ; + // } + + // this.emit('blur'); + + Terminal.focus = null; +}; + +/** + * Initialize global behavior + */ + +Terminal.prototype.initGlobal = function() { + var document = this.document; + + Terminal._boundDocs = Terminal._boundDocs || []; + if (~indexOf(Terminal._boundDocs, document)) { + return; + } + Terminal._boundDocs.push(document); + + Terminal.bindPaste(document); + + Terminal.bindKeys(document); + + Terminal.bindCopy(document); + + if (this.isMobile) { + this.fixMobile(document); + } + + if (this.useStyle) { + Terminal.insertStyle(document, this.colors[256], this.colors[257]); + } +}; + +/** + * Bind to paste event + */ + +Terminal.bindPaste = function(document) { + // This seems to work well for ctrl-V and middle-click, + // even without the contentEditable workaround. + var window = document.defaultView; + on(window, 'paste', function(ev) { + var term = Terminal.focus; + if (!term) return; + if (ev.clipboardData) { + term.send(ev.clipboardData.getData('text/plain')); + } else if (term.context.clipboardData) { + term.send(term.context.clipboardData.getData('Text')); + } + // Not necessary. Do it anyway for good measure. + term.element.contentEditable = 'inherit'; + return cancel(ev); + }); +}; + +/** + * Global Events for key handling + */ + +Terminal.bindKeys = function(document) { + // We should only need to check `target === body` below, + // but we can check everything for good measure. + on(document, 'keydown', function(ev) { + if (!Terminal.focus) return; + var target = ev.target || ev.srcElement; + if (!target) return; + if (target === Terminal.focus.element + || target === Terminal.focus.context + || target === Terminal.focus.document + || target === Terminal.focus.body + || target === Terminal._textarea + || target === Terminal.focus.parent) { + return Terminal.focus.keyDown(ev); + } + }, true); + + on(document, 'keypress', function(ev) { + if (!Terminal.focus) return; + var target = ev.target || ev.srcElement; + if (!target) return; + if (target === Terminal.focus.element + || target === Terminal.focus.context + || target === Terminal.focus.document + || target === Terminal.focus.body + || target === Terminal._textarea + || target === Terminal.focus.parent) { + return Terminal.focus.keyPress(ev); + } + }, true); + + // If we click somewhere other than a + // terminal, unfocus the terminal. + on(document, 'mousedown', function(ev) { + if (!Terminal.focus) return; + + var el = ev.target || ev.srcElement; + if (!el) return; + + do { + if (el === Terminal.focus.element) return; + } while (el = el.parentNode); + + Terminal.focus.blur(); + }); +}; + +/** + * Copy Selection w/ Ctrl-C (Select Mode) + */ + +Terminal.bindCopy = function(document) { + var window = document.defaultView; + + // if (!('onbeforecopy' in document)) { + // // Copies to *only* the clipboard. + // on(window, 'copy', function fn(ev) { + // var term = Terminal.focus; + // if (!term) return; + // if (!term._selected) return; + // var text = term.grabText( + // term._selected.x1, term._selected.x2, + // term._selected.y1, term._selected.y2); + // term.emit('copy', text); + // ev.clipboardData.setData('text/plain', text); + // }); + // return; + // } + + // Copies to primary selection *and* clipboard. + // NOTE: This may work better on capture phase, + // or using the `beforecopy` event. + on(window, 'copy', function(ev) { + var term = Terminal.focus; + if (!term) return; + if (!term._selected) return; + var textarea = term.getCopyTextarea(); + var text = term.grabText( + term._selected.x1, term._selected.x2, + term._selected.y1, term._selected.y2); + term.emit('copy', text); + textarea.focus(); + textarea.textContent = text; + textarea.value = text; + textarea.setSelectionRange(0, text.length); + setTimeout(function() { + term.element.focus(); + term.focus(); + }, 1); + }); +}; + +/** + * Fix Mobile + */ + +Terminal.prototype.fixMobile = function(document) { + var self = this; + + var textarea = document.createElement('textarea'); + textarea.style.position = 'absolute'; + textarea.style.left = '-32000px'; + textarea.style.top = '-32000px'; + textarea.style.width = '0px'; + textarea.style.height = '0px'; + textarea.style.opacity = '0'; + textarea.style.backgroundColor = 'transparent'; + textarea.style.borderStyle = 'none'; + textarea.style.outlineStyle = 'none'; + textarea.autocapitalize = 'none'; + textarea.autocorrect = 'off'; + + document.getElementsByTagName('body')[0].appendChild(textarea); + + Terminal._textarea = textarea; + + setTimeout(function() { + textarea.focus(); + }, 1000); + + if (this.isAndroid) { + on(textarea, 'change', function() { + var value = textarea.textContent || textarea.value; + textarea.value = ''; + textarea.textContent = ''; + self.send(value + '\r'); + }); + } +}; + +/** + * Insert a default style + */ + +Terminal.insertStyle = function(document, bg, fg) { + var style = document.getElementById('term-style'); + if (style) return; + + var head = document.getElementsByTagName('head')[0]; + if (!head) return; + + var style = document.createElement('style'); + style.id = 'term-style'; + + // textContent doesn't work well with IE for <style> elements. + style.innerHTML = '' + + '.terminal {\n' + + ' float: left;\n' + + ' border: ' + bg + ' solid 5px;\n' + + ' font-family: "DejaVu Sans Mono", "Liberation Mono", monospace;\n' + + ' font-size: 11px;\n' + + ' color: ' + fg + ';\n' + + ' background: ' + bg + ';\n' + + '}\n' + + '\n' + + '.terminal-cursor {\n' + + ' color: ' + bg + ';\n' + + ' background: ' + fg + ';\n' + + '}\n'; + + // var out = ''; + // each(Terminal.colors, function(color, i) { + // if (i === 256) { + // out += '\n.term-bg-color-default { background-color: ' + color + '; }'; + // } + // if (i === 257) { + // out += '\n.term-fg-color-default { color: ' + color + '; }'; + // } + // out += '\n.term-bg-color-' + i + ' { background-color: ' + color + '; }'; + // out += '\n.term-fg-color-' + i + ' { color: ' + color + '; }'; + // }); + // style.innerHTML += out + '\n'; + + head.insertBefore(style, head.firstChild); +}; + +/** + * Open Terminal + */ + +Terminal.prototype.open = function(parent) { + 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]; + + // Parse user-agent strings. + if (this.context.navigator && this.context.navigator.userAgent) { + this.isMac = !!~this.context.navigator.userAgent.indexOf('Mac'); + this.isIpad = !!~this.context.navigator.userAgent.indexOf('iPad'); + this.isIphone = !!~this.context.navigator.userAgent.indexOf('iPhone'); + this.isAndroid = !!~this.context.navigator.userAgent.indexOf('Android'); + this.isMobile = this.isIpad || this.isIphone || this.isAndroid; + this.isMSIE = !!~this.context.navigator.userAgent.indexOf('MSIE'); + } + + // Create our main terminal element. + this.element = this.document.createElement('div'); + this.element.className = 'terminal'; + this.element.style.outline = 'none'; + this.element.setAttribute('tabindex', 0); + this.element.setAttribute('spellcheck', 'false'); + this.element.style.backgroundColor = this.colors[256]; + this.element.style.color = this.colors[257]; + + // Create the lines for our terminal. + this.children = []; + for (; i < this.rows; i++) { + div = this.document.createElement('div'); + this.element.appendChild(div); + this.children.push(div); + } + this.parent.appendChild(this.element); + + // Draw the screen. + this.refresh(0, this.rows - 1); + + if (!('useEvents' in this.options) || this.options.useEvents) { + // Initialize global actions that + // need to be taken on the document. + this.initGlobal(); + } + + if (!('useFocus' in this.options) || this.options.useFocus) { + // Ensure there is a Terminal.focus. + this.focus(); + + // Start blinking the cursor. + this.startBlink(); + + // Bind to DOM events related + // to focus and paste behavior. + on(this.element, 'focus', function() { + self.focus(); + if (self.isMobile) { + Terminal._textarea.focus(); + } + }); + + // This causes slightly funky behavior. + // on(this.element, 'blur', function() { + // self.blur(); + // }); + + on(this.element, 'mousedown', function() { + self.focus(); + }); + + // Clickable paste workaround, using contentEditable. + // This probably shouldn't work, + // ... but it does. Firefox's paste + // event seems to only work for textareas? + on(this.element, 'mousedown', function(ev) { + var button = ev.button != null + ? +ev.button + : ev.which != null + ? ev.which - 1 + : null; + + // Does IE9 do this? + if (self.isMSIE) { + button = button === 1 ? 0 : button === 4 ? 1 : button; + } + + if (button !== 2) return; + + self.element.contentEditable = 'true'; + setTimeout(function() { + self.element.contentEditable = 'inherit'; // 'false'; + }, 1); + }, true); + } + + if (!('useMouse' in this.options) || this.options.useMouse) { + // Listen for mouse events and translate + // them into terminal mouse protocols. + this.bindMouse(); + } + + // this.emit('open'); + + if (!('useFocus' in this.options) || this.options.useFocus) { + // This can be useful for pasting, + // as well as the iPad fix. + setTimeout(function() { + self.element.focus(); + }, 100); + } + + // Figure out whether boldness affects + // the character width of monospace fonts. + if (Terminal.brokenBold == null) { + Terminal.brokenBold = isBoldBroken(this.document); + } + + this.emit('open'); +}; + +Terminal.prototype.setRawMode = function(value) { + this.isRaw = !!value; +}; + +// 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; + + var wheelEvent = 'onmousewheel' in this.context + ? 'mousewheel' + : 'DOMMouseScroll'; + + // mouseup, mousedown, mousewheel + // left click: ^[[M 3<^[[M#3< + // mousewheel up: ^[[M`3> + function sendButton(ev) { + var button + , pos; + + // get the xterm-style button + button = getButton(ev); + + // get mouse coordinates + pos = getCoords(ev); + if (!pos) return; + + sendEvent(button, pos); + + switch (ev.type) { + case 'mousedown': + pressed = button; + break; + case 'mouseup': + // keep it at the left + // button, just in case. + pressed = 32; + break; + case wheelEvent: + // 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 = getCoords(ev); + 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 = '\x1b[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('\x1b[' + + 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('\x1b[' + button + ';' + pos.x + ';' + pos.y + 'M'); + return; + } + + if (self.sgrMouse) { + pos.x -= 32; + pos.y -= 32; + self.send('\x1b[<' + + ((button & 3) === 3 ? button & ~3 : button) + + ';' + + 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('\x1b[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.type) { + case 'mousedown': + button = ev.button != null + ? +ev.button + : ev.which != null + ? ev.which - 1 + : null; + + if (self.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 'mousewheel': + 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; + } + + // mouse coordinates measured in cols/rows + function getCoords(ev) { + var x, y, w, h, el; + + // ignore browsers without pageX for now + if (ev.pageX == null) return; + + x = ev.pageX; + y = ev.pageY; + el = self.element; + + // should probably check offsetParent + // but this is more portable + while (el && el !== self.document.documentElement) { + x -= el.offsetLeft; + y -= el.offsetTop; + el = 'offsetParent' in el + ? el.offsetParent + : el.parentNode; + } + + // convert to cols/rows + w = self.element.clientWidth; + h = self.element.clientHeight; + x = Math.round((x / w) * self.cols); + y = Math.round((y / h) * self.rows); + + // be sure to avoid sending + // bad positions to the program + if (x < 0) x = 0; + if (x > self.cols) x = self.cols; + if (y < 0) y = 0; + if (y > self.rows) y = self.rows; + + // xterm sends raw bytes and + // starts at 32 (SP) for each. + x += 32; + y += 32; + + return { + x: x, + y: y, + type: ev.type === wheelEvent + ? 'mousewheel' + : ev.type + }; + } + + on(el, 'mousedown', function(ev) { + if (!self.mouseEvents) return; + + // send the button + sendButton(ev); + + // ensure focus + self.focus(); + + // fix for odd bug + //if (self.vt200Mouse && !self.normalMouse) { + // XXX This seems to break certain programs. + // if (self.vt200Mouse) { + // sendButton({ __proto__: ev, type: 'mouseup' }); + // return 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 cancel(ev); + }); + } + + return cancel(ev); + }); + + //if (self.normalMouse) { + // on(self.document, 'mousemove', sendMove); + //} + + on(el, wheelEvent, function(ev) { + if (!self.mouseEvents) return; + if (self.x10Mouse + || self.vt300Mouse + || self.decLocator) return; + sendButton(ev); + return cancel(ev); + }); + + // allow mousewheel scrolling in + // the shell for example + on(el, wheelEvent, function(ev) { + if (self.mouseEvents) return; + if (self.applicationKeypad) return; + if (ev.type === 'DOMMouseScroll') { + self.scrollDisp(ev.detail < 0 ? -5 : 5); + } else { + self.scrollDisp(ev.wheelDeltaY > 0 ? -5 : 5); + } + return cancel(ev); + }); +}; + +/** + * Destroy Terminal + */ + +Terminal.prototype.close = +Terminal.prototype.destroySoon = +Terminal.prototype.destroy = function() { + if (this.destroyed) { + return; + } + + if (this._blink) { + clearInterval(this._blink); + delete this._blink; + } + + this.readable = false; + this.writable = false; + this.destroyed = true; + this._events = {}; + + this.handler = function() {}; + this.write = function() {}; + this.end = function() {}; + + if (this.element.parentNode) { + this.element.parentNode.removeChild(this.element); + } + + this.emit('end'); + this.emit('close'); + this.emit('finish'); + this.emit('destroy'); +}; + +/** + * 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 + +Terminal.prototype.refresh = function(start, end) { + var x + , y + , i + , line + , out + , ch + , width + , data + , attr + , bg + , fg + , flags + , row + , parent; + + if (end - start >= this.rows / 2) { + parent = this.element.parentNode; + if (parent) parent.removeChild(this.element); + } + + width = this.cols; + y = start; + + if (end >= this.lines.length) { + this.log('`end` is too large. Most likely a bad CSR.'); + end = this.lines.length - 1; + } + + for (; y <= end; y++) { + row = y + this.ydisp; + + line = this.lines[row]; + out = ''; + + if (y === this.y + && this.cursorState + && (this.ydisp === this.ybase || this.selectMode) + && !this.cursorHidden) { + x = this.x; + } else { + x = -1; + } + + attr = this.defAttr; + i = 0; + + for (; i < width; i++) { + data = line[i][0]; + ch = line[i][1]; + + if (i === x) data = -1; + + if (data !== attr) { + if (attr !== this.defAttr) { + out += '</span>'; + } + if (data !== this.defAttr) { + if (data === -1) { + out += '<span class="reverse-video terminal-cursor">'; + } else { + out += '<span style="'; + + bg = data & 0x1ff; + fg = (data >> 9) & 0x1ff; + flags = data >> 18; + + // bold + if (flags & 1) { + if (!Terminal.brokenBold) { + out += 'font-weight:bold;'; + } + // See: XTerm*boldColors + if (fg < 8) fg += 8; + } + + // underline + if (flags & 2) { + out += 'text-decoration:underline;'; + } + + // blink + if (flags & 4) { + if (flags & 2) { + out = out.slice(0, -1); + out += ' blink;'; + } else { + out += 'text-decoration:blink;'; + } + } + + // inverse + if (flags & 8) { + bg = (data >> 9) & 0x1ff; + fg = data & 0x1ff; + // Should inverse just be before the + // above boldColors effect instead? + if ((flags & 1) && fg < 8) fg += 8; + } + + // invisible + if (flags & 16) { + out += 'visibility:hidden;'; + } + + // out += '" class="' + // + 'term-bg-color-' + bg + // + ' ' + // + 'term-fg-color-' + fg + // + '">'; + + if (bg !== 256) { + out += 'background-color:' + + this.colors[bg] + + ';'; + } + + if (fg !== 257) { + out += 'color:' + + this.colors[fg] + + ';'; + } + + out += '">'; + } + } + } + + switch (ch) { + case '&': + out += '&'; + break; + case '<': + out += '<'; + break; + case '>': + out += '>'; + break; + default: + if (ch <= ' ') { + out += ' '; + } else { + if (isWide(ch)) i++; + out += ch; + } + break; + } + + attr = data; + } + + if (attr !== this.defAttr) { + out += '</span>'; + } + + this.children[y].innerHTML = out; + } + + if (parent) parent.appendChild(this.element); +}; + +Terminal.prototype._cursorBlink = function() { + if (Terminal.focus !== this) return; + this.cursorState ^= 1; + this.refresh(this.y, this.y); +}; + +Terminal.prototype.showCursor = function() { + if (!this.cursorState) { + this.cursorState = 1; + this.refresh(this.y, this.y); + } else { + // Temporarily disabled: + // this.refreshBlink(); + } +}; + +Terminal.prototype.startBlink = function() { + if (!this.cursorBlink) return; + var self = this; + this._blinker = function() { + self._cursorBlink(); + }; + this._blink = setInterval(this._blinker, 500); +}; + +Terminal.prototype.refreshBlink = function() { + if (!this.cursorBlink || !this._blink) return; + clearInterval(this._blink); + this._blink = setInterval(this._blinker, 500); +}; + +Terminal.prototype.scroll = function() { + var row; + + if (++this.ybase === this.scrollback) { + this.ybase = this.ybase / 2 | 0; + this.lines = this.lines.slice(-(this.ybase + this.rows) + 1); + } + + this.ydisp = this.ybase; + + // last line + row = this.ybase + this.rows - 1; + + // subtract the bottom scroll region + row -= this.rows - 1 - this.scrollBottom; + + if (row === this.lines.length) { + // potential optimization: + // pushing is faster than splicing + // when they amount to the same + // behavior. + this.lines.push(this.blankLine()); + } else { + // add our new line + this.lines.splice(row, 0, this.blankLine()); + } + + if (this.scrollTop !== 0) { + if (this.ybase !== 0) { + this.ybase--; + this.ydisp = this.ybase; + } + this.lines.splice(this.ybase + this.scrollTop, 1); + } + + // this.maxRange(); + this.updateRange(this.scrollTop); + this.updateRange(this.scrollBottom); +}; + +Terminal.prototype.scrollDisp = function(disp) { + this.ydisp += disp; + + if (this.ydisp > this.ybase) { + this.ydisp = this.ybase; + } else if (this.ydisp < 0) { + this.ydisp = 0; + } + + this.refresh(0, this.rows - 1); +}; + +Terminal.prototype.write = function(data) { + var l = data.length + , i = 0 + , j + , cs + , ch; + + this.refreshStart = this.y; + this.refreshEnd = this.y; + + if (this.ybase !== this.ydisp) { + this.ydisp = this.ybase; + this.maxRange(); + } + + // this.log(JSON.stringify(data.replace(/\x1b/g, '^['))); + + for (; i < l; i++, this.lch = ch) { + ch = data[i]; + switch (this.state) { + case normal: + switch (ch) { + // '\0' + // case '\0': + // case '\200': + // break; + + // '\a' + case '\x07': + this.bell(); + break; + + // '\n', '\v', '\f' + case '\n': + case '\x0b': + case '\x0c': + if (this.convertEol) { + this.x = 0; + } + // TODO: Implement eat_newline_glitch. + // if (this.realX >= this.cols) break; + // this.realX = 0; + this.y++; + if (this.y > this.scrollBottom) { + this.y--; + this.scroll(); + } + break; + + // '\r' + case '\r': + this.x = 0; + break; + + // '\b' + case '\x08': + if (this.x > 0) { + this.x--; + } + break; + + // '\t' + case '\t': + this.x = this.nextStop(); + break; + + // shift out + case '\x0e': + this.setgLevel(1); + break; + + // shift in + case '\x0f': + this.setgLevel(0); + break; + + // '\e' + case '\x1b': + this.state = escaped; + break; + + default: + // ' ' + if (ch >= ' ') { + if (this.charset && this.charset[ch]) { + ch = this.charset[ch]; + } + + if (this.x >= this.cols) { + this.x = 0; + this.y++; + if (this.y > this.scrollBottom) { + this.y--; + this.scroll(); + } + } + + this.lines[this.y + this.ybase][this.x] = [this.curAttr, ch]; + this.x++; + this.updateRange(this.y); + + if (isWide(ch)) { + j = this.y + this.ybase; + if (this.cols < 2 || this.x >= this.cols) { + this.lines[j][this.x - 1] = [this.curAttr, ' ']; + break; + } + this.lines[j][this.x] = [this.curAttr, ' ']; + this.x++; + } + } + break; + } + break; + case escaped: + switch (ch) { + // ESC [ Control Sequence Introducer ( CSI is 0x9b). + case '[': + this.params = []; + this.currentParam = 0; + this.state = csi; + break; + + // ESC ] Operating System Command ( OSC is 0x9d). + case ']': + this.params = []; + this.currentParam = 0; + this.state = osc; + break; + + // ESC P Device Control String ( DCS is 0x90). + case 'P': + this.params = []; + this.prefix = ''; + this.currentParam = ''; + this.state = dcs; + break; + + // ESC _ Application Program Command ( APC is 0x9f). + case '_': + this.state = ignore; + break; + + // ESC ^ Privacy Message ( PM is 0x9e). + case '^': + this.state = ignore; + break; + + // ESC c Full Reset (RIS). + case 'c': + this.reset(); + break; + + // ESC E Next Line ( NEL is 0x85). + // ESC D Index ( IND is 0x84). + case 'E': + this.x = 0; + ; + case 'D': + this.index(); + break; + + // ESC M Reverse Index ( RI is 0x8d). + case 'M': + this.reverseIndex(); + break; + + // ESC % Select default/utf-8 character set. + // @ = default, G = utf-8 + case '%': + //this.charset = null; + this.setgLevel(0); + this.setgCharset(0, Terminal.charsets.US); + this.state = normal; + i++; + break; + + // ESC (,),*,+,-,. Designate G0-G2 Character Set. + case '(': // <-- this seems to get all the attention + case ')': + case '*': + case '+': + case '-': + case '.': + switch (ch) { + case '(': + this.gcharset = 0; + break; + case ')': + this.gcharset = 1; + break; + case '*': + this.gcharset = 2; + break; + case '+': + this.gcharset = 3; + break; + case '-': + this.gcharset = 1; + break; + case '.': + this.gcharset = 2; + break; + } + this.state = charset; + break; + + // Designate G3 Character Set (VT300). + // A = ISO Latin-1 Supplemental. + // Not implemented. + case '/': + this.gcharset = 3; + this.state = charset; + i--; + break; + + // ESC N + // Single Shift Select of G2 Character Set + // ( SS2 is 0x8e). This affects next character only. + case 'N': + break; + // ESC O + // Single Shift Select of G3 Character Set + // ( SS3 is 0x8f). This affects next character only. + case 'O': + break; + // ESC n + // Invoke the G2 Character Set as GL (LS2). + case 'n': + this.setgLevel(2); + break; + // ESC o + // Invoke the G3 Character Set as GL (LS3). + case 'o': + this.setgLevel(3); + break; + // ESC | + // Invoke the G3 Character Set as GR (LS3R). + case '|': + this.setgLevel(3); + break; + // ESC } + // Invoke the G2 Character Set as GR (LS2R). + case '}': + this.setgLevel(2); + break; + // ESC ~ + // Invoke the G1 Character Set as GR (LS1R). + case '~': + this.setgLevel(1); + break; + + // ESC 7 Save Cursor (DECSC). + case '7': + this.saveCursor(); + this.state = normal; + break; + + // ESC 8 Restore Cursor (DECRC). + case '8': + this.restoreCursor(); + this.state = normal; + break; + + // ESC # 3 DEC line height/width + case '#': + this.state = normal; + i++; + break; + + // ESC H Tab Set (HTS is 0x88). + case 'H': + this.tabSet(); + break; + + // ESC = Application Keypad (DECPAM). + case '=': + this.log('Serial port requested application keypad.'); + this.applicationKeypad = true; + this.state = normal; + break; + + // ESC > Normal Keypad (DECPNM). + case '>': + this.log('Switching back to normal keypad.'); + this.applicationKeypad = false; + this.state = normal; + break; + + default: + this.state = normal; + this.error('Unknown ESC control: %s.', ch); + break; + } + break; + + case charset: + switch (ch) { + case '0': // DEC Special Character and Line Drawing Set. + cs = Terminal.charsets.SCLD; + break; + case 'A': // UK + cs = Terminal.charsets.UK; + break; + case 'B': // United States (USASCII). + cs = Terminal.charsets.US; + break; + case '4': // Dutch + cs = Terminal.charsets.Dutch; + break; + case 'C': // Finnish + case '5': + cs = Terminal.charsets.Finnish; + break; + case 'R': // French + cs = Terminal.charsets.French; + break; + case 'Q': // FrenchCanadian + cs = Terminal.charsets.FrenchCanadian; + break; + case 'K': // German + cs = Terminal.charsets.German; + break; + case 'Y': // Italian + cs = Terminal.charsets.Italian; + break; + case 'E': // NorwegianDanish + case '6': + cs = Terminal.charsets.NorwegianDanish; + break; + case 'Z': // Spanish + cs = Terminal.charsets.Spanish; + break; + case 'H': // Swedish + case '7': + cs = Terminal.charsets.Swedish; + break; + case '=': // Swiss + cs = Terminal.charsets.Swiss; + break; + case '/': // ISOLatin (actually /A) + cs = Terminal.charsets.ISOLatin; + i++; + break; + default: // Default + cs = Terminal.charsets.US; + break; + } + this.setgCharset(this.gcharset, cs); + this.gcharset = null; + this.state = normal; + break; + + case osc: + // OSC Ps ; Pt ST + // OSC Ps ; Pt BEL + // Set Text Parameters. + if ((this.lch === '\x1b' && ch === '\\') || ch === '\x07') { + if (this.lch === '\x1b') { + if (typeof this.currentParam === 'string') { + this.currentParam = this.currentParam.slice(0, -1); + } else if (typeof this.currentParam == 'number') { + this.currentParam = (this.currentParam - ('\x1b'.charCodeAt(0) - 48)) / 10; + } + } + + this.params.push(this.currentParam); + + switch (this.params[0]) { + case 0: + case 1: + case 2: + if (this.params[1]) { + this.title = this.params[1]; + this.handleTitle(this.title); + } + break; + case 3: + // set X property + break; + case 4: + case 5: + // change dynamic colors + break; + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + case 16: + case 17: + case 18: + case 19: + // change dynamic ui colors + break; + case 46: + // change log file + break; + case 50: + // dynamic font + break; + case 51: + // emacs shell + break; + case 52: + // manipulate selection data + break; + case 104: + case 105: + case 110: + case 111: + case 112: + case 113: + case 114: + case 115: + case 116: + case 117: + case 118: + // reset colors + break; + } + + this.params = []; + this.currentParam = 0; + this.state = normal; + } else { + if (!this.params.length) { + if (ch >= '0' && ch <= '9') { + this.currentParam = + this.currentParam * 10 + ch.charCodeAt(0) - 48; + } else if (ch === ';') { + this.params.push(this.currentParam); + this.currentParam = ''; + } + } else { + this.currentParam += ch; + } + } + break; + + case csi: + // '?', '>', '!' + if (ch === '?' || ch === '>' || ch === '!') { + this.prefix = ch; + break; + } + + // 0 - 9 + if (ch >= '0' && ch <= '9') { + this.currentParam = this.currentParam * 10 + ch.charCodeAt(0) - 48; + break; + } + + // '$', '"', ' ', '\'' + if (ch === '$' || ch === '"' || ch === ' ' || ch === '\'') { + this.postfix = ch; + break; + } + + this.params.push(this.currentParam); + this.currentParam = 0; + + // ';' + if (ch === ';') break; + + this.state = normal; + + switch (ch) { + // CSI Ps A + // Cursor Up Ps Times (default = 1) (CUU). + case 'A': + this.cursorUp(this.params); + break; + + // CSI Ps B + // Cursor Down Ps Times (default = 1) (CUD). + case 'B': + this.cursorDown(this.params); + break; + + // CSI Ps C + // Cursor Forward Ps Times (default = 1) (CUF). + case 'C': + this.cursorForward(this.params); + break; + + // CSI Ps D + // Cursor Backward Ps Times (default = 1) (CUB). + case 'D': + this.cursorBackward(this.params); + break; + + // CSI Ps ; Ps H + // Cursor Position [row;column] (default = [1,1]) (CUP). + case 'H': + this.cursorPos(this.params); + break; + + // CSI Ps J Erase in Display (ED). + case 'J': + this.eraseInDisplay(this.params); + break; + + // CSI Ps K Erase in Line (EL). + case 'K': + this.eraseInLine(this.params); + break; + + // CSI Pm m Character Attributes (SGR). + case 'm': + if (!this.prefix) { + this.charAttributes(this.params); + } + break; + + // CSI Ps n Device Status Report (DSR). + case 'n': + if (!this.prefix) { + this.deviceStatus(this.params); + } + break; + + /** + * Additions + */ + + // CSI Ps @ + // Insert Ps (Blank) Character(s) (default = 1) (ICH). + case '@': + this.insertChars(this.params); + break; + + // CSI Ps E + // Cursor Next Line Ps Times (default = 1) (CNL). + case 'E': + this.cursorNextLine(this.params); + break; + + // CSI Ps F + // Cursor Preceding Line Ps Times (default = 1) (CNL). + case 'F': + this.cursorPrecedingLine(this.params); + break; + + // CSI Ps G + // Cursor Character Absolute [column] (default = [row,1]) (CHA). + case 'G': + this.cursorCharAbsolute(this.params); + break; + + // CSI Ps L + // Insert Ps Line(s) (default = 1) (IL). + case 'L': + this.insertLines(this.params); + break; + + // CSI Ps M + // Delete Ps Line(s) (default = 1) (DL). + case 'M': + this.deleteLines(this.params); + break; + + // CSI Ps P + // Delete Ps Character(s) (default = 1) (DCH). + case 'P': + this.deleteChars(this.params); + break; + + // CSI Ps X + // Erase Ps Character(s) (default = 1) (ECH). + case 'X': + this.eraseChars(this.params); + break; + + // CSI Pm ` Character Position Absolute + // [column] (default = [row,1]) (HPA). + case '`': + this.charPosAbsolute(this.params); + break; + + // 141 61 a * HPR - + // Horizontal Position Relative + case 'a': + this.HPositionRelative(this.params); + break; + + // CSI P s c + // Send Device Attributes (Primary DA). + // CSI > P s c + // Send Device Attributes (Secondary DA) + case 'c': + this.sendDeviceAttributes(this.params); + break; + + // CSI Pm d + // Line Position Absolute [row] (default = [1,column]) (VPA). + case 'd': + this.linePosAbsolute(this.params); + break; + + // 145 65 e * VPR - Vertical Position Relative + case 'e': + this.VPositionRelative(this.params); + break; + + // CSI Ps ; Ps f + // Horizontal and Vertical Position [row;column] (default = + // [1,1]) (HVP). + case 'f': + this.HVPosition(this.params); + break; + + // CSI Pm h Set Mode (SM). + // CSI ? Pm h - mouse escape codes, cursor escape codes + case 'h': + this.setMode(this.params); + break; + + // CSI Pm l Reset Mode (RM). + // CSI ? Pm l + case 'l': + this.resetMode(this.params); + break; + + // CSI Ps ; Ps r + // Set Scrolling Region [top;bottom] (default = full size of win- + // dow) (DECSTBM). + // CSI ? Pm r + case 'r': + this.setScrollRegion(this.params); + break; + + // CSI s + // Save cursor (ANSI.SYS). + case 's': + this.saveCursor(this.params); + break; + + // CSI u + // Restore cursor (ANSI.SYS). + case 'u': + this.restoreCursor(this.params); + break; + + /** + * Lesser Used + */ + + // CSI Ps I + // Cursor Forward Tabulation Ps tab stops (default = 1) (CHT). + case 'I': + this.cursorForwardTab(this.params); + break; + + // CSI Ps S Scroll up Ps lines (default = 1) (SU). + case 'S': + this.scrollUp(this.params); + break; + + // CSI Ps T Scroll down Ps lines (default = 1) (SD). + // CSI Ps ; Ps ; Ps ; Ps ; Ps T + // CSI > Ps; Ps T + case 'T': + // if (this.prefix === '>') { + // this.resetTitleModes(this.params); + // break; + // } + // if (this.params.length > 2) { + // this.initMouseTracking(this.params); + // break; + // } + if (this.params.length < 2 && !this.prefix) { + this.scrollDown(this.params); + } + break; + + // CSI Ps Z + // Cursor Backward Tabulation Ps tab stops (default = 1) (CBT). + case 'Z': + this.cursorBackwardTab(this.params); + break; + + // CSI Ps b Repeat the preceding graphic character Ps times (REP). + case 'b': + this.repeatPrecedingCharacter(this.params); + break; + + // CSI Ps g Tab Clear (TBC). + case 'g': + this.tabClear(this.params); + break; + + // CSI Pm i Media Copy (MC). + // CSI ? Pm i + // case 'i': + // this.mediaCopy(this.params); + // break; + + // CSI Pm m Character Attributes (SGR). + // CSI > Ps; Ps m + // case 'm': // duplicate + // if (this.prefix === '>') { + // this.setResources(this.params); + // } else { + // this.charAttributes(this.params); + // } + // break; + + // CSI Ps n Device Status Report (DSR). + // CSI > Ps n + // case 'n': // duplicate + // if (this.prefix === '>') { + // this.disableModifiers(this.params); + // } else { + // this.deviceStatus(this.params); + // } + // break; + + // CSI > Ps p Set pointer mode. + // CSI ! p Soft terminal reset (DECSTR). + // CSI Ps$ p + // Request ANSI mode (DECRQM). + // CSI ? Ps$ p + // Request DEC private mode (DECRQM). + // CSI Ps ; Ps " p + case 'p': + switch (this.prefix) { + // case '>': + // this.setPointerMode(this.params); + // break; + case '!': + this.softReset(this.params); + break; + // case '?': + // if (this.postfix === '$') { + // this.requestPrivateMode(this.params); + // } + // break; + // default: + // if (this.postfix === '"') { + // this.setConformanceLevel(this.params); + // } else if (this.postfix === '$') { + // this.requestAnsiMode(this.params); + // } + // break; + } + break; + + // CSI Ps q Load LEDs (DECLL). + // CSI Ps SP q + // CSI Ps " q + // case 'q': + // if (this.postfix === ' ') { + // this.setCursorStyle(this.params); + // break; + // } + // if (this.postfix === '"') { + // this.setCharProtectionAttr(this.params); + // break; + // } + // this.loadLEDs(this.params); + // break; + + // CSI Ps ; Ps r + // Set Scrolling Region [top;bottom] (default = full size of win- + // dow) (DECSTBM). + // CSI ? Pm r + // CSI Pt; Pl; Pb; Pr; Ps$ r + // case 'r': // duplicate + // if (this.prefix === '?') { + // this.restorePrivateValues(this.params); + // } else if (this.postfix === '$') { + // this.setAttrInRectangle(this.params); + // } else { + // this.setScrollRegion(this.params); + // } + // break; + + // CSI s Save cursor (ANSI.SYS). + // CSI ? Pm s + // case 's': // duplicate + // if (this.prefix === '?') { + // this.savePrivateValues(this.params); + // } else { + // this.saveCursor(this.params); + // } + // break; + + // CSI Ps ; Ps ; Ps t + // CSI Pt; Pl; Pb; Pr; Ps$ t + // CSI > Ps; Ps t + // CSI Ps SP t + // case 't': + // if (this.postfix === '$') { + // this.reverseAttrInRectangle(this.params); + // } else if (this.postfix === ' ') { + // this.setWarningBellVolume(this.params); + // } else { + // if (this.prefix === '>') { + // this.setTitleModeFeature(this.params); + // } else { + // this.manipulateWindow(this.params); + // } + // } + // break; + + // CSI u Restore cursor (ANSI.SYS). + // CSI Ps SP u + // case 'u': // duplicate + // if (this.postfix === ' ') { + // this.setMarginBellVolume(this.params); + // } else { + // this.restoreCursor(this.params); + // } + // break; + + // CSI Pt; Pl; Pb; Pr; Pp; Pt; Pl; Pp$ v + // case 'v': + // if (this.postfix === '$') { + // this.copyRectagle(this.params); + // } + // break; + + // CSI Pt ; Pl ; Pb ; Pr ' w + // case 'w': + // if (this.postfix === '\'') { + // this.enableFilterRectangle(this.params); + // } + // break; + + // CSI Ps x Request Terminal Parameters (DECREQTPARM). + // CSI Ps x Select Attribute Change Extent (DECSACE). + // CSI Pc; Pt; Pl; Pb; Pr$ x + // case 'x': + // if (this.postfix === '$') { + // this.fillRectangle(this.params); + // } else { + // this.requestParameters(this.params); + // //this.__(this.params); + // } + // break; + + // CSI Ps ; Pu ' z + // CSI Pt; Pl; Pb; Pr$ z + // case 'z': + // if (this.postfix === '\'') { + // this.enableLocatorReporting(this.params); + // } else if (this.postfix === '$') { + // this.eraseRectangle(this.params); + // } + // break; + + // CSI Pm ' { + // CSI Pt; Pl; Pb; Pr$ { + // case '{': + // if (this.postfix === '\'') { + // this.setLocatorEvents(this.params); + // } else if (this.postfix === '$') { + // this.selectiveEraseRectangle(this.params); + // } + // break; + + // CSI Ps ' | + // case '|': + // if (this.postfix === '\'') { + // this.requestLocatorPosition(this.params); + // } + // break; + + // CSI P m SP } + // Insert P s Column(s) (default = 1) (DECIC), VT420 and up. + // case '}': + // if (this.postfix === ' ') { + // this.insertColumns(this.params); + // } + // break; + + // CSI P m SP ~ + // Delete P s Column(s) (default = 1) (DECDC), VT420 and up + // case '~': + // if (this.postfix === ' ') { + // this.deleteColumns(this.params); + // } + // break; + + default: + this.error('Unknown CSI code: %s.', ch); + break; + } + + this.prefix = ''; + this.postfix = ''; + break; + + case dcs: + if ((this.lch === '\x1b' && ch === '\\') || ch === '\x07') { + // Workarounds: + if (this.prefix === 'tmux;\x1b') { + // `DCS tmux; Pt ST` may contain a Pt with an ST + // XXX Does tmux work this way? + // if (this.lch === '\x1b' & data[i + 1] === '\x1b' && data[i + 2] === '\\') { + // this.currentParam += ch; + // continue; + // } + // Tmux only accepts ST, not BEL: + if (ch === '\x07') { + this.currentParam += ch; + continue; + } + } + + if (this.lch === '\x1b') { + if (typeof this.currentParam === 'string') { + this.currentParam = this.currentParam.slice(0, -1); + } else if (typeof this.currentParam == 'number') { + this.currentParam = (this.currentParam - ('\x1b'.charCodeAt(0) - 48)) / 10; + } + } + + this.params.push(this.currentParam); + + var pt = this.params[this.params.length - 1]; + + switch (this.prefix) { + // User-Defined Keys (DECUDK). + // DCS Ps; Ps| Pt ST + case UDK: + this.emit('udk', { + clearAll: this.params[0] === 0, + eraseBelow: this.params[0] === 1, + lockKeys: this.params[1] === 0, + dontLockKeys: this.params[1] === 1, + keyList: (this.params[2] + '').split(';').map(function(part) { + part = part.split('/'); + return { + keyCode: part[0], + hexKeyValue: part[1] + }; + }) + }); + break; + + // Request Status String (DECRQSS). + // DCS $ q Pt ST + // test: echo -e '\eP$q"p\e\\' + case '$q': + var valid = 0; + + switch (pt) { + // DECSCA + // CSI Ps " q + case '"q': + pt = '0"q'; + valid = 1; + break; + + // DECSCL + // CSI Ps ; Ps " p + case '"p': + pt = '61;0"p'; + valid = 1; + break; + + // DECSTBM + // CSI Ps ; Ps r + case 'r': + pt = '' + + (this.scrollTop + 1) + + ';' + + (this.scrollBottom + 1) + + 'r'; + valid = 1; + break; + + // SGR + // CSI Pm m + case 'm': + // TODO: Parse this.curAttr here. + // pt = '0m'; + // valid = 1; + valid = 0; // Not implemented. + break; + + default: + this.error('Unknown DCS Pt: %s.', pt); + valid = 0; // unimplemented + break; + } + + this.send('\x1bP' + valid + '$r' + pt + '\x1b\\'); + break; + + // Set Termcap/Terminfo Data (xterm, experimental). + // DCS + p Pt ST + case '+p': + this.emit('set terminfo', { + name: this.params[0] + }); + break; + + // Request Termcap/Terminfo String (xterm, experimental) + // Regular xterm does not even respond to this sequence. + // This can cause a small glitch in vim. + // DCS + q Pt ST + // test: echo -ne '\eP+q6b64\e\\' + case '+q': + var valid = false; + this.send('\x1bP' + +valid + '+r' + pt + '\x1b\\'); + break; + + // Implement tmux sequence forwarding is + // someone uses term.js for a multiplexer. + // DCS tmux; ESC Pt ST + case 'tmux;\x1b': + this.emit('passthrough', pt); + break; + + default: + this.error('Unknown DCS prefix: %s.', pt); + break; + } + + this.currentParam = 0; + this.prefix = ''; + this.state = normal; + } else { + this.currentParam += ch; + if (!this.prefix) { + if (/^\d*;\d*\|/.test(this.currentParam)) { + this.prefix = UDK; + this.params = this.currentParam.split(/[;|]/).map(function(n) { + if (!n.length) return 0; + return +n; + }).slice(0, -1); + this.currentParam = ''; + } else if (/^[$+][a-zA-Z]/.test(this.currentParam) + || /^\w+;\x1b/.test(this.currentParam)) { + this.prefix = this.currentParam; + this.currentParam = ''; + } + } + } + break; + + case ignore: + // For PM and APC. + if ((this.lch === '\x1b' && ch === '\\') || ch === '\x07') { + this.state = normal; + } + break; + } + } + + this.updateRange(this.y); + this.refresh(this.refreshStart, this.refreshEnd); + + return true; +}; + +Terminal.prototype.writeln = function(data) { + return this.write(data + '\r\n'); +}; + +Terminal.prototype.end = function(data) { + var ret = true; + if (data) { + ret = this.write(data); + } + this.destroySoon(); + return ret; +}; + +Terminal.prototype.resume = function() { + ; +}; + +Terminal.prototype.pause = function() { + ; +}; + +// Key Resources: +// https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent +Terminal.prototype.keyDown = function(ev) { + var self = this + , key; + + switch (ev.keyCode) { + // backspace + case 8: + if (ev.altKey) { + key = '\x17'; + break; + } + if (ev.shiftKey) { + key = '\x08'; // ^H + break; + } + key = '\x7f'; // ^? + break; + // tab + case 9: + if (ev.shiftKey) { + key = '\x1b[Z'; + break; + } + key = '\t'; + break; + // return/enter + case 13: + key = '\r'; + break; + // escape + case 27: + key = '\x1b'; + break; + // space + case 32: + key = '\x20'; + break; + // left-arrow + case 37: + if (this.applicationCursor) { + key = '\x1bOD'; // SS3 as ^[O for 7-bit + //key = '\x8fD'; // SS3 as 0x8f for 8-bit + break; + } + if (ev.ctrlKey) { + key = '\x1b[5D'; + break; + } + key = '\x1b[D'; + break; + // right-arrow + case 39: + if (this.applicationCursor) { + key = '\x1bOC'; + break; + } + if (ev.ctrlKey) { + key = '\x1b[5C'; + break; + } + key = '\x1b[C'; + break; + // up-arrow + case 38: + if (this.applicationCursor) { + key = '\x1bOA'; + break; + } + if (ev.ctrlKey) { + this.scrollDisp(-1); + return cancel(ev); + } else { + key = '\x1b[A'; + } + break; + // down-arrow + case 40: + if (this.applicationCursor) { + key = '\x1bOB'; + break; + } + if (ev.ctrlKey) { + this.scrollDisp(1); + return cancel(ev); + } else { + key = '\x1b[B'; + } + break; + // delete + case 46: + key = '\x1b[3~'; + break; + // insert + case 45: + key = '\x1b[2~'; + break; + // home + case 36: + if (this.applicationKeypad) { + key = '\x1bOH'; + break; + } + key = '\x1bOH'; + break; + // end + case 35: + if (this.applicationKeypad) { + key = '\x1bOF'; + break; + } + key = '\x1bOF'; + break; + // page up + case 33: + if (ev.shiftKey) { + this.scrollDisp(-(this.rows - 1)); + return cancel(ev); + } else { + key = '\x1b[5~'; + } + break; + // page down + case 34: + if (ev.shiftKey) { + this.scrollDisp(this.rows - 1); + return cancel(ev); + } else { + key = '\x1b[6~'; + } + break; + // F1 + case 112: + key = '\x1bOP'; + break; + // F2 + case 113: + key = '\x1bOQ'; + break; + // F3 + case 114: + key = '\x1bOR'; + break; + // F4 + case 115: + key = '\x1bOS'; + break; + // F5 + case 116: + key = '\x1b[15~'; + break; + // F6 + case 117: + key = '\x1b[17~'; + break; + // F7 + case 118: + key = '\x1b[18~'; + break; + // F8 + case 119: + key = '\x1b[19~'; + break; + // F9 + case 120: + key = '\x1b[20~'; + break; + // F10 + case 121: + key = '\x1b[21~'; + break; + // F11 + case 122: + key = '\x1b[23~'; + break; + // F12 + case 123: + key = '\x1b[24~'; + break; + default: + // a-z and space + if (ev.ctrlKey) { + if (ev.keyCode >= 65 && ev.keyCode <= 90) { + // Ctrl-A + if (this.screenKeys) { + if (!this.prefixMode && !this.selectMode && ev.keyCode === 65) { + this.enterPrefix(); + return cancel(ev); + } + } + // Ctrl-V + if (this.prefixMode && ev.keyCode === 86) { + this.leavePrefix(); + return; + } + // Ctrl-C + if ((this.prefixMode || this.selectMode) && ev.keyCode === 67) { + if (this.visualMode) { + setTimeout(function() { + self.leaveVisual(); + }, 1); + } + return; + } + key = String.fromCharCode(ev.keyCode - 64); + } else if (ev.keyCode === 32) { + // NUL + key = String.fromCharCode(0); + } else if (ev.keyCode >= 51 && ev.keyCode <= 55) { + // escape, file sep, group sep, record sep, unit sep + key = String.fromCharCode(ev.keyCode - 51 + 27); + } else if (ev.keyCode === 56) { + // delete + key = String.fromCharCode(127); + } else if (ev.keyCode === 219) { + // ^[ - escape + key = String.fromCharCode(27); + } else if (ev.keyCode === 221) { + // ^] - group sep + key = String.fromCharCode(29); + } + } else if (ev.altKey) { + if (ev.keyCode >= 65 && ev.keyCode <= 90) { + key = '\x1b' + String.fromCharCode(ev.keyCode + 32); + } else if (ev.keyCode === 192) { + key = '\x1b`'; + } else if (ev.keyCode >= 48 && ev.keyCode <= 57) { + key = '\x1b' + (ev.keyCode - 48); + } + } + break; + } + + if (!key) return true; + + if (this.prefixMode) { + this.leavePrefix(); + return cancel(ev); + } + + if (this.selectMode) { + this.keySelect(ev, key); + return cancel(ev); + } + + this.emit('keydown', ev); + this.emit('key', key, ev); + + this.showCursor(); + this.handler(key); + + return cancel(ev); +}; + +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; + + 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.ctrlKey || ev.altKey || ev.metaKey) return false; + + key = String.fromCharCode(key); + + if (this.prefixMode) { + this.leavePrefix(); + this.keyPrefix(ev, key); + return false; + } + + if (this.selectMode) { + this.keySelect(ev, key); + return false; + } + + this.emit('keypress', key, ev); + this.emit('key', key, ev); + + this.showCursor(); + this.handler(key); + + return false; +}; + +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() { + this.emit('bell'); + 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) { + var line + , el + , i + , j + , ch; + + if (x < 1) x = 1; + if (y < 1) y = 1; + + // resize cols + j = this.cols; + if (j < x) { + ch = [this.defAttr, ' ']; // does xterm use the default attr? + i = this.lines.length; + while (i--) { + while (this.lines[i].length < x) { + this.lines[i].push(ch); + } + } + } else if (j > x) { + i = this.lines.length; + while (i--) { + while (this.lines[i].length > x) { + this.lines[i].pop(); + } + } + } + this.setupStops(j); + this.cols = x; + this.columns = x; + + // resize rows + j = this.rows; + if (j < y) { + el = this.element; + while (j++ < y) { + if (this.lines.length < y + this.ybase) { + this.lines.push(this.blankLine()); + } + if (this.children.length < y) { + line = this.document.createElement('div'); + el.appendChild(line); + this.children.push(line); + } + } + } else if (j > y) { + while (j-- > y) { + if (this.lines.length > y + this.ybase) { + this.lines.pop(); + } + if (this.children.length > y) { + el = this.children.pop(); + if (!el) continue; + el.parentNode.removeChild(el); + } + } + } + this.rows = y; + + // make sure the cursor stays on screen + if (this.y >= y) this.y = y - 1; + if (this.x >= x) this.x = x - 1; + + this.scrollTop = 0; + this.scrollBottom = y - 1; + + this.refresh(0, this.rows - 1); + + // it's a real nightmare trying + // to resize the original + // screen buffer. just set it + // to null for now. + this.normal = null; + + // Act as though we are a node TTY stream: + this.emit('resize'); +}; + +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; + // } + // } +}; + +Terminal.prototype.maxRange = function() { + this.refreshStart = 0; + this.refreshEnd = this.rows - 1; +}; + +Terminal.prototype.setupStops = function(i) { + if (i != null) { + if (!this.tabs[i]) { + i = this.prevStop(i); + } + } else { + this.tabs = {}; + i = 0; + } + + for (; i < this.cols; i += 8) { + this.tabs[i] = true; + } +}; + +Terminal.prototype.prevStop = function(x) { + if (x == null) x = this.x; + while (!this.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.x; + while (!this.tabs[++x] && x < this.cols); + return x >= this.cols + ? this.cols - 1 + : x < 0 ? 0 : x; +}; + +// back_color_erase feature for xterm. +Terminal.prototype.eraseAttr = function() { + // if (this.is('screen')) return this.defAttr; + return (this.defAttr & ~0x1ff) | (this.curAttr & 0x1ff); +}; + +Terminal.prototype.eraseRight = function(x, y) { + var line = this.lines[this.ybase + y] + , ch = [this.eraseAttr(), ' ']; // xterm + + + for (; x < this.cols; x++) { + line[x] = ch; + } + + this.updateRange(y); +}; + +Terminal.prototype.eraseLeft = function(x, y) { + var line = this.lines[this.ybase + y] + , ch = [this.eraseAttr(), ' ']; // xterm + + x++; + while (x--) line[x] = ch; + + this.updateRange(y); +}; + +Terminal.prototype.eraseLine = function(y) { + this.eraseRight(0, y); +}; + +Terminal.prototype.blankLine = function(cur) { + var attr = cur + ? this.eraseAttr() + : this.defAttr; + + var ch = [attr, ' '] + , line = [] + , i = 0; + + for (; i < this.cols; i++) { + line[i] = ch; + } + + return line; +}; + +Terminal.prototype.ch = function(cur) { + return cur + ? [this.eraseAttr(), ' '] + : [this.defAttr, ' ']; +}; + +Terminal.prototype.is = function(term) { + var name = this.termName; + return (name + '').indexOf(term) === 0; +}; + +Terminal.prototype.handler = function(data) { + this.emit('data', data); +}; + +Terminal.prototype.handleTitle = function(title) { + this.emit('title', title); +}; + +/** + * ESC + */ + +// ESC D Index (IND is 0x84). +Terminal.prototype.index = function() { + this.y++; + if (this.y > this.scrollBottom) { + this.y--; + this.scroll(); + } + this.state = normal; +}; + +// ESC M Reverse Index (RI is 0x8d). +Terminal.prototype.reverseIndex = function() { + var j; + this.y--; + if (this.y < this.scrollTop) { + this.y++; + // 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.lines.splice(this.y + this.ybase, 0, this.blankLine(true)); + j = this.rows - 1 - this.scrollBottom; + this.lines.splice(this.rows - 1 + this.ybase - j + 1, 1); + // this.maxRange(); + this.updateRange(this.scrollTop); + this.updateRange(this.scrollBottom); + } + this.state = normal; +}; + +// ESC c Full Reset (RIS). +Terminal.prototype.reset = function() { + this.options.rows = this.rows; + this.options.cols = this.cols; + Terminal.call(this, this.options); + this.refresh(0, this.rows - 1); +}; + +// ESC H Tab Set (HTS is 0x88). +Terminal.prototype.tabSet = function() { + this.tabs[this.x] = true; + this.state = normal; +}; + +/** + * CSI + */ + +// CSI Ps A +// Cursor Up Ps Times (default = 1) (CUU). +Terminal.prototype.cursorUp = function(params) { + var param = params[0]; + if (param < 1) param = 1; + this.y -= param; + if (this.y < 0) this.y = 0; +}; + +// CSI Ps B +// Cursor Down Ps Times (default = 1) (CUD). +Terminal.prototype.cursorDown = function(params) { + var param = params[0]; + if (param < 1) param = 1; + this.y += param; + if (this.y >= this.rows) { + this.y = this.rows - 1; + } +}; + +// CSI Ps C +// Cursor Forward Ps Times (default = 1) (CUF). +Terminal.prototype.cursorForward = function(params) { + var param = params[0]; + if (param < 1) param = 1; + this.x += param; + if (this.x >= this.cols) { + this.x = this.cols - 1; + } +}; + +// CSI Ps D +// Cursor Backward Ps Times (default = 1) (CUB). +Terminal.prototype.cursorBackward = function(params) { + var param = params[0]; + if (param < 1) param = 1; + this.x -= param; + if (this.x < 0) this.x = 0; +}; + +// CSI Ps ; Ps H +// Cursor Position [row;column] (default = [1,1]) (CUP). +Terminal.prototype.cursorPos = 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.rows) { + row = this.rows - 1; + } + + if (col < 0) { + col = 0; + } else if (col >= this.cols) { + col = this.cols - 1; + } + + this.x = col; + this.y = row; +}; + +// CSI Ps J Erase in Display (ED). +// Ps = 0 -> Erase Below (default). +// Ps = 1 -> Erase Above. +// Ps = 2 -> Erase All. +// Ps = 3 -> Erase Saved Lines (xterm). +// CSI ? Ps J +// Erase in Display (DECSED). +// Ps = 0 -> Selective Erase Below (default). +// Ps = 1 -> Selective Erase Above. +// Ps = 2 -> Selective Erase All. +Terminal.prototype.eraseInDisplay = function(params) { + var j; + switch (params[0]) { + case 0: + this.eraseRight(this.x, this.y); + j = this.y + 1; + for (; j < this.rows; j++) { + this.eraseLine(j); + } + break; + case 1: + this.eraseLeft(this.x, this.y); + j = this.y; + while (j--) { + this.eraseLine(j); + } + break; + case 2: + j = this.rows; + while (j--) this.eraseLine(j); + break; + case 3: + ; // no saved lines + break; + } +}; + +// CSI Ps K Erase in Line (EL). +// Ps = 0 -> Erase to Right (default). +// Ps = 1 -> Erase to Left. +// Ps = 2 -> Erase All. +// CSI ? Ps K +// Erase in Line (DECSEL). +// Ps = 0 -> Selective Erase to Right (default). +// Ps = 1 -> Selective Erase to Left. +// Ps = 2 -> Selective Erase All. +Terminal.prototype.eraseInLine = function(params) { + switch (params[0]) { + case 0: + this.eraseRight(this.x, this.y); + break; + case 1: + this.eraseLeft(this.x, this.y); + break; + case 2: + this.eraseLine(this.y); + break; + } +}; + +// CSI Pm m Character Attributes (SGR). +// Ps = 0 -> Normal (default). +// Ps = 1 -> Bold. +// Ps = 4 -> Underlined. +// Ps = 5 -> Blink (appears as Bold). +// Ps = 7 -> Inverse. +// Ps = 8 -> Invisible, i.e., hidden (VT300). +// Ps = 2 2 -> Normal (neither bold nor faint). +// Ps = 2 4 -> Not underlined. +// Ps = 2 5 -> Steady (not blinking). +// Ps = 2 7 -> Positive (not inverse). +// Ps = 2 8 -> Visible, i.e., not hidden (VT300). +// Ps = 3 0 -> Set foreground color to Black. +// Ps = 3 1 -> Set foreground color to Red. +// Ps = 3 2 -> Set foreground color to Green. +// Ps = 3 3 -> Set foreground color to Yellow. +// Ps = 3 4 -> Set foreground color to Blue. +// Ps = 3 5 -> Set foreground color to Magenta. +// Ps = 3 6 -> Set foreground color to Cyan. +// Ps = 3 7 -> Set foreground color to White. +// Ps = 3 9 -> Set foreground color to default (original). +// Ps = 4 0 -> Set background color to Black. +// Ps = 4 1 -> Set background color to Red. +// Ps = 4 2 -> Set background color to Green. +// Ps = 4 3 -> Set background color to Yellow. +// Ps = 4 4 -> Set background color to Blue. +// Ps = 4 5 -> Set background color to Magenta. +// Ps = 4 6 -> Set background color to Cyan. +// Ps = 4 7 -> Set background color to White. +// Ps = 4 9 -> Set background color to default (original). + +// If 16-color support is compiled, the following apply. Assume +// that xterm's resources are set so that the ISO color codes are +// the first 8 of a set of 16. Then the aixterm colors are the +// bright versions of the ISO colors: +// Ps = 9 0 -> Set foreground color to Black. +// Ps = 9 1 -> Set foreground color to Red. +// Ps = 9 2 -> Set foreground color to Green. +// Ps = 9 3 -> Set foreground color to Yellow. +// Ps = 9 4 -> Set foreground color to Blue. +// Ps = 9 5 -> Set foreground color to Magenta. +// Ps = 9 6 -> Set foreground color to Cyan. +// Ps = 9 7 -> Set foreground color to White. +// Ps = 1 0 0 -> Set background color to Black. +// Ps = 1 0 1 -> Set background color to Red. +// Ps = 1 0 2 -> Set background color to Green. +// Ps = 1 0 3 -> Set background color to Yellow. +// Ps = 1 0 4 -> Set background color to Blue. +// Ps = 1 0 5 -> Set background color to Magenta. +// Ps = 1 0 6 -> Set background color to Cyan. +// Ps = 1 0 7 -> Set background color to White. + +// If xterm is compiled with the 16-color support disabled, it +// supports the following, from rxvt: +// Ps = 1 0 0 -> Set foreground and background color to +// default. + +// If 88- or 256-color support is compiled, the following apply. +// Ps = 3 8 ; 5 ; Ps -> Set foreground color to the second +// Ps. +// Ps = 4 8 ; 5 ; Ps -> Set background color to the second +// Ps. +Terminal.prototype.charAttributes = function(params) { + // Optimize a single SGR0. + if (params.length === 1 && params[0] === 0) { + this.curAttr = this.defAttr; + return; + } + + var l = params.length + , i = 0 + , flags = this.curAttr >> 18 + , fg = (this.curAttr >> 9) & 0x1ff + , bg = this.curAttr & 0x1ff + , p; + + for (; i < l; i++) { + p = params[i]; + if (p >= 30 && p <= 37) { + // fg color 8 + fg = p - 30; + } else if (p >= 40 && p <= 47) { + // bg color 8 + bg = p - 40; + } else if (p >= 90 && p <= 97) { + // fg color 16 + p += 8; + fg = p - 90; + } else if (p >= 100 && p <= 107) { + // bg color 16 + p += 8; + bg = p - 100; + } else if (p === 0) { + // default + flags = this.defAttr >> 18; + fg = (this.defAttr >> 9) & 0x1ff; + bg = this.defAttr & 0x1ff; + // flags = 0; + // fg = 0x1ff; + // bg = 0x1ff; + } else if (p === 1) { + // bold text + flags |= 1; + } else if (p === 4) { + // underlined text + flags |= 2; + } else if (p === 5) { + // blink + flags |= 4; + } else if (p === 7) { + // inverse and positive + // test with: echo -e '\e[31m\e[42mhello\e[7mworld\e[27mhi\e[m' + flags |= 8; + } else if (p === 8) { + // invisible + flags |= 16; + } else if (p === 22) { + // not bold + flags &= ~1; + } else if (p === 24) { + // not underlined + flags &= ~2; + } else if (p === 25) { + // not blink + flags &= ~4; + } else if (p === 27) { + // not inverse + flags &= ~8; + } else if (p === 28) { + // not invisible + flags &= ~16; + } else if (p === 39) { + // reset fg + fg = (this.defAttr >> 9) & 0x1ff; + } else if (p === 49) { + // reset bg + bg = this.defAttr & 0x1ff; + } else if (p === 38) { + // fg color 256 + if (params[i + 1] === 2) { + i += 2; + fg = 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) { + // bg color 256 + if (params[i + 1] === 2) { + i += 2; + bg = 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) { + // reset fg/bg + fg = (this.defAttr >> 9) & 0x1ff; + bg = this.defAttr & 0x1ff; + } else { + this.error('Unknown SGR attribute: %d.', p); + } + } + + this.curAttr = (flags << 18) | (fg << 9) | bg; +}; + +// CSI Ps n Device Status Report (DSR). +// Ps = 5 -> Status Report. Result (``OK'') is +// CSI 0 n +// Ps = 6 -> Report Cursor Position (CPR) [row;column]. +// Result is +// CSI r ; c R +// CSI ? Ps n +// Device Status Report (DSR, DEC-specific). +// Ps = 6 -> Report Cursor Position (CPR) [row;column] as CSI +// ? r ; c R (assumes page is zero). +// Ps = 1 5 -> Report Printer status as CSI ? 1 0 n (ready). +// or CSI ? 1 1 n (not ready). +// Ps = 2 5 -> Report UDK status as CSI ? 2 0 n (unlocked) +// or CSI ? 2 1 n (locked). +// Ps = 2 6 -> Report Keyboard status as +// CSI ? 2 7 ; 1 ; 0 ; 0 n (North American). +// The last two parameters apply to VT400 & up, and denote key- +// board ready and LK01 respectively. +// Ps = 5 3 -> Report Locator status as +// CSI ? 5 3 n Locator available, if compiled-in, or +// CSI ? 5 0 n No Locator, if not. +Terminal.prototype.deviceStatus = function(params) { + if (!this.prefix) { + switch (params[0]) { + case 5: + // status report + this.send('\x1b[0n'); + break; + case 6: + // cursor position + this.send('\x1b[' + + (this.y + 1) + + ';' + + (this.x + 1) + + 'R'); + break; + } + } else if (this.prefix === '?') { + // modern xterm doesnt seem to + // respond to any of these except ?6, 6, and 5 + switch (params[0]) { + case 6: + // cursor position + this.send('\x1b[?' + + (this.y + 1) + + ';' + + (this.x + 1) + + 'R'); + break; + case 15: + // no printer + // this.send('\x1b[?11n'); + break; + case 25: + // dont support user defined keys + // this.send('\x1b[?21n'); + break; + case 26: + // north american keyboard + // this.send('\x1b[?27;1;0;0n'); + break; + case 53: + // no dec locator/mouse + // this.send('\x1b[?50n'); + break; + } + } +}; + +/** + * Additions + */ + +// CSI Ps @ +// Insert Ps (Blank) Character(s) (default = 1) (ICH). +Terminal.prototype.insertChars = function(params) { + var param, row, j, ch; + + param = params[0]; + if (param < 1) param = 1; + + row = this.y + this.ybase; + j = this.x; + ch = [this.eraseAttr(), ' ']; // xterm + + while (param-- && j < this.cols) { + this.lines[row].splice(j++, 0, ch); + this.lines[row].pop(); + } +}; + +// CSI Ps E +// Cursor Next Line Ps Times (default = 1) (CNL). +// same as CSI Ps B ? +Terminal.prototype.cursorNextLine = function(params) { + var param = params[0]; + if (param < 1) param = 1; + this.y += param; + if (this.y >= this.rows) { + this.y = this.rows - 1; + } + this.x = 0; +}; + +// CSI Ps F +// Cursor Preceding Line Ps Times (default = 1) (CNL). +// reuse CSI Ps A ? +Terminal.prototype.cursorPrecedingLine = function(params) { + var param = params[0]; + if (param < 1) param = 1; + this.y -= param; + if (this.y < 0) this.y = 0; + this.x = 0; +}; + +// CSI Ps G +// Cursor Character Absolute [column] (default = [row,1]) (CHA). +Terminal.prototype.cursorCharAbsolute = function(params) { + var param = params[0]; + if (param < 1) param = 1; + this.x = param - 1; +}; + +// CSI Ps L +// Insert Ps Line(s) (default = 1) (IL). +Terminal.prototype.insertLines = function(params) { + var param, row, j; + + param = params[0]; + if (param < 1) param = 1; + row = this.y + this.ybase; + + j = this.rows - 1 - this.scrollBottom; + j = this.rows - 1 + this.ybase - j + 1; + + while (param--) { + // test: echo -e '\e[44m\e[1L\e[0m' + // blankLine(true) - xterm/linux behavior + this.lines.splice(row, 0, this.blankLine(true)); + this.lines.splice(j, 1); + } + + // this.maxRange(); + this.updateRange(this.y); + this.updateRange(this.scrollBottom); +}; + +// CSI Ps M +// Delete Ps Line(s) (default = 1) (DL). +Terminal.prototype.deleteLines = function(params) { + var param, row, j; + + param = params[0]; + if (param < 1) param = 1; + row = this.y + this.ybase; + + j = this.rows - 1 - this.scrollBottom; + j = this.rows - 1 + this.ybase - j; + + while (param--) { + // test: echo -e '\e[44m\e[1M\e[0m' + // blankLine(true) - xterm/linux behavior + this.lines.splice(j + 1, 0, this.blankLine(true)); + this.lines.splice(row, 1); + } + + // this.maxRange(); + this.updateRange(this.y); + this.updateRange(this.scrollBottom); +}; + +// CSI Ps P +// Delete Ps Character(s) (default = 1) (DCH). +Terminal.prototype.deleteChars = function(params) { + var param, row, ch; + + param = params[0]; + if (param < 1) param = 1; + + row = this.y + this.ybase; + ch = [this.eraseAttr(), ' ']; // xterm + + while (param--) { + this.lines[row].splice(this.x, 1); + this.lines[row].push(ch); + } +}; + +// CSI Ps X +// Erase Ps Character(s) (default = 1) (ECH). +Terminal.prototype.eraseChars = function(params) { + var param, row, j, ch; + + param = params[0]; + if (param < 1) param = 1; + + row = this.y + this.ybase; + j = this.x; + ch = [this.eraseAttr(), ' ']; // xterm + + while (param-- && j < this.cols) { + this.lines[row][j++] = ch; + } +}; + +// CSI Pm ` Character Position Absolute +// [column] (default = [row,1]) (HPA). +Terminal.prototype.charPosAbsolute = function(params) { + var param = params[0]; + if (param < 1) param = 1; + this.x = param - 1; + if (this.x >= this.cols) { + this.x = this.cols - 1; + } +}; + +// 141 61 a * HPR - +// Horizontal Position Relative +// reuse CSI Ps C ? +Terminal.prototype.HPositionRelative = function(params) { + var param = params[0]; + if (param < 1) param = 1; + this.x += param; + if (this.x >= this.cols) { + this.x = this.cols - 1; + } +}; + +// CSI Ps c Send Device Attributes (Primary DA). +// Ps = 0 or omitted -> request attributes from terminal. The +// response depends on the decTerminalID resource setting. +// -> CSI ? 1 ; 2 c (``VT100 with Advanced Video Option'') +// -> CSI ? 1 ; 0 c (``VT101 with No Options'') +// -> CSI ? 6 c (``VT102'') +// -> CSI ? 6 0 ; 1 ; 2 ; 6 ; 8 ; 9 ; 1 5 ; c (``VT220'') +// The VT100-style response parameters do not mean anything by +// themselves. VT220 parameters do, telling the host what fea- +// tures the terminal supports: +// Ps = 1 -> 132-columns. +// Ps = 2 -> Printer. +// Ps = 6 -> Selective erase. +// Ps = 8 -> User-defined keys. +// Ps = 9 -> National replacement character sets. +// Ps = 1 5 -> Technical characters. +// Ps = 2 2 -> ANSI color, e.g., VT525. +// Ps = 2 9 -> ANSI text locator (i.e., DEC Locator mode). +// CSI > Ps c +// Send Device Attributes (Secondary DA). +// Ps = 0 or omitted -> request the terminal's identification +// code. The response depends on the decTerminalID resource set- +// ting. It should apply only to VT220 and up, but xterm extends +// this to VT100. +// -> CSI > Pp ; Pv ; Pc c +// where Pp denotes the terminal type +// Pp = 0 -> ``VT100''. +// Pp = 1 -> ``VT220''. +// and Pv is the firmware version (for xterm, this was originally +// the XFree86 patch number, starting with 95). In a DEC termi- +// nal, Pc indicates the ROM cartridge registration number and is +// always zero. +// More information: +// xterm/charproc.c - line 2012, for more information. +// vim responds with ^[[?0c or ^[[?1c after the terminal's response (?) +Terminal.prototype.sendDeviceAttributes = function(params) { + if (params[0] > 0) return; + + if (!this.prefix) { + if (this.is('xterm') + || this.is('rxvt-unicode') + || this.is('screen')) { + this.send('\x1b[?1;2c'); + } else if (this.is('linux')) { + this.send('\x1b[?6c'); + } + } else if (this.prefix === '>') { + // xterm and urxvt + // seem to spit this + // out around ~370 times (?). + if (this.is('xterm')) { + this.send('\x1b[>0;276;0c'); + } else if (this.is('rxvt-unicode')) { + this.send('\x1b[>85;95;0c'); + } else if (this.is('linux')) { + // not supported by linux console. + // linux console echoes parameters. + this.send(params[0] + 'c'); + } else if (this.is('screen')) { + this.send('\x1b[>83;40003;0c'); + } + } +}; + +// CSI Pm d +// Line Position Absolute [row] (default = [1,column]) (VPA). +Terminal.prototype.linePosAbsolute = function(params) { + var param = params[0]; + if (param < 1) param = 1; + this.y = param - 1; + if (this.y >= this.rows) { + this.y = this.rows - 1; + } +}; + +// 145 65 e * VPR - Vertical Position Relative +// reuse CSI Ps B ? +Terminal.prototype.VPositionRelative = function(params) { + var param = params[0]; + if (param < 1) param = 1; + this.y += param; + if (this.y >= this.rows) { + this.y = this.rows - 1; + } +}; + +// CSI Ps ; Ps f +// Horizontal and Vertical Position [row;column] (default = +// [1,1]) (HVP). +Terminal.prototype.HVPosition = function(params) { + if (params[0] < 1) params[0] = 1; + if (params[1] < 1) params[1] = 1; + + this.y = params[0] - 1; + if (this.y >= this.rows) { + this.y = this.rows - 1; + } + + this.x = params[1] - 1; + if (this.x >= this.cols) { + this.x = this.cols - 1; + } +}; + +// CSI Pm h Set Mode (SM). +// Ps = 2 -> Keyboard Action Mode (AM). +// Ps = 4 -> Insert Mode (IRM). +// Ps = 1 2 -> Send/receive (SRM). +// Ps = 2 0 -> Automatic Newline (LNM). +// CSI ? Pm h +// DEC Private Mode Set (DECSET). +// Ps = 1 -> Application Cursor Keys (DECCKM). +// Ps = 2 -> Designate USASCII for character sets G0-G3 +// (DECANM), and set VT100 mode. +// Ps = 3 -> 132 Column Mode (DECCOLM). +// Ps = 4 -> Smooth (Slow) Scroll (DECSCLM). +// Ps = 5 -> Reverse Video (DECSCNM). +// Ps = 6 -> Origin Mode (DECOM). +// Ps = 7 -> Wraparound Mode (DECAWM). +// Ps = 8 -> Auto-repeat Keys (DECARM). +// Ps = 9 -> Send Mouse X & Y on button press. See the sec- +// tion Mouse Tracking. +// Ps = 1 0 -> Show toolbar (rxvt). +// Ps = 1 2 -> Start Blinking Cursor (att610). +// Ps = 1 8 -> Print form feed (DECPFF). +// Ps = 1 9 -> Set print extent to full screen (DECPEX). +// Ps = 2 5 -> Show Cursor (DECTCEM). +// Ps = 3 0 -> Show scrollbar (rxvt). +// Ps = 3 5 -> Enable font-shifting functions (rxvt). +// Ps = 3 8 -> Enter Tektronix Mode (DECTEK). +// Ps = 4 0 -> Allow 80 -> 132 Mode. +// Ps = 4 1 -> more(1) fix (see curses resource). +// Ps = 4 2 -> Enable Nation Replacement Character sets (DECN- +// RCM). +// Ps = 4 4 -> Turn On Margin Bell. +// Ps = 4 5 -> Reverse-wraparound Mode. +// Ps = 4 6 -> Start Logging. This is normally disabled by a +// compile-time option. +// Ps = 4 7 -> Use Alternate Screen Buffer. (This may be dis- +// abled by the titeInhibit resource). +// Ps = 6 6 -> Application keypad (DECNKM). +// Ps = 6 7 -> Backarrow key sends backspace (DECBKM). +// Ps = 1 0 0 0 -> Send Mouse X & Y on button press and +// release. See the section Mouse Tracking. +// Ps = 1 0 0 1 -> Use Hilite Mouse Tracking. +// Ps = 1 0 0 2 -> Use Cell Motion Mouse Tracking. +// Ps = 1 0 0 3 -> Use All Motion Mouse Tracking. +// Ps = 1 0 0 4 -> Send FocusIn/FocusOut events. +// Ps = 1 0 0 5 -> Enable Extended Mouse Mode. +// Ps = 1 0 1 0 -> Scroll to bottom on tty output (rxvt). +// Ps = 1 0 1 1 -> Scroll to bottom on key press (rxvt). +// Ps = 1 0 3 4 -> Interpret "meta" key, sets eighth bit. +// (enables the eightBitInput resource). +// Ps = 1 0 3 5 -> Enable special modifiers for Alt and Num- +// Lock keys. (This enables the numLock resource). +// Ps = 1 0 3 6 -> Send ESC when Meta modifies a key. (This +// enables the metaSendsEscape resource). +// Ps = 1 0 3 7 -> Send DEL from the editing-keypad Delete +// key. +// Ps = 1 0 3 9 -> Send ESC when Alt modifies a key. (This +// enables the altSendsEscape resource). +// Ps = 1 0 4 0 -> Keep selection even if not highlighted. +// (This enables the keepSelection resource). +// Ps = 1 0 4 1 -> Use the CLIPBOARD selection. (This enables +// the selectToClipboard resource). +// Ps = 1 0 4 2 -> Enable Urgency window manager hint when +// Control-G is received. (This enables the bellIsUrgent +// resource). +// Ps = 1 0 4 3 -> Enable raising of the window when Control-G +// is received. (enables the popOnBell resource). +// Ps = 1 0 4 7 -> Use Alternate Screen Buffer. (This may be +// disabled by the titeInhibit resource). +// Ps = 1 0 4 8 -> Save cursor as in DECSC. (This may be dis- +// abled by the titeInhibit resource). +// Ps = 1 0 4 9 -> Save cursor as in DECSC and use Alternate +// Screen Buffer, clearing it first. (This may be disabled by +// the titeInhibit resource). This combines the effects of the 1 +// 0 4 7 and 1 0 4 8 modes. Use this with terminfo-based +// applications rather than the 4 7 mode. +// Ps = 1 0 5 0 -> Set terminfo/termcap function-key mode. +// Ps = 1 0 5 1 -> Set Sun function-key mode. +// Ps = 1 0 5 2 -> Set HP function-key mode. +// Ps = 1 0 5 3 -> Set SCO function-key mode. +// Ps = 1 0 6 0 -> Set legacy keyboard emulation (X11R6). +// Ps = 1 0 6 1 -> Set VT220 keyboard emulation. +// Ps = 2 0 0 4 -> Set bracketed paste mode. +// Modes: +// http://vt100.net/docs/vt220-rm/chapter4.html +Terminal.prototype.setMode = function(params) { + if (typeof params === 'object') { + var l = params.length + , i = 0; + + for (; i < l; i++) { + this.setMode(params[i]); + } + + return; + } + + if (!this.prefix) { + switch (params) { + case 4: + this.insertMode = true; + break; + case 20: + //this.convertEol = true; + break; + } + } else if (this.prefix === '?') { + switch (params) { + case 1: + this.applicationCursor = true; + break; + case 2: + this.setgCharset(0, Terminal.charsets.US); + this.setgCharset(1, Terminal.charsets.US); + this.setgCharset(2, Terminal.charsets.US); + this.setgCharset(3, Terminal.charsets.US); + // set VT100 mode here + break; + case 3: // 132 col mode + this.savedCols = this.cols; + this.resize(132, this.rows); + break; + case 6: + this.originMode = true; + break; + case 7: + this.wraparoundMode = true; + break; + case 12: + // this.cursorBlink = true; + break; + case 66: + this.log('Serial port requested application keypad.'); + this.applicationKeypad = true; + break; + case 9: // X10 Mouse + // no release, no motion, no wheel, no modifiers. + case 1000: // vt200 mouse + // no motion. + // no modifiers, except control on the wheel. + case 1002: // button event mouse + case 1003: // any event mouse + // any event - sends motion events, + // even if there is no button held down. + this.x10Mouse = params === 9; + this.vt200Mouse = params === 1000; + this.normalMouse = params > 1000; + this.mouseEvents = true; + this.element.style.cursor = 'default'; + this.log('Binding to mouse events.'); + break; + case 1004: // send focusin/focusout events + // focusin: ^[[I + // focusout: ^[[O + this.sendFocus = true; + break; + case 1005: // utf8 ext mode mouse + this.utfMouse = true; + // for wide terminals + // simply encodes large values as utf8 characters + break; + case 1006: // sgr ext mode mouse + this.sgrMouse = true; + // for wide terminals + // does not add 32 to fields + // press: ^[[<b;x;yM + // release: ^[[<b;x;ym + break; + case 1015: // urxvt ext mode mouse + this.urxvtMouse = true; + // for wide terminals + // numbers for fields + // press: ^[[b;x;yM + // motion: ^[[b;x;yT + break; + case 25: // show cursor + this.cursorHidden = false; + break; + case 1049: // alt screen buffer cursor + //this.saveCursor(); + ; // FALL-THROUGH + case 47: // alt screen buffer + case 1047: // alt screen buffer + if (!this.normal) { + var normal = { + lines: this.lines, + ybase: this.ybase, + ydisp: this.ydisp, + x: this.x, + y: this.y, + scrollTop: this.scrollTop, + scrollBottom: this.scrollBottom, + tabs: this.tabs + // XXX save charset(s) here? + // charset: this.charset, + // glevel: this.glevel, + // charsets: this.charsets + }; + this.reset(); + this.normal = normal; + this.showCursor(); + } + break; + } + } +}; + +// CSI Pm l Reset Mode (RM). +// Ps = 2 -> Keyboard Action Mode (AM). +// Ps = 4 -> Replace Mode (IRM). +// Ps = 1 2 -> Send/receive (SRM). +// Ps = 2 0 -> Normal Linefeed (LNM). +// CSI ? Pm l +// DEC Private Mode Reset (DECRST). +// Ps = 1 -> Normal Cursor Keys (DECCKM). +// Ps = 2 -> Designate VT52 mode (DECANM). +// Ps = 3 -> 80 Column Mode (DECCOLM). +// Ps = 4 -> Jump (Fast) Scroll (DECSCLM). +// Ps = 5 -> Normal Video (DECSCNM). +// Ps = 6 -> Normal Cursor Mode (DECOM). +// Ps = 7 -> No Wraparound Mode (DECAWM). +// Ps = 8 -> No Auto-repeat Keys (DECARM). +// Ps = 9 -> Don't send Mouse X & Y on button press. +// Ps = 1 0 -> Hide toolbar (rxvt). +// Ps = 1 2 -> Stop Blinking Cursor (att610). +// Ps = 1 8 -> Don't print form feed (DECPFF). +// Ps = 1 9 -> Limit print to scrolling region (DECPEX). +// Ps = 2 5 -> Hide Cursor (DECTCEM). +// Ps = 3 0 -> Don't show scrollbar (rxvt). +// Ps = 3 5 -> Disable font-shifting functions (rxvt). +// Ps = 4 0 -> Disallow 80 -> 132 Mode. +// Ps = 4 1 -> No more(1) fix (see curses resource). +// Ps = 4 2 -> Disable Nation Replacement Character sets (DEC- +// NRCM). +// Ps = 4 4 -> Turn Off Margin Bell. +// Ps = 4 5 -> No Reverse-wraparound Mode. +// Ps = 4 6 -> Stop Logging. (This is normally disabled by a +// compile-time option). +// Ps = 4 7 -> Use Normal Screen Buffer. +// Ps = 6 6 -> Numeric keypad (DECNKM). +// Ps = 6 7 -> Backarrow key sends delete (DECBKM). +// Ps = 1 0 0 0 -> Don't send Mouse X & Y on button press and +// release. See the section Mouse Tracking. +// Ps = 1 0 0 1 -> Don't use Hilite Mouse Tracking. +// Ps = 1 0 0 2 -> Don't use Cell Motion Mouse Tracking. +// Ps = 1 0 0 3 -> Don't use All Motion Mouse Tracking. +// Ps = 1 0 0 4 -> Don't send FocusIn/FocusOut events. +// Ps = 1 0 0 5 -> Disable Extended Mouse Mode. +// Ps = 1 0 1 0 -> Don't scroll to bottom on tty output +// (rxvt). +// Ps = 1 0 1 1 -> Don't scroll to bottom on key press (rxvt). +// Ps = 1 0 3 4 -> Don't interpret "meta" key. (This disables +// the eightBitInput resource). +// Ps = 1 0 3 5 -> Disable special modifiers for Alt and Num- +// Lock keys. (This disables the numLock resource). +// Ps = 1 0 3 6 -> Don't send ESC when Meta modifies a key. +// (This disables the metaSendsEscape resource). +// Ps = 1 0 3 7 -> Send VT220 Remove from the editing-keypad +// Delete key. +// Ps = 1 0 3 9 -> Don't send ESC when Alt modifies a key. +// (This disables the altSendsEscape resource). +// Ps = 1 0 4 0 -> Do not keep selection when not highlighted. +// (This disables the keepSelection resource). +// Ps = 1 0 4 1 -> Use the PRIMARY selection. (This disables +// the selectToClipboard resource). +// Ps = 1 0 4 2 -> Disable Urgency window manager hint when +// Control-G is received. (This disables the bellIsUrgent +// resource). +// Ps = 1 0 4 3 -> Disable raising of the window when Control- +// G is received. (This disables the popOnBell resource). +// Ps = 1 0 4 7 -> Use Normal Screen Buffer, clearing screen +// first if in the Alternate Screen. (This may be disabled by +// the titeInhibit resource). +// Ps = 1 0 4 8 -> Restore cursor as in DECRC. (This may be +// disabled by the titeInhibit resource). +// Ps = 1 0 4 9 -> Use Normal Screen Buffer and restore cursor +// as in DECRC. (This may be disabled by the titeInhibit +// resource). This combines the effects of the 1 0 4 7 and 1 0 +// 4 8 modes. Use this with terminfo-based applications rather +// than the 4 7 mode. +// Ps = 1 0 5 0 -> Reset terminfo/termcap function-key mode. +// Ps = 1 0 5 1 -> Reset Sun function-key mode. +// Ps = 1 0 5 2 -> Reset HP function-key mode. +// Ps = 1 0 5 3 -> Reset SCO function-key mode. +// Ps = 1 0 6 0 -> Reset legacy keyboard emulation (X11R6). +// Ps = 1 0 6 1 -> Reset keyboard emulation to Sun/PC style. +// Ps = 2 0 0 4 -> Reset bracketed paste mode. +Terminal.prototype.resetMode = function(params) { + if (typeof params === 'object') { + var l = params.length + , i = 0; + + for (; i < l; i++) { + this.resetMode(params[i]); + } + + return; + } + + if (!this.prefix) { + switch (params) { + case 4: + this.insertMode = false; + break; + case 20: + //this.convertEol = false; + break; + } + } else if (this.prefix === '?') { + switch (params) { + case 1: + this.applicationCursor = false; + break; + case 3: + if (this.cols === 132 && this.savedCols) { + this.resize(this.savedCols, this.rows); + } + delete this.savedCols; + break; + case 6: + this.originMode = false; + break; + case 7: + this.wraparoundMode = false; + break; + case 12: + // this.cursorBlink = false; + break; + case 66: + this.log('Switching back to normal keypad.'); + this.applicationKeypad = false; + break; + case 9: // X10 Mouse + case 1000: // vt200 mouse + case 1002: // button event mouse + case 1003: // any event mouse + this.x10Mouse = false; + this.vt200Mouse = false; + this.normalMouse = false; + this.mouseEvents = false; + this.element.style.cursor = ''; + break; + case 1004: // send focusin/focusout events + this.sendFocus = false; + break; + case 1005: // utf8 ext mode mouse + this.utfMouse = false; + break; + case 1006: // sgr ext mode mouse + this.sgrMouse = false; + break; + case 1015: // urxvt ext mode mouse + this.urxvtMouse = false; + break; + case 25: // hide cursor + this.cursorHidden = true; + break; + case 1049: // alt screen buffer cursor + ; // FALL-THROUGH + case 47: // normal screen buffer + case 1047: // normal screen buffer - clearing it first + if (this.normal) { + this.lines = this.normal.lines; + this.ybase = this.normal.ybase; + this.ydisp = this.normal.ydisp; + this.x = this.normal.x; + this.y = this.normal.y; + this.scrollTop = this.normal.scrollTop; + this.scrollBottom = this.normal.scrollBottom; + this.tabs = this.normal.tabs; + this.normal = null; + // if (params === 1049) { + // this.x = this.savedX; + // this.y = this.savedY; + // } + this.refresh(0, this.rows - 1); + this.showCursor(); + } + break; + } + } +}; + +// CSI Ps ; Ps r +// Set Scrolling Region [top;bottom] (default = full size of win- +// dow) (DECSTBM). +// CSI ? Pm r +Terminal.prototype.setScrollRegion = function(params) { + if (this.prefix) return; + this.scrollTop = (params[0] || 1) - 1; + this.scrollBottom = (params[1] || this.rows) - 1; + this.x = 0; + this.y = 0; +}; + +// CSI s +// Save cursor (ANSI.SYS). +Terminal.prototype.saveCursor = function(params) { + this.savedX = this.x; + this.savedY = this.y; +}; + +// CSI u +// Restore cursor (ANSI.SYS). +Terminal.prototype.restoreCursor = function(params) { + this.x = this.savedX || 0; + this.y = this.savedY || 0; +}; + +/** + * Lesser Used + */ + +// CSI Ps I +// Cursor Forward Tabulation Ps tab stops (default = 1) (CHT). +Terminal.prototype.cursorForwardTab = function(params) { + var param = params[0] || 1; + while (param--) { + this.x = this.nextStop(); + } +}; + +// CSI Ps S Scroll up Ps lines (default = 1) (SU). +Terminal.prototype.scrollUp = function(params) { + var param = params[0] || 1; + while (param--) { + this.lines.splice(this.ybase + this.scrollTop, 1); + this.lines.splice(this.ybase + this.scrollBottom, 0, this.blankLine()); + } + // this.maxRange(); + this.updateRange(this.scrollTop); + this.updateRange(this.scrollBottom); +}; + +// CSI Ps T Scroll down Ps lines (default = 1) (SD). +Terminal.prototype.scrollDown = function(params) { + var param = params[0] || 1; + while (param--) { + this.lines.splice(this.ybase + this.scrollBottom, 1); + this.lines.splice(this.ybase + this.scrollTop, 0, this.blankLine()); + } + // this.maxRange(); + this.updateRange(this.scrollTop); + this.updateRange(this.scrollBottom); +}; + +// CSI Ps ; Ps ; Ps ; Ps ; Ps T +// Initiate highlight mouse tracking. Parameters are +// [func;startx;starty;firstrow;lastrow]. See the section Mouse +// Tracking. +Terminal.prototype.initMouseTracking = function(params) { + // Relevant: DECSET 1001 +}; + +// CSI > Ps; Ps T +// Reset one or more features of the title modes to the default +// value. Normally, "reset" disables the feature. It is possi- +// ble to disable the ability to reset features by compiling a +// different default for the title modes into xterm. +// Ps = 0 -> Do not set window/icon labels using hexadecimal. +// Ps = 1 -> Do not query window/icon labels using hexadeci- +// mal. +// Ps = 2 -> Do not set window/icon labels using UTF-8. +// Ps = 3 -> Do not query window/icon labels using UTF-8. +// (See discussion of "Title Modes"). +Terminal.prototype.resetTitleModes = function(params) { + ; +}; + +// CSI Ps Z Cursor Backward Tabulation Ps tab stops (default = 1) (CBT). +Terminal.prototype.cursorBackwardTab = function(params) { + var param = params[0] || 1; + while (param--) { + this.x = this.prevStop(); + } +}; + +// CSI Ps b Repeat the preceding graphic character Ps times (REP). +Terminal.prototype.repeatPrecedingCharacter = function(params) { + var param = params[0] || 1 + , line = this.lines[this.ybase + this.y] + , ch = line[this.x - 1] || [this.defAttr, ' ']; + + while (param--) line[this.x++] = ch; +}; + +// CSI Ps g Tab Clear (TBC). +// Ps = 0 -> Clear Current Column (default). +// Ps = 3 -> Clear All. +// Potentially: +// Ps = 2 -> Clear Stops on Line. +// http://vt100.net/annarbor/aaa-ug/section6.html +Terminal.prototype.tabClear = function(params) { + var param = params[0]; + if (param <= 0) { + delete this.tabs[this.x]; + } else if (param === 3) { + this.tabs = {}; + } +}; + +// CSI Pm i Media Copy (MC). +// Ps = 0 -> Print screen (default). +// Ps = 4 -> Turn off printer controller mode. +// Ps = 5 -> Turn on printer controller mode. +// CSI ? Pm i +// Media Copy (MC, DEC-specific). +// Ps = 1 -> Print line containing cursor. +// Ps = 4 -> Turn off autoprint mode. +// Ps = 5 -> Turn on autoprint mode. +// Ps = 1 0 -> Print composed display, ignores DECPEX. +// Ps = 1 1 -> Print all pages. +Terminal.prototype.mediaCopy = function(params) { + ; +}; + +// CSI > Ps; Ps m +// Set or reset resource-values used by xterm to decide whether +// to construct escape sequences holding information about the +// modifiers pressed with a given key. The first parameter iden- +// tifies the resource to set/reset. The second parameter is the +// value to assign to the resource. If the second parameter is +// omitted, the resource is reset to its initial value. +// Ps = 1 -> modifyCursorKeys. +// Ps = 2 -> modifyFunctionKeys. +// Ps = 4 -> modifyOtherKeys. +// If no parameters are given, all resources are reset to their +// initial values. +Terminal.prototype.setResources = function(params) { + ; +}; + +// CSI > Ps n +// Disable modifiers which may be enabled via the CSI > Ps; Ps m +// sequence. This corresponds to a resource value of "-1", which +// cannot be set with the other sequence. The parameter identi- +// fies the resource to be disabled: +// Ps = 1 -> modifyCursorKeys. +// Ps = 2 -> modifyFunctionKeys. +// Ps = 4 -> modifyOtherKeys. +// If the parameter is omitted, modifyFunctionKeys is disabled. +// When modifyFunctionKeys is disabled, xterm uses the modifier +// keys to make an extended sequence of functions rather than +// adding a parameter to each function key to denote the modi- +// fiers. +Terminal.prototype.disableModifiers = function(params) { + ; +}; + +// CSI > Ps p +// Set resource value pointerMode. This is used by xterm to +// decide whether to hide the pointer cursor as the user types. +// Valid values for the parameter: +// Ps = 0 -> never hide the pointer. +// Ps = 1 -> hide if the mouse tracking mode is not enabled. +// Ps = 2 -> always hide the pointer. If no parameter is +// given, xterm uses the default, which is 1 . +Terminal.prototype.setPointerMode = function(params) { + ; +}; + +// CSI ! p Soft terminal reset (DECSTR). +// http://vt100.net/docs/vt220-rm/table4-10.html +Terminal.prototype.softReset = function(params) { + this.cursorHidden = false; + this.insertMode = false; + this.originMode = false; + this.wraparoundMode = false; // autowrap + this.applicationKeypad = false; // ? + this.applicationCursor = false; + this.scrollTop = 0; + this.scrollBottom = this.rows - 1; + this.curAttr = this.defAttr; + this.x = this.y = 0; // ? + this.charset = null; + this.glevel = 0; // ?? + this.charsets = [null]; // ?? +}; + +// CSI Ps$ p +// Request ANSI mode (DECRQM). For VT300 and up, reply is +// CSI Ps; Pm$ y +// where Ps is the mode number as in RM, and Pm is the mode +// value: +// 0 - not recognized +// 1 - set +// 2 - reset +// 3 - permanently set +// 4 - permanently reset +Terminal.prototype.requestAnsiMode = function(params) { + ; +}; + +// CSI ? Ps$ p +// Request DEC private mode (DECRQM). For VT300 and up, reply is +// CSI ? Ps; Pm$ p +// where Ps is the mode number as in DECSET, Pm is the mode value +// as in the ANSI DECRQM. +Terminal.prototype.requestPrivateMode = function(params) { + ; +}; + +// CSI Ps ; Ps " p +// Set conformance level (DECSCL). Valid values for the first +// parameter: +// Ps = 6 1 -> VT100. +// Ps = 6 2 -> VT200. +// Ps = 6 3 -> VT300. +// Valid values for the second parameter: +// Ps = 0 -> 8-bit controls. +// Ps = 1 -> 7-bit controls (always set for VT100). +// Ps = 2 -> 8-bit controls. +Terminal.prototype.setConformanceLevel = function(params) { + ; +}; + +// CSI Ps q Load LEDs (DECLL). +// Ps = 0 -> Clear all LEDS (default). +// Ps = 1 -> Light Num Lock. +// Ps = 2 -> Light Caps Lock. +// Ps = 3 -> Light Scroll Lock. +// Ps = 2 1 -> Extinguish Num Lock. +// Ps = 2 2 -> Extinguish Caps Lock. +// Ps = 2 3 -> Extinguish Scroll Lock. +Terminal.prototype.loadLEDs = function(params) { + ; +}; + +// CSI Ps SP q +// Set cursor style (DECSCUSR, VT520). +// Ps = 0 -> blinking block. +// Ps = 1 -> blinking block (default). +// Ps = 2 -> steady block. +// Ps = 3 -> blinking underline. +// Ps = 4 -> steady underline. +Terminal.prototype.setCursorStyle = function(params) { + ; +}; + +// CSI Ps " q +// Select character protection attribute (DECSCA). Valid values +// for the parameter: +// Ps = 0 -> DECSED and DECSEL can erase (default). +// Ps = 1 -> DECSED and DECSEL cannot erase. +// Ps = 2 -> DECSED and DECSEL can erase. +Terminal.prototype.setCharProtectionAttr = function(params) { + ; +}; + +// CSI ? Pm r +// Restore DEC Private Mode Values. The value of Ps previously +// saved is restored. Ps values are the same as for DECSET. +Terminal.prototype.restorePrivateValues = function(params) { + ; +}; + +// CSI Pt; Pl; Pb; Pr; Ps$ r +// Change Attributes in Rectangular Area (DECCARA), VT400 and up. +// Pt; Pl; Pb; Pr denotes the rectangle. +// Ps denotes the SGR attributes to change: 0, 1, 4, 5, 7. +// NOTE: xterm doesn't enable this code by default. +Terminal.prototype.setAttrInRectangle = function(params) { + var t = params[0] + , l = params[1] + , b = params[2] + , r = params[3] + , attr = params[4]; + + var line + , i; + + for (; t < b + 1; t++) { + line = this.lines[this.ybase + t]; + for (i = l; i < r; i++) { + line[i] = [attr, line[i][1]]; + } + } + + // this.maxRange(); + this.updateRange(params[0]); + this.updateRange(params[2]); +}; + +// CSI ? Pm s +// Save DEC Private Mode Values. Ps values are the same as for +// DECSET. +Terminal.prototype.savePrivateValues = function(params) { + ; +}; + +// CSI Ps ; Ps ; Ps t +// Window manipulation (from dtterm, as well as extensions). +// These controls may be disabled using the allowWindowOps +// resource. Valid values for the first (and any additional +// parameters) are: +// Ps = 1 -> De-iconify window. +// Ps = 2 -> Iconify window. +// Ps = 3 ; x ; y -> Move window to [x, y]. +// Ps = 4 ; height ; width -> Resize the xterm window to +// height and width in pixels. +// Ps = 5 -> Raise the xterm window to the front of the stack- +// ing order. +// Ps = 6 -> Lower the xterm window to the bottom of the +// stacking order. +// Ps = 7 -> Refresh the xterm window. +// Ps = 8 ; height ; width -> Resize the text area to +// [height;width] in characters. +// Ps = 9 ; 0 -> Restore maximized window. +// Ps = 9 ; 1 -> Maximize window (i.e., resize to screen +// size). +// Ps = 1 0 ; 0 -> Undo full-screen mode. +// Ps = 1 0 ; 1 -> Change to full-screen. +// Ps = 1 1 -> Report xterm window state. If the xterm window +// is open (non-iconified), it returns CSI 1 t . If the xterm +// window is iconified, it returns CSI 2 t . +// Ps = 1 3 -> Report xterm window position. Result is CSI 3 +// ; x ; y t +// Ps = 1 4 -> Report xterm window in pixels. Result is CSI +// 4 ; height ; width t +// Ps = 1 8 -> Report the size of the text area in characters. +// Result is CSI 8 ; height ; width t +// Ps = 1 9 -> Report the size of the screen in characters. +// Result is CSI 9 ; height ; width t +// Ps = 2 0 -> Report xterm window's icon label. Result is +// OSC L label ST +// Ps = 2 1 -> Report xterm window's title. Result is OSC l +// label ST +// Ps = 2 2 ; 0 -> Save xterm icon and window title on +// stack. +// Ps = 2 2 ; 1 -> Save xterm icon title on stack. +// Ps = 2 2 ; 2 -> Save xterm window title on stack. +// Ps = 2 3 ; 0 -> Restore xterm icon and window title from +// stack. +// Ps = 2 3 ; 1 -> Restore xterm icon title from stack. +// Ps = 2 3 ; 2 -> Restore xterm window title from stack. +// Ps >= 2 4 -> Resize to Ps lines (DECSLPP). +Terminal.prototype.manipulateWindow = function(params) { + ; +}; + +// CSI Pt; Pl; Pb; Pr; Ps$ t +// Reverse Attributes in Rectangular Area (DECRARA), VT400 and +// up. +// Pt; Pl; Pb; Pr denotes the rectangle. +// Ps denotes the attributes to reverse, i.e., 1, 4, 5, 7. +// NOTE: xterm doesn't enable this code by default. +Terminal.prototype.reverseAttrInRectangle = function(params) { + ; +}; + +// CSI > Ps; Ps t +// Set one or more features of the title modes. Each parameter +// enables a single feature. +// Ps = 0 -> Set window/icon labels using hexadecimal. +// Ps = 1 -> Query window/icon labels using hexadecimal. +// Ps = 2 -> Set window/icon labels using UTF-8. +// Ps = 3 -> Query window/icon labels using UTF-8. (See dis- +// cussion of "Title Modes") +Terminal.prototype.setTitleModeFeature = function(params) { + ; +}; + +// CSI Ps SP t +// Set warning-bell volume (DECSWBV, VT520). +// Ps = 0 or 1 -> off. +// Ps = 2 , 3 or 4 -> low. +// Ps = 5 , 6 , 7 , or 8 -> high. +Terminal.prototype.setWarningBellVolume = function(params) { + ; +}; + +// CSI Ps SP u +// Set margin-bell volume (DECSMBV, VT520). +// Ps = 1 -> off. +// Ps = 2 , 3 or 4 -> low. +// Ps = 0 , 5 , 6 , 7 , or 8 -> high. +Terminal.prototype.setMarginBellVolume = function(params) { + ; +}; + +// CSI Pt; Pl; Pb; Pr; Pp; Pt; Pl; Pp$ v +// Copy Rectangular Area (DECCRA, VT400 and up). +// Pt; Pl; Pb; Pr denotes the rectangle. +// Pp denotes the source page. +// Pt; Pl denotes the target location. +// Pp denotes the target page. +// NOTE: xterm doesn't enable this code by default. +Terminal.prototype.copyRectangle = function(params) { + ; +}; + +// CSI Pt ; Pl ; Pb ; Pr ' w +// Enable Filter Rectangle (DECEFR), VT420 and up. +// Parameters are [top;left;bottom;right]. +// Defines the coordinates of a filter rectangle and activates +// it. Anytime the locator is detected outside of the filter +// rectangle, an outside rectangle event is generated and the +// rectangle is disabled. Filter rectangles are always treated +// as "one-shot" events. Any parameters that are omitted default +// to the current locator position. If all parameters are omit- +// ted, any locator motion will be reported. DECELR always can- +// cels any prevous rectangle definition. +Terminal.prototype.enableFilterRectangle = function(params) { + ; +}; + +// CSI Ps x Request Terminal Parameters (DECREQTPARM). +// if Ps is a "0" (default) or "1", and xterm is emulating VT100, +// the control sequence elicits a response of the same form whose +// parameters describe the terminal: +// Ps -> the given Ps incremented by 2. +// Pn = 1 <- no parity. +// Pn = 1 <- eight bits. +// Pn = 1 <- 2 8 transmit 38.4k baud. +// Pn = 1 <- 2 8 receive 38.4k baud. +// Pn = 1 <- clock multiplier. +// Pn = 0 <- STP flags. +Terminal.prototype.requestParameters = function(params) { + ; +}; + +// CSI Ps x Select Attribute Change Extent (DECSACE). +// Ps = 0 -> from start to end position, wrapped. +// Ps = 1 -> from start to end position, wrapped. +// Ps = 2 -> rectangle (exact). +Terminal.prototype.selectChangeExtent = function(params) { + ; +}; + +// CSI Pc; Pt; Pl; Pb; Pr$ x +// Fill Rectangular Area (DECFRA), VT420 and up. +// Pc is the character to use. +// Pt; Pl; Pb; Pr denotes the rectangle. +// NOTE: xterm doesn't enable this code by default. +Terminal.prototype.fillRectangle = function(params) { + var ch = params[0] + , t = params[1] + , l = params[2] + , b = params[3] + , r = params[4]; + + var line + , i; + + for (; t < b + 1; t++) { + line = this.lines[this.ybase + t]; + for (i = l; i < r; i++) { + line[i] = [line[i][0], String.fromCharCode(ch)]; + } + } + + // this.maxRange(); + this.updateRange(params[1]); + this.updateRange(params[3]); +}; + +// CSI Ps ; Pu ' z +// Enable Locator Reporting (DECELR). +// Valid values for the first parameter: +// Ps = 0 -> Locator disabled (default). +// Ps = 1 -> Locator enabled. +// Ps = 2 -> Locator enabled for one report, then disabled. +// The second parameter specifies the coordinate unit for locator +// reports. +// Valid values for the second parameter: +// Pu = 0 <- or omitted -> default to character cells. +// Pu = 1 <- device physical pixels. +// Pu = 2 <- character cells. +Terminal.prototype.enableLocatorReporting = function(params) { + var val = params[0] > 0; + //this.mouseEvents = val; + //this.decLocator = val; +}; + +// CSI Pt; Pl; Pb; Pr$ z +// Erase Rectangular Area (DECERA), VT400 and up. +// Pt; Pl; Pb; Pr denotes the rectangle. +// NOTE: xterm doesn't enable this code by default. +Terminal.prototype.eraseRectangle = function(params) { + var t = params[0] + , l = params[1] + , b = params[2] + , r = params[3]; + + var line + , i + , ch; + + ch = [this.eraseAttr(), ' ']; // xterm? + + for (; t < b + 1; t++) { + line = this.lines[this.ybase + t]; + for (i = l; i < r; i++) { + line[i] = ch; + } + } + + // this.maxRange(); + this.updateRange(params[0]); + this.updateRange(params[2]); +}; + +// CSI Pm ' { +// Select Locator Events (DECSLE). +// Valid values for the first (and any additional parameters) +// are: +// Ps = 0 -> only respond to explicit host requests (DECRQLP). +// (This is default). It also cancels any filter +// rectangle. +// Ps = 1 -> report button down transitions. +// Ps = 2 -> do not report button down transitions. +// Ps = 3 -> report button up transitions. +// Ps = 4 -> do not report button up transitions. +Terminal.prototype.setLocatorEvents = function(params) { + ; +}; + +// CSI Pt; Pl; Pb; Pr$ { +// Selective Erase Rectangular Area (DECSERA), VT400 and up. +// Pt; Pl; Pb; Pr denotes the rectangle. +Terminal.prototype.selectiveEraseRectangle = function(params) { + ; +}; + +// CSI Ps ' | +// Request Locator Position (DECRQLP). +// Valid values for the parameter are: +// Ps = 0 , 1 or omitted -> transmit a single DECLRP locator +// report. + +// If Locator Reporting has been enabled by a DECELR, xterm will +// respond with a DECLRP Locator Report. This report is also +// generated on button up and down events if they have been +// enabled with a DECSLE, or when the locator is detected outside +// of a filter rectangle, if filter rectangles have been enabled +// with a DECEFR. + +// -> CSI Pe ; Pb ; Pr ; Pc ; Pp & w + +// Parameters are [event;button;row;column;page]. +// Valid values for the event: +// Pe = 0 -> locator unavailable - no other parameters sent. +// Pe = 1 -> request - xterm received a DECRQLP. +// Pe = 2 -> left button down. +// Pe = 3 -> left button up. +// Pe = 4 -> middle button down. +// Pe = 5 -> middle button up. +// Pe = 6 -> right button down. +// Pe = 7 -> right button up. +// Pe = 8 -> M4 button down. +// Pe = 9 -> M4 button up. +// Pe = 1 0 -> locator outside filter rectangle. +// ``button'' parameter is a bitmask indicating which buttons are +// pressed: +// Pb = 0 <- no buttons down. +// Pb & 1 <- right button down. +// Pb & 2 <- middle button down. +// Pb & 4 <- left button down. +// Pb & 8 <- M4 button down. +// ``row'' and ``column'' parameters are the coordinates of the +// locator position in the xterm window, encoded as ASCII deci- +// mal. +// The ``page'' parameter is not used by xterm, and will be omit- +// ted. +Terminal.prototype.requestLocatorPosition = function(params) { + ; +}; + +// CSI P m SP } +// Insert P s Column(s) (default = 1) (DECIC), VT420 and up. +// NOTE: xterm doesn't enable this code by default. +Terminal.prototype.insertColumns = function() { + var param = params[0] + , l = this.ybase + this.rows + , ch = [this.eraseAttr(), ' '] // xterm? + , i; + + while (param--) { + for (i = this.ybase; i < l; i++) { + this.lines[i].splice(this.x + 1, 0, ch); + this.lines[i].pop(); + } + } + + this.maxRange(); +}; + +// CSI P m SP ~ +// Delete P s Column(s) (default = 1) (DECDC), VT420 and up +// NOTE: xterm doesn't enable this code by default. +Terminal.prototype.deleteColumns = function() { + var param = params[0] + , l = this.ybase + this.rows + , ch = [this.eraseAttr(), ' '] // xterm? + , i; + + while (param--) { + for (i = this.ybase; i < l; i++) { + this.lines[i].splice(this.x, 1); + this.lines[i].push(ch); + } + } + + this.maxRange(); +}; + +/** + * Prefix/Select/Visual/Search Modes + */ + +Terminal.prototype.enterPrefix = function() { + this.prefixMode = true; +}; + +Terminal.prototype.leavePrefix = function() { + this.prefixMode = false; +}; + +Terminal.prototype.enterSelect = function() { + this._real = { + x: this.x, + y: this.y, + ydisp: this.ydisp, + ybase: this.ybase, + cursorHidden: this.cursorHidden, + lines: this.copyBuffer(this.lines), + write: this.write + }; + this.write = function() {}; + this.selectMode = true; + this.visualMode = false; + this.cursorHidden = false; + this.refresh(this.y, this.y); +}; + +Terminal.prototype.leaveSelect = function() { + this.x = this._real.x; + this.y = this._real.y; + this.ydisp = this._real.ydisp; + this.ybase = this._real.ybase; + this.cursorHidden = this._real.cursorHidden; + this.lines = this._real.lines; + this.write = this._real.write; + delete this._real; + this.selectMode = false; + this.visualMode = false; + this.refresh(0, this.rows - 1); +}; + +Terminal.prototype.enterVisual = function() { + this._real.preVisual = this.copyBuffer(this.lines); + this.selectText(this.x, this.x, this.ydisp + this.y, this.ydisp + this.y); + this.visualMode = true; +}; + +Terminal.prototype.leaveVisual = function() { + this.lines = this._real.preVisual; + delete this._real.preVisual; + delete this._selected; + this.visualMode = false; + this.refresh(0, this.rows - 1); +}; + +Terminal.prototype.enterSearch = function(down) { + this.entry = ''; + this.searchMode = true; + this.searchDown = down; + this._real.preSearch = this.copyBuffer(this.lines); + this._real.preSearchX = this.x; + this._real.preSearchY = this.y; + + var bottom = this.ydisp + this.rows - 1; + for (var i = 0; i < this.entryPrefix.length; i++) { + //this.lines[bottom][i][0] = (this.defAttr & ~0x1ff) | 4; + //this.lines[bottom][i][1] = this.entryPrefix[i]; + this.lines[bottom][i] = [ + (this.defAttr & ~0x1ff) | 4, + this.entryPrefix[i] + ]; + } + + this.y = this.rows - 1; + this.x = this.entryPrefix.length; + + this.refresh(this.rows - 1, this.rows - 1); +}; + +Terminal.prototype.leaveSearch = function() { + this.searchMode = false; + + if (this._real.preSearch) { + this.lines = this._real.preSearch; + this.x = this._real.preSearchX; + this.y = this._real.preSearchY; + delete this._real.preSearch; + delete this._real.preSearchX; + delete this._real.preSearchY; + } + + this.refresh(this.rows - 1, this.rows - 1); +}; + +Terminal.prototype.copyBuffer = function(lines) { + var lines = lines || this.lines + , out = []; + + for (var y = 0; y < lines.length; y++) { + out[y] = []; + for (var x = 0; x < lines[y].length; x++) { + out[y][x] = [lines[y][x][0], lines[y][x][1]]; + } + } + + return out; +}; + +Terminal.prototype.getCopyTextarea = function(text) { + var textarea = this._copyTextarea + , document = this.document; + + if (!textarea) { + textarea = document.createElement('textarea'); + textarea.style.position = 'absolute'; + textarea.style.left = '-32000px'; + textarea.style.top = '-32000px'; + textarea.style.width = '0px'; + textarea.style.height = '0px'; + textarea.style.opacity = '0'; + textarea.style.backgroundColor = 'transparent'; + textarea.style.borderStyle = 'none'; + textarea.style.outlineStyle = 'none'; + + document.getElementsByTagName('body')[0].appendChild(textarea); + + this._copyTextarea = textarea; + } + + return textarea; +}; + +// NOTE: Only works for primary selection on X11. +// Non-X11 users should use Ctrl-C instead. +Terminal.prototype.copyText = function(text) { + var self = this + , textarea = this.getCopyTextarea(); + + this.emit('copy', text); + + textarea.focus(); + textarea.textContent = text; + textarea.value = text; + textarea.setSelectionRange(0, text.length); + + setTimeout(function() { + self.element.focus(); + self.focus(); + }, 1); +}; + +Terminal.prototype.selectText = function(x1, x2, y1, y2) { + var ox1 + , ox2 + , oy1 + , oy2 + , tmp + , x + , y + , xl + , attr; + + if (this._selected) { + ox1 = this._selected.x1; + ox2 = this._selected.x2; + oy1 = this._selected.y1; + oy2 = this._selected.y2; + + if (oy2 < oy1) { + tmp = ox2; + ox2 = ox1; + ox1 = tmp; + tmp = oy2; + oy2 = oy1; + oy1 = tmp; + } + + if (ox2 < ox1 && oy1 === oy2) { + tmp = ox2; + ox2 = ox1; + ox1 = tmp; + } + + for (y = oy1; y <= oy2; y++) { + x = 0; + xl = this.cols - 1; + if (y === oy1) { + x = ox1; + } + if (y === oy2) { + xl = ox2; + } + for (; x <= xl; x++) { + if (this.lines[y][x].old != null) { + //this.lines[y][x][0] = this.lines[y][x].old; + //delete this.lines[y][x].old; + attr = this.lines[y][x].old; + delete this.lines[y][x].old; + this.lines[y][x] = [attr, this.lines[y][x][1]]; + } + } + } + + y1 = this._selected.y1; + x1 = this._selected.x1; + } + + y1 = Math.max(y1, 0); + y1 = Math.min(y1, this.ydisp + this.rows - 1); + + y2 = Math.max(y2, 0); + y2 = Math.min(y2, this.ydisp + this.rows - 1); + + this._selected = { x1: x1, x2: x2, y1: y1, y2: y2 }; + + if (y2 < y1) { + tmp = x2; + x2 = x1; + x1 = tmp; + tmp = y2; + y2 = y1; + y1 = tmp; + } + + if (x2 < x1 && y1 === y2) { + tmp = x2; + x2 = x1; + x1 = tmp; + } + + for (y = y1; y <= y2; y++) { + x = 0; + xl = this.cols - 1; + if (y === y1) { + x = x1; + } + if (y === y2) { + xl = x2; + } + for (; x <= xl; x++) { + //this.lines[y][x].old = this.lines[y][x][0]; + //this.lines[y][x][0] &= ~0x1ff; + //this.lines[y][x][0] |= (0x1ff << 9) | 4; + attr = this.lines[y][x][0]; + this.lines[y][x] = [ + (attr & ~0x1ff) | ((0x1ff << 9) | 4), + this.lines[y][x][1] + ]; + this.lines[y][x].old = attr; + } + } + + y1 = y1 - this.ydisp; + y2 = y2 - this.ydisp; + + y1 = Math.max(y1, 0); + y1 = Math.min(y1, this.rows - 1); + + y2 = Math.max(y2, 0); + y2 = Math.min(y2, this.rows - 1); + + //this.refresh(y1, y2); + this.refresh(0, this.rows - 1); +}; + +Terminal.prototype.grabText = function(x1, x2, y1, y2) { + var out = '' + , buf = '' + , ch + , x + , y + , xl + , tmp; + + if (y2 < y1) { + tmp = x2; + x2 = x1; + x1 = tmp; + tmp = y2; + y2 = y1; + y1 = tmp; + } + + if (x2 < x1 && y1 === y2) { + tmp = x2; + x2 = x1; + x1 = tmp; + } + + for (y = y1; y <= y2; y++) { + x = 0; + xl = this.cols - 1; + if (y === y1) { + x = x1; + } + if (y === y2) { + xl = x2; + } + for (; x <= xl; x++) { + ch = this.lines[y][x][1]; + if (ch === ' ') { + buf += ch; + continue; + } + if (buf) { + out += buf; + buf = ''; + } + out += ch; + if (isWide(ch)) x++; + } + buf = ''; + out += '\n'; + } + + // If we're not at the end of the + // line, don't add a newline. + for (x = x2, y = y2; x < this.cols; x++) { + if (this.lines[y][x][1] !== ' ') { + out = out.slice(0, -1); + break; + } + } + + return out; +}; + +Terminal.prototype.keyPrefix = function(ev, key) { + if (key === 'k' || key === '&') { + this.destroy(); + } else if (key === 'p' || key === ']') { + this.emit('request paste'); + } else if (key === 'c') { + this.emit('request create'); + } else if (key >= '0' && key <= '9') { + key = +key - 1; + if (!~key) key = 9; + this.emit('request term', key); + } else if (key === 'n') { + this.emit('request term next'); + } else if (key === 'P') { + this.emit('request term previous'); + } else if (key === ':') { + this.emit('request command mode'); + } else if (key === '[') { + this.enterSelect(); + } +}; + +Terminal.prototype.keySelect = function(ev, key) { + this.showCursor(); + + if (this.searchMode || key === 'n' || key === 'N') { + return this.keySearch(ev, key); + } + + if (key === '\x04') { // ctrl-d + var y = this.ydisp + this.y; + if (this.ydisp === this.ybase) { + // Mimic vim behavior + this.y = Math.min(this.y + (this.rows - 1) / 2 | 0, this.rows - 1); + this.refresh(0, this.rows - 1); + } else { + this.scrollDisp((this.rows - 1) / 2 | 0); + } + if (this.visualMode) { + this.selectText(this.x, this.x, y, this.ydisp + this.y); + } + return; + } + + if (key === '\x15') { // ctrl-u + var y = this.ydisp + this.y; + if (this.ydisp === 0) { + // Mimic vim behavior + this.y = Math.max(this.y - (this.rows - 1) / 2 | 0, 0); + this.refresh(0, this.rows - 1); + } else { + this.scrollDisp(-(this.rows - 1) / 2 | 0); + } + if (this.visualMode) { + this.selectText(this.x, this.x, y, this.ydisp + this.y); + } + return; + } + + if (key === '\x06') { // ctrl-f + var y = this.ydisp + this.y; + this.scrollDisp(this.rows - 1); + if (this.visualMode) { + this.selectText(this.x, this.x, y, this.ydisp + this.y); + } + return; + } + + if (key === '\x02') { // ctrl-b + var y = this.ydisp + this.y; + this.scrollDisp(-(this.rows - 1)); + if (this.visualMode) { + this.selectText(this.x, this.x, y, this.ydisp + this.y); + } + return; + } + + if (key === 'k' || key === '\x1b[A') { + var y = this.ydisp + this.y; + this.y--; + if (this.y < 0) { + this.y = 0; + this.scrollDisp(-1); + } + if (this.visualMode) { + this.selectText(this.x, this.x, y, this.ydisp + this.y); + } else { + this.refresh(this.y, this.y + 1); + } + return; + } + + if (key === 'j' || key === '\x1b[B') { + var y = this.ydisp + this.y; + this.y++; + if (this.y >= this.rows) { + this.y = this.rows - 1; + this.scrollDisp(1); + } + if (this.visualMode) { + this.selectText(this.x, this.x, y, this.ydisp + this.y); + } else { + this.refresh(this.y - 1, this.y); + } + return; + } + + if (key === 'h' || key === '\x1b[D') { + var x = this.x; + this.x--; + if (this.x < 0) { + this.x = 0; + } + if (this.visualMode) { + this.selectText(x, this.x, this.ydisp + this.y, this.ydisp + this.y); + } else { + this.refresh(this.y, this.y); + } + return; + } + + if (key === 'l' || key === '\x1b[C') { + var x = this.x; + this.x++; + if (this.x >= this.cols) { + this.x = this.cols - 1; + } + if (this.visualMode) { + this.selectText(x, this.x, this.ydisp + this.y, this.ydisp + this.y); + } else { + this.refresh(this.y, this.y); + } + return; + } + + if (key === 'v' || key === ' ') { + if (!this.visualMode) { + this.enterVisual(); + } else { + this.leaveVisual(); + } + return; + } + + if (key === 'y') { + if (this.visualMode) { + var text = this.grabText( + this._selected.x1, this._selected.x2, + this._selected.y1, this._selected.y2); + this.copyText(text); + this.leaveVisual(); + // this.leaveSelect(); + } + return; + } + + if (key === 'q' || key === '\x1b') { + if (this.visualMode) { + this.leaveVisual(); + } else { + this.leaveSelect(); + } + return; + } + + if (key === 'w' || key === 'W') { + var ox = this.x; + var oy = this.y; + var oyd = this.ydisp; + + var x = this.x; + var y = this.y; + var yb = this.ydisp; + var saw_space = false; + + for (;;) { + var line = this.lines[yb + y]; + while (x < this.cols) { + if (line[x][1] <= ' ') { + saw_space = true; + } else if (saw_space) { + break; + } + x++; + } + if (x >= this.cols) x = this.cols - 1; + if (x === this.cols - 1 && line[x][1] <= ' ') { + x = 0; + if (++y >= this.rows) { + y--; + if (++yb > this.ybase) { + yb = this.ybase; + x = this.x; + break; + } + } + continue; + } + break; + } + + this.x = x, this.y = y; + this.scrollDisp(-this.ydisp + yb); + + if (this.visualMode) { + this.selectText(ox, this.x, oy + oyd, this.ydisp + this.y); + } + return; + } + + if (key === 'b' || key === 'B') { + var ox = this.x; + var oy = this.y; + var oyd = this.ydisp; + + var x = this.x; + var y = this.y; + var yb = this.ydisp; + + for (;;) { + var line = this.lines[yb + y]; + var saw_space = x > 0 && line[x][1] > ' ' && line[x - 1][1] > ' '; + while (x >= 0) { + if (line[x][1] <= ' ') { + if (saw_space && (x + 1 < this.cols && line[x + 1][1] > ' ')) { + x++; + break; + } else { + saw_space = true; + } + } + x--; + } + if (x < 0) x = 0; + if (x === 0 && (line[x][1] <= ' ' || !saw_space)) { + x = this.cols - 1; + if (--y < 0) { + y++; + if (--yb < 0) { + yb++; + x = 0; + break; + } + } + continue; + } + break; + } + + this.x = x, this.y = y; + this.scrollDisp(-this.ydisp + yb); + + if (this.visualMode) { + this.selectText(ox, this.x, oy + oyd, this.ydisp + this.y); + } + return; + } + + if (key === 'e' || key === 'E') { + var x = this.x + 1; + var y = this.y; + var yb = this.ydisp; + if (x >= this.cols) x--; + + for (;;) { + var line = this.lines[yb + y]; + while (x < this.cols) { + if (line[x][1] <= ' ') { + x++; + } else { + break; + } + } + while (x < this.cols) { + if (line[x][1] <= ' ') { + if (x - 1 >= 0 && line[x - 1][1] > ' ') { + x--; + break; + } + } + x++; + } + if (x >= this.cols) x = this.cols - 1; + if (x === this.cols - 1 && line[x][1] <= ' ') { + x = 0; + if (++y >= this.rows) { + y--; + if (++yb > this.ybase) { + yb = this.ybase; + break; + } + } + continue; + } + break; + } + + this.x = x, this.y = y; + this.scrollDisp(-this.ydisp + yb); + + if (this.visualMode) { + this.selectText(ox, this.x, oy + oyd, this.ydisp + this.y); + } + return; + } + + if (key === '^' || key === '0') { + var ox = this.x; + + if (key === '0') { + this.x = 0; + } else if (key === '^') { + var line = this.lines[this.ydisp + this.y]; + var x = 0; + while (x < this.cols) { + if (line[x][1] > ' ') { + break; + } + x++; + } + if (x >= this.cols) x = this.cols - 1; + this.x = x; + } + + if (this.visualMode) { + this.selectText(ox, this.x, this.ydisp + this.y, this.ydisp + this.y); + } else { + this.refresh(this.y, this.y); + } + return; + } + + if (key === '$') { + var ox = this.x; + var line = this.lines[this.ydisp + this.y]; + var x = this.cols - 1; + while (x >= 0) { + if (line[x][1] > ' ') { + if (this.visualMode && x < this.cols - 1) x++; + break; + } + x--; + } + if (x < 0) x = 0; + this.x = x; + if (this.visualMode) { + this.selectText(ox, this.x, this.ydisp + this.y, this.ydisp + this.y); + } else { + this.refresh(this.y, this.y); + } + return; + } + + if (key === 'g' || key === 'G') { + var ox = this.x; + var oy = this.y; + var oyd = this.ydisp; + if (key === 'g') { + this.x = 0, this.y = 0; + this.scrollDisp(-this.ydisp); + } else if (key === 'G') { + this.x = 0, this.y = this.rows - 1; + this.scrollDisp(this.ybase); + } + if (this.visualMode) { + this.selectText(ox, this.x, oy + oyd, this.ydisp + this.y); + } + return; + } + + if (key === 'H' || key === 'M' || key === 'L') { + var ox = this.x; + var oy = this.y; + if (key === 'H') { + this.x = 0, this.y = 0; + } else if (key === 'M') { + this.x = 0, this.y = this.rows / 2 | 0; + } else if (key === 'L') { + this.x = 0, this.y = this.rows - 1; + } + if (this.visualMode) { + this.selectText(ox, this.x, this.ydisp + oy, this.ydisp + this.y); + } else { + this.refresh(oy, oy); + this.refresh(this.y, this.y); + } + return; + } + + if (key === '{' || key === '}') { + var ox = this.x; + var oy = this.y; + var oyd = this.ydisp; + + var line; + var saw_full = false; + var found = false; + var first_is_space = -1; + var y = this.y + (key === '{' ? -1 : 1); + var yb = this.ydisp; + var i; + + if (key === '{') { + if (y < 0) { + y++; + if (yb > 0) yb--; + } + } else if (key === '}') { + if (y >= this.rows) { + y--; + if (yb < this.ybase) yb++; + } + } + + for (;;) { + line = this.lines[yb + y]; + + for (i = 0; i < this.cols; i++) { + if (line[i][1] > ' ') { + if (first_is_space === -1) { + first_is_space = 0; + } + saw_full = true; + break; + } else if (i === this.cols - 1) { + if (first_is_space === -1) { + first_is_space = 1; + } else if (first_is_space === 0) { + found = true; + } else if (first_is_space === 1) { + if (saw_full) found = true; + } + break; + } + } + + if (found) break; + + if (key === '{') { + y--; + if (y < 0) { + y++; + if (yb > 0) yb--; + else break; + } + } else if (key === '}') { + y++; + if (y >= this.rows) { + y--; + if (yb < this.ybase) yb++; + else break; + } + } + } + + if (!found) { + if (key === '{') { + y = 0; + yb = 0; + } else if (key === '}') { + y = this.rows - 1; + yb = this.ybase; + } + } + + this.x = 0, this.y = y; + this.scrollDisp(-this.ydisp + yb); + + if (this.visualMode) { + this.selectText(ox, this.x, oy + oyd, this.ydisp + this.y); + } + return; + } + + if (key === '/' || key === '?') { + if (!this.visualMode) { + this.enterSearch(key === '/'); + } + return; + } + + return false; +}; + +Terminal.prototype.keySearch = function(ev, key) { + if (key === '\x1b') { + this.leaveSearch(); + return; + } + + if (key === '\r' || (!this.searchMode && (key === 'n' || key === 'N'))) { + this.leaveSearch(); + + var entry = this.entry; + + if (!entry) { + this.refresh(0, this.rows - 1); + return; + } + + var ox = this.x; + var oy = this.y; + var oyd = this.ydisp; + + var line; + var found = false; + var wrapped = false; + var x = this.x + 1; + var y = this.ydisp + this.y; + var yb, i; + var up = key === 'N' + ? this.searchDown + : !this.searchDown; + + for (;;) { + line = this.lines[y]; + + while (x < this.cols) { + for (i = 0; i < entry.length; i++) { + if (x + i >= this.cols) break; + if (line[x + i][1] !== entry[i]) { + break; + } else if (line[x + i][1] === entry[i] && i === entry.length - 1) { + found = true; + break; + } + } + if (found) break; + x += i + 1; + } + if (found) break; + + x = 0; + + if (!up) { + y++; + if (y > this.ybase + this.rows - 1) { + if (wrapped) break; + // this.setMessage('Search wrapped. Continuing at TOP.'); + wrapped = true; + y = 0; + } + } else { + y--; + if (y < 0) { + if (wrapped) break; + // this.setMessage('Search wrapped. Continuing at BOTTOM.'); + wrapped = true; + y = this.ybase + this.rows - 1; + } + } + } + + if (found) { + if (y - this.ybase < 0) { + yb = y; + y = 0; + if (yb > this.ybase) { + y = yb - this.ybase; + yb = this.ybase; + } + } else { + yb = this.ybase; + y -= this.ybase; + } + + this.x = x, this.y = y; + this.scrollDisp(-this.ydisp + yb); + + if (this.visualMode) { + this.selectText(ox, this.x, oy + oyd, this.ydisp + this.y); + } + return; + } + + // this.setMessage("No matches found."); + this.refresh(0, this.rows - 1); + + return; + } + + if (key === '\b' || key === '\x7f') { + if (this.entry.length === 0) return; + var bottom = this.ydisp + this.rows - 1; + this.entry = this.entry.slice(0, -1); + var i = this.entryPrefix.length + this.entry.length; + //this.lines[bottom][i][1] = ' '; + this.lines[bottom][i] = [ + this.lines[bottom][i][0], + ' ' + ]; + this.x--; + this.refresh(this.rows - 1, this.rows - 1); + this.refresh(this.y, this.y); + return; + } + + if (key.length === 1 && key >= ' ' && key <= '~') { + var bottom = this.ydisp + this.rows - 1; + this.entry += key; + var i = this.entryPrefix.length + this.entry.length - 1; + //this.lines[bottom][i][0] = (this.defAttr & ~0x1ff) | 4; + //this.lines[bottom][i][1] = key; + this.lines[bottom][i] = [ + (this.defAttr & ~0x1ff) | 4, + key + ]; + this.x++; + this.refresh(this.rows - 1, this.rows - 1); + this.refresh(this.y, this.y); + return; + } + + return false; +}; + +/** + * Character Sets + */ + +Terminal.charsets = {}; + +// DEC Special Character and Line Drawing Set. +// http://vt100.net/docs/vt102-ug/table5-13.html +// A lot of curses apps use this if they see TERM=xterm. +// testing: echo -e '\e(0a\e(B' +// The xterm output sometimes seems to conflict with the +// reference above. xterm seems in line with the reference +// when running vttest however. +// The table below now uses xterm's output from vttest. +Terminal.charsets.SCLD = { // (0 + '`': '\u25c6', // '◆' + 'a': '\u2592', // '▒' + 'b': '\u0009', // '\t' + 'c': '\u000c', // '\f' + 'd': '\u000d', // '\r' + 'e': '\u000a', // '\n' + 'f': '\u00b0', // '°' + 'g': '\u00b1', // '±' + 'h': '\u2424', // '\u2424' (NL) + 'i': '\u000b', // '\v' + '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' // '·' +}; + +Terminal.charsets.UK = null; // (A +Terminal.charsets.US = null; // (B (USASCII) +Terminal.charsets.Dutch = null; // (4 +Terminal.charsets.Finnish = null; // (C or (5 +Terminal.charsets.French = null; // (R +Terminal.charsets.FrenchCanadian = null; // (Q +Terminal.charsets.German = null; // (K +Terminal.charsets.Italian = null; // (Y +Terminal.charsets.NorwegianDanish = null; // (E or (6 +Terminal.charsets.Spanish = null; // (Z +Terminal.charsets.Swedish = null; // (H or (7 +Terminal.charsets.Swiss = null; // (= +Terminal.charsets.ISOLatin = null; // /A + +/** + * Helpers + */ + +function on(el, type, handler, capture) { + el.addEventListener(type, handler, capture || false); +} + +function off(el, type, handler, capture) { + el.removeEventListener(type, handler, capture || false); +} + +function cancel(ev) { + if (ev.preventDefault) ev.preventDefault(); + ev.returnValue = false; + if (ev.stopPropagation) ev.stopPropagation(); + ev.cancelBubble = true; + return false; +} + +function inherits(child, parent) { + function f() { + this.constructor = child; + } + f.prototype = parent.prototype; + child.prototype = new f; +} + +// if bold is broken, we can't +// use it in the terminal. +function isBoldBroken(document) { + var body = document.getElementsByTagName('body')[0]; + var terminal = document.createElement('div'); + terminal.className = 'terminal'; + var line = document.createElement('div'); + var el = document.createElement('span'); + el.innerHTML = 'hello world'; + line.appendChild(el); + terminal.appendChild(line); + body.appendChild(terminal); + var w1 = el.scrollWidth; + el.style.fontWeight = 'bold'; + var w2 = el.scrollWidth; + body.removeChild(terminal); + return w1 !== w2; +} + +var String = this.String; +var setTimeout = this.setTimeout; +var setInterval = this.setInterval; + +function indexOf(obj, el) { + var i = obj.length; + while (i--) { + if (obj[i] === el) return i; + } + return -1; +} + +function isWide(ch) { + if (ch <= '\uff00') return false; + return (ch >= '\uff01' && ch <= '\uffbe') + || (ch >= '\uffc2' && ch <= '\uffc7') + || (ch >= '\uffca' && ch <= '\uffcf') + || (ch >= '\uffd2' && ch <= '\uffd7') + || (ch >= '\uffda' && ch <= '\uffdc') + || (ch >= '\uffe0' && ch <= '\uffe6') + || (ch >= '\uffe8' && ch <= '\uffee'); +} + +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 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.EventEmitter = EventEmitter; +Terminal.Stream = Stream; +Terminal.inherits = inherits; +Terminal.on = on; +Terminal.off = off; +Terminal.cancel = cancel; + +if (typeof module !== 'undefined') { + module.exports = Terminal; +} else { + this.Terminal = Terminal; +} + +}).call(function() { + return this || (typeof window !== 'undefined' ? window : global); +}()); |