/**
* xterm.js: xterm, in the browser
- * Copyright (c) 2014, SourceLair Limited <www.sourcelair.com> (MIT License)
+ * Copyright (c) 2014-2014, SourceLair Private Company <www.sourcelair.com> (MIT License)
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
* https://github.com/chjj/term.js
*
import { CompositionHelper } from './CompositionHelper.js';
import { EventEmitter } from './EventEmitter.js';
import { Viewport } from './Viewport.js';
+import { rightClickHandler, pasteHandler, copyHandler } from './handlers/Clipboard.js';
/**
* Terminal Emulation References:
* Creates a new `Terminal` object.
*
* @param {object} options An object containing a set of options, the available options are:
- * - cursorBlink (boolean): Whether the terminal cursor blinks
+ * - `cursorBlink` (boolean): Whether the terminal cursor blinks
+ * - `cols` (number): The number of columns of the terminal (horizontal size)
+ * - `rows` (number): The number of rows of the terminal (vertical size)
*
* @public
* @class Xterm Xterm
this.cols = options.cols || options.geometry[0];
this.rows = options.rows || options.geometry[1];
+ this.geometry = [this.cols, this.rows];
if (options.handler) {
this.on('data', options.handler);
return this.textarea.focus();
};
+/**
+ * Retrieves an option's value from the terminal.
+ * @param {string} key The option key.
+ */
+Terminal.prototype.getOption = function(key, value) {
+ if (!(key in Terminal.defaults)) {
+ throw new Error('No option with key "' + key + '"');
+ }
+
+ if (typeof this.options[key] !== 'undefined') {
+ return this.options[key];
+ }
+
+ return this[key];
+};
+
/**
* Sets an option on the terminal.
* @param {string} key The option key.
* Initialize default behavior
*/
Terminal.prototype.initGlobal = function() {
- Terminal.bindPaste(this);
+ var term = this;
+
Terminal.bindKeys(this);
- Terminal.bindCopy(this);
Terminal.bindFocus(this);
Terminal.bindBlur(this);
-};
-/**
- * Bind to paste event and allow both keyboard and right-click pasting, without having the
- * contentEditable value set to true.
- */
-Terminal.bindPaste = function(term) {
- on([term.textarea, term.element], 'paste', function(ev) {
- ev.stopPropagation();
- if (ev.clipboardData) {
- var text = ev.clipboardData.getData('text/plain');
- term.handler(text);
- term.textarea.value = '';
- return term.cancel(ev);
- }
+ // Bind clipboard functionality
+ on(this.element, 'copy', copyHandler);
+ on(this.textarea, 'paste', function (ev) {
+ pasteHandler.call(this, ev, term);
+ });
+ on(this.element, 'contextmenu', function (ev) {
+ rightClickHandler.call(this, ev, term);
});
-};
-
-/**
- * Prepares text copied from terminal selection, to be saved in the clipboard by:
- * 1. stripping all trailing white spaces
- * 2. converting all non-breaking spaces to regular spaces
- * @param {string} text The copied text that needs processing for storing in clipboard
- * @returns {string}
- * @static
- */
-Terminal.prepareCopiedTextForClipboard = function (text) {
- var space = String.fromCharCode(32),
- nonBreakingSpace = String.fromCharCode(160),
- allNonBreakingSpaces = new RegExp(nonBreakingSpace, 'g'),
- processedText = text.split('\n').map(function (line) {
- /**
- * Strip all trailing white spaces and convert all non-breaking spaces to regular
- * spaces.
- */
- var processedLine = line.replace(/\s+$/g, '').replace(allNonBreakingSpaces, space);
-
- return processedLine;
- }).join('\n');
-
- return processedText;
};
/**
term.on('refresh', term.compositionHelper.updateCompositionElements.bind(term.compositionHelper));
};
-/**
- * Binds copy functionality to the given terminal.
- * @static
- */
-Terminal.bindCopy = function(term) {
- on(term.element, 'copy', function(ev) {
- return; // temporary
- });
-};
-
/**
* Insert the given row to the terminal or produce a new one
Terminal.loadAddon = function(addon, callback) {
if (typeof exports === 'object' && typeof module === 'object') {
// CommonJS
- return require(__dirname + '/../addons/' + addon);
+ return require('./addons/' + addon + '/' + addon);
} else if (typeof define == 'function') {
// RequireJS
- return require(['../addons/' + addon + '/' + addon], callback);
+ return require(['./addons/' + addon + '/' + addon], callback);
} else {
console.error('Cannot load a module without a CommonJS or RequireJS environment.');
return false;
// the shell for example
on(el, 'wheel', function(ev) {
if (self.mouseEvents) return;
- if (self.applicationKeypad) return;
self.viewport.onWheel(ev);
return self.cancel(ev);
});
this.refresh(0, this.rows - 1);
};
+/**
+ * Scroll the display of the terminal by a number of pages.
+ * @param {number} pageCount The number of pages to scroll (negative scrolls up).
+ */
+Terminal.prototype.scrollPages = function(pageCount) {
+ this.scrollDisp(pageCount * (this.rows - 1));
+}
+
+/**
+ * Scrolls the display of the terminal to the top.
+ */
+Terminal.prototype.scrollToTop = function() {
+ this.scrollDisp(-this.ydisp);
+}
+
+/**
+ * Scrolls the display of the terminal to the bottom.
+ */
+Terminal.prototype.scrollToBottom = function() {
+ this.scrollDisp(this.ybase - this.ydisp);
+}
+
/**
* Writes text to the terminal.
* @param {string} text The text to write to the terminal.
case '=':
this.log('Serial port requested application keypad.');
this.applicationKeypad = true;
- this.viewport.setApplicationMode(true);
+ this.viewport.syncScrollArea();
this.state = normal;
break;
case '>':
this.log('Switching back to normal keypad.');
this.applicationKeypad = false;
- this.viewport.setApplicationMode(false);
+ this.viewport.syncScrollArea();
this.state = normal;
break;
if (result.scrollDisp) {
this.scrollDisp(result.scrollDisp);
- return this.cancel(ev);
+ return this.cancel(ev, true);
}
if (isThirdLevelShift(this, ev)) {
return true;
}
- if (result.cancel ) {
+ if (result.cancel) {
// The event is canceled at the end already, is this necessary?
this.cancel(ev, true);
}
};
var modifiers = ev.shiftKey << 0 | ev.altKey << 1 | ev.ctrlKey << 2 | ev.metaKey << 3;
switch (ev.keyCode) {
- // backspace
case 8:
+ // backspace
if (ev.shiftKey) {
result.key = '\x08'; // ^H
break;
}
result.key = '\x7f'; // ^?
break;
- // tab
case 9:
+ // tab
if (ev.shiftKey) {
result.key = '\x1b[Z';
break;
result.key = '\t';
result.cancel = true;
break;
- // return/enter
case 13:
+ // return/enter
result.key = '\r';
result.cancel = true;
break;
- // escape
case 27:
+ // escape
result.key = '\x1b';
result.cancel = true;
break;
- // left-arrow
case 37:
+ // left-arrow
if (modifiers) {
result.key = '\x1b[1;' + (modifiers + 1) + 'D';
// HACK: Make Alt + left-arrow behave like Ctrl + left-arrow: move one word backwards
result.key = '\x1b[D';
}
break;
- // right-arrow
case 39:
+ // right-arrow
if (modifiers) {
result.key = '\x1b[1;' + (modifiers + 1) + 'C';
// HACK: Make Alt + right-arrow behave like Ctrl + right-arrow: move one word forward
result.key = '\x1b[C';
}
break;
- // up-arrow
case 38:
+ // up-arrow
if (modifiers) {
result.key = '\x1b[1;' + (modifiers + 1) + 'A';
// HACK: Make Alt + up-arrow behave like Ctrl + up-arrow
result.key = '\x1b[A';
}
break;
- // down-arrow
case 40:
+ // down-arrow
if (modifiers) {
result.key = '\x1b[1;' + (modifiers + 1) + 'B';
// HACK: Make Alt + down-arrow behave like Ctrl + down-arrow
result.key = '\x1b[B';
}
break;
- // insert
case 45:
+ // insert
if (!ev.shiftKey && !ev.ctrlKey) {
// <Ctrl> or <Shift> + <Insert> are used to
// copy-paste on some systems.
result.key = '\x1b[2~';
}
break;
- // delete
case 46:
+ // delete
if (modifiers) {
result.key = '\x1b[3;' + (modifiers + 1) + '~';
} else {
result.key = '\x1b[3~';
}
break;
- // home
case 36:
+ // home
if (modifiers)
result.key = '\x1b[1;' + (modifiers + 1) + 'H';
else if (this.applicationCursor)
else
result.key = '\x1b[H';
break;
- // end
case 35:
+ // end
if (modifiers)
result.key = '\x1b[1;' + (modifiers + 1) + 'F';
else if (this.applicationCursor)
else
result.key = '\x1b[F';
break;
- // page up
case 33:
+ // page up
if (ev.shiftKey) {
result.scrollDisp = -(this.rows - 1);
} else {
result.key = '\x1b[5~';
}
break;
- // page down
case 34:
+ // page down
if (ev.shiftKey) {
result.scrollDisp = this.rows - 1;
} else {
result.key = '\x1b[6~';
}
break;
- // F1-F12
case 112:
+ // F1-F12
if (modifiers) {
result.key = '\x1b[1;' + (modifiers + 1) + 'P';
} else {
this.normal = null;
+ this.geometry = [this.cols, this.rows];
this.emit('resize', {terminal: this, cols: x, rows: y});
};
* Clears the entire buffer, making the prompt line the new first line.
*/
Terminal.prototype.clear = function() {
+ if (this.ybase === 0 && this.y === 0) {
+ // Don't clear if it's already clear
+ return;
+ }
+ this.lines = [this.lines[this.ybase + this.y]];
this.ydisp = 0;
this.ybase = 0;
this.y = 0;
- this.lines = [this.lines[this.lines.length - 1]];
for (var i = 1; i < this.rows; i++) {
this.lines.push(this.blankLine());
}
/**
- * Emit the 'data' event and populate the given data.
- * @param {string} data The data to populate in the event.
- */
+ * Emit the 'data' event and populate the given data.
+ * @param {string} data The data to populate in the event.
+ */
Terminal.prototype.handler = function(data) {
this.emit('data', data);
};
Terminal.call(this, this.options);
this.customKeydownHandler = customKeydownHandler;
this.refresh(0, this.rows - 1);
+ this.viewport.syncScrollArea();
};
case 66:
this.log('Serial port requested application keypad.');
this.applicationKeypad = true;
- this.viewport.setApplicationMode(true);
+ this.viewport.syncScrollArea();
break;
case 9: // X10 Mouse
// no release, no motion, no wheel, no modifiers.
case 66:
this.log('Switching back to normal keypad.');
this.applicationKeypad = false;
- this.viewport.setApplicationMode(false);
+ this.viewport.syncScrollArea();
break;
case 9: // X10 Mouse
case 1000: // vt200 mouse
/**
- * CSI Ps T Scroll down Ps lines (default = 1) (SD).
- */
+ * CSI Ps T Scroll down Ps lines (default = 1) (SD).
+ */
Terminal.prototype.scrollDown = function(params) {
var param = params[0] || 1;
while (param--) {
this.originMode = false;
this.wraparoundMode = false; // autowrap
this.applicationKeypad = false; // ?
- this.viewport.setApplicationMode(false);
+ this.viewport.syncScrollArea();
this.applicationCursor = false;
this.scrollTop = 0;
this.scrollBottom = this.rows - 1;