2016-09-21 06:27:11 -08:00
|
|
|
|
import {hterm, lib} from 'hterm-umdjs';
|
2016-10-03 07:29:33 -08:00
|
|
|
|
import fromCharCode from './utils/key-code';
|
2016-09-21 06:27:11 -08:00
|
|
|
|
|
2016-08-17 17:45:18 -08:00
|
|
|
|
const selection = require('./utils/selection');
|
2016-07-08 10:48:24 -08:00
|
|
|
|
|
|
|
|
|
|
hterm.defaultStorage = new lib.Storage.Memory();
|
|
|
|
|
|
|
2016-08-18 19:09:40 -08:00
|
|
|
|
// Provide selectAll to terminal viewport
|
|
|
|
|
|
hterm.Terminal.prototype.selectAll = function () {
|
|
|
|
|
|
// We need to clear dom range to reset anchorNode
|
|
|
|
|
|
selection.clear(this);
|
|
|
|
|
|
selection.all(this);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2016-07-08 10:48:24 -08:00
|
|
|
|
// override double click behavior to copy
|
|
|
|
|
|
const oldMouse = hterm.Terminal.prototype.onMouse_;
|
|
|
|
|
|
hterm.Terminal.prototype.onMouse_ = function (e) {
|
2016-09-21 06:27:11 -08:00
|
|
|
|
if (e.type === 'dblclick') {
|
2016-08-18 19:09:40 -08:00
|
|
|
|
selection.extend(this);
|
2016-07-08 10:48:24 -08:00
|
|
|
|
console.log('[hyperterm+hterm] ignore double click');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
return oldMouse.call(this, e);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// there's no option to turn off the size overlay
|
|
|
|
|
|
hterm.Terminal.prototype.overlaySize = function () {};
|
|
|
|
|
|
|
|
|
|
|
|
// fixing a bug in hterm where a double click triggers
|
|
|
|
|
|
// a non-collapsed selection whose text is '', and results
|
|
|
|
|
|
// in an infinite copy loop
|
|
|
|
|
|
hterm.Terminal.prototype.copySelectionToClipboard = function () {
|
2016-09-21 06:27:11 -08:00
|
|
|
|
const text = this.getSelectionText();
|
2016-10-04 10:16:34 -08:00
|
|
|
|
if (text) {
|
2016-07-08 10:48:24 -08:00
|
|
|
|
this.copyStringToClipboard(text);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// passthrough all the commands that are meant to control
|
|
|
|
|
|
// hyperterm and not the terminal itself
|
|
|
|
|
|
const oldKeyDown = hterm.Keyboard.prototype.onKeyDown_;
|
|
|
|
|
|
hterm.Keyboard.prototype.onKeyDown_ = function (e) {
|
2016-10-03 07:29:33 -08:00
|
|
|
|
const modifierKeysConf = this.terminal.modifierKeys;
|
|
|
|
|
|
|
2016-08-01 16:08:51 -08:00
|
|
|
|
/**
|
|
|
|
|
|
* Add fixes for U.S. International PC Keyboard layout
|
|
|
|
|
|
* These keys are sent through as 'Dead' keys, as they're used as modifiers.
|
|
|
|
|
|
* Ignore that and insert the correct character.
|
|
|
|
|
|
*/
|
2016-08-10 09:37:35 -08:00
|
|
|
|
if (e.key === 'Dead') {
|
|
|
|
|
|
if (e.code === 'Quote' && e.shiftKey === false) {
|
2016-09-21 06:27:11 -08:00
|
|
|
|
this.terminal.onVTKeystroke('\'');
|
2016-08-10 09:37:35 -08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (e.code === 'Quote' && e.shiftKey === true) {
|
|
|
|
|
|
this.terminal.onVTKeystroke('"');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if ((e.code === 'IntlBackslash' || e.code === 'Backquote') && e.shiftKey === true) {
|
|
|
|
|
|
this.terminal.onVTKeystroke('~');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
// This key is also a tilde on all tested keyboards
|
2016-10-03 07:29:33 -08:00
|
|
|
|
if (e.code === 'KeyN' && e.altKey === true && modifierKeysConf.altIsMeta === false) {
|
2016-08-10 09:37:35 -08:00
|
|
|
|
this.terminal.onVTKeystroke('~');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if ((e.code === 'IntlBackslash' || e.code === 'Backquote') && e.shiftKey === false) {
|
|
|
|
|
|
this.terminal.onVTKeystroke('`');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (e.code === 'Digit6') {
|
|
|
|
|
|
this.terminal.onVTKeystroke('^');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
// German keyboard layout
|
|
|
|
|
|
if (e.code === 'Equal' && e.shiftKey === false) {
|
|
|
|
|
|
this.terminal.onVTKeystroke('´');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (e.code === 'Equal' && e.shiftKey === true) {
|
|
|
|
|
|
this.terminal.onVTKeystroke('`');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
// Italian keyboard layout
|
2016-10-03 07:29:33 -08:00
|
|
|
|
if (e.code === 'Digit9' && e.altKey === true && modifierKeysConf.altIsMeta === false) {
|
2016-08-10 09:37:35 -08:00
|
|
|
|
this.terminal.onVTKeystroke('`');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2016-10-03 07:29:33 -08:00
|
|
|
|
if (e.code === 'Digit8' && e.altKey === true && modifierKeysConf.altIsMeta === false) {
|
2016-08-10 09:37:35 -08:00
|
|
|
|
this.terminal.onVTKeystroke('´');
|
|
|
|
|
|
// To fix issue with changing the terminal prompt
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2016-08-22 09:43:56 -08:00
|
|
|
|
// French keyboard layout
|
|
|
|
|
|
if (e.code === 'BracketLeft') {
|
|
|
|
|
|
this.terminal.onVTKeystroke('^');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (e.code === 'Backslash') {
|
|
|
|
|
|
this.terminal.onVTKeystroke('`');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2016-08-10 09:37:35 -08:00
|
|
|
|
console.warn('Uncaught dead key on international keyboard', e);
|
2016-08-01 16:08:51 -08:00
|
|
|
|
}
|
2016-08-10 09:37:35 -08:00
|
|
|
|
|
2016-09-30 04:30:11 -08:00
|
|
|
|
if (e.altKey &&
|
2016-10-03 07:29:33 -08:00
|
|
|
|
e.which !== 16 && // Ignore other modifer keys
|
|
|
|
|
|
e.which !== 17 &&
|
|
|
|
|
|
e.which !== 18 &&
|
|
|
|
|
|
e.which !== 91 &&
|
2016-09-30 04:30:11 -08:00
|
|
|
|
modifierKeysConf.altIsMeta) {
|
2016-10-03 07:29:33 -08:00
|
|
|
|
const char = fromCharCode(e);
|
|
|
|
|
|
this.terminal.onVTKeystroke('\x1b' + char);
|
2016-09-30 04:30:11 -08:00
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (e.metaKey &&
|
|
|
|
|
|
e.code !== 'MetaLeft' &&
|
|
|
|
|
|
e.code !== 'MetaRight' &&
|
2016-10-03 07:29:33 -08:00
|
|
|
|
e.which !== 16 &&
|
|
|
|
|
|
e.which !== 17 &&
|
|
|
|
|
|
e.which !== 18 &&
|
|
|
|
|
|
e.which !== 91 &&
|
2016-09-30 04:30:11 -08:00
|
|
|
|
modifierKeysConf.cmdIsMeta) {
|
2016-10-03 07:29:33 -08:00
|
|
|
|
const char = fromCharCode(e);
|
|
|
|
|
|
this.terminal.onVTKeystroke('\x1b' + char);
|
2016-09-30 04:30:11 -08:00
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-10-04 07:33:21 -08:00
|
|
|
|
if (e.metaKey || e.altKey || (e.ctrlKey && e.code === 'Tab')) {
|
2016-07-08 10:48:24 -08:00
|
|
|
|
return;
|
2016-09-21 06:27:11 -08:00
|
|
|
|
}
|
|
|
|
|
|
if ((!e.ctrlKey || e.code !== 'ControlLeft') && !e.shiftKey && e.code !== 'CapsLock') {
|
2016-08-13 13:03:44 -08:00
|
|
|
|
// Test for valid keys in order to clear the terminal selection
|
2016-09-21 06:27:11 -08:00
|
|
|
|
selection.clear(this.terminal);
|
2016-07-08 10:48:24 -08:00
|
|
|
|
}
|
|
|
|
|
|
return oldKeyDown.call(this, e);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const oldKeyPress = hterm.Keyboard.prototype.onKeyPress_;
|
|
|
|
|
|
hterm.Keyboard.prototype.onKeyPress_ = function (e) {
|
|
|
|
|
|
if (e.metaKey) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2016-09-21 06:27:11 -08:00
|
|
|
|
selection.clear(this.terminal);
|
2016-07-08 10:48:24 -08:00
|
|
|
|
return oldKeyPress.call(this, e);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2016-07-13 12:44:24 -08:00
|
|
|
|
// we re-implement `wipeContents` to preserve the line
|
|
|
|
|
|
// and cursor position that the client is in.
|
|
|
|
|
|
// otherwise the user ends up with a completely clear
|
|
|
|
|
|
// screen which is really strange
|
|
|
|
|
|
hterm.Terminal.prototype.clearPreserveCursorRow = function () {
|
|
|
|
|
|
this.scrollbackRows_.length = 0;
|
|
|
|
|
|
this.scrollPort_.resetCache();
|
|
|
|
|
|
|
2016-09-21 06:27:11 -08:00
|
|
|
|
[this.primaryScreen_, this.alternateScreen_].forEach(screen => {
|
2016-07-13 12:44:24 -08:00
|
|
|
|
const bottom = screen.getHeight();
|
|
|
|
|
|
if (bottom > 0) {
|
|
|
|
|
|
this.renumberRows_(0, bottom);
|
|
|
|
|
|
|
|
|
|
|
|
const x = screen.cursorPosition.column;
|
|
|
|
|
|
const y = screen.cursorPosition.row;
|
|
|
|
|
|
|
|
|
|
|
|
if (x === 0) {
|
|
|
|
|
|
// Empty screen, nothing to do.
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// here we move the row that the user was focused on
|
|
|
|
|
|
// to the top of the screen
|
|
|
|
|
|
this.moveRows_(y, 1, 0);
|
|
|
|
|
|
|
|
|
|
|
|
for (let i = 1; i < bottom; i++) {
|
|
|
|
|
|
screen.setCursorPosition(i, 0);
|
|
|
|
|
|
screen.clearCursorRow();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// we restore the cursor position
|
|
|
|
|
|
screen.setCursorPosition(0, x);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
this.syncCursorPosition_();
|
|
|
|
|
|
this.scrollPort_.invalidate();
|
|
|
|
|
|
|
|
|
|
|
|
// this will avoid a bug where the `wipeContents`
|
2016-07-17 13:05:37 -08:00
|
|
|
|
// hterm API doesn't send the scroll to the top
|
2016-07-13 12:44:24 -08:00
|
|
|
|
this.scrollPort_.redraw_();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2016-10-03 18:00:50 -08:00
|
|
|
|
const oldOnMouse = hterm.Terminal.prototype.onMouse_;
|
|
|
|
|
|
hterm.Terminal.prototype.onMouse_ = function (e) {
|
|
|
|
|
|
// override `preventDefault` to not actually
|
|
|
|
|
|
// prevent default when the type of event is
|
|
|
|
|
|
// mousedown, so that we can still trigger
|
|
|
|
|
|
// focus on the terminal when the underlying
|
|
|
|
|
|
// VT is interested in mouse events, as is the
|
|
|
|
|
|
// case of programs like `vtop` that allow for
|
|
|
|
|
|
// the user to click on rows
|
|
|
|
|
|
if (e.type === 'mousedown') {
|
|
|
|
|
|
e.preventDefault = function () { };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return oldOnMouse.call(this, e);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// sine above we're no longer relying on `preventDefault`
|
|
|
|
|
|
// to avoid selections, we use css instead, so that
|
|
|
|
|
|
// focus is not lost, but selections are still not possible
|
|
|
|
|
|
// when the appropiate VT mode is set
|
|
|
|
|
|
hterm.VT.prototype.__defineSetter__('mouseReport', function (val) {
|
|
|
|
|
|
this.mouseReport_ = val;
|
|
|
|
|
|
const rowNodes = this.terminal.scrollPort_.rowNodes_;
|
|
|
|
|
|
if (rowNodes) {
|
|
|
|
|
|
if (val === this.MOUSE_REPORT_DISABLED) {
|
|
|
|
|
|
rowNodes.style.webkitUserSelect = 'text';
|
|
|
|
|
|
} else {
|
|
|
|
|
|
rowNodes.style.webkitUserSelect = 'none';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
hterm.VT.prototype.__defineGetter__('mouseReport', function () {
|
|
|
|
|
|
return this.mouseReport_;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2016-07-19 09:46:26 -08:00
|
|
|
|
// fixes a bug in hterm, where the shorthand hex
|
|
|
|
|
|
// is not properly converted to rgb
|
2016-07-20 14:21:29 -08:00
|
|
|
|
lib.colors.hexToRGB = function (arg) {
|
2016-09-21 06:27:11 -08:00
|
|
|
|
const hex16 = lib.colors.re_.hex16;
|
|
|
|
|
|
const hex24 = lib.colors.re_.hex24;
|
2016-07-19 09:46:26 -08:00
|
|
|
|
|
2016-09-21 06:27:11 -08:00
|
|
|
|
function convert(hex) {
|
2016-07-20 14:21:29 -08:00
|
|
|
|
if (hex.length === 4) {
|
2016-09-21 06:27:11 -08:00
|
|
|
|
hex = hex.replace(hex16, (h, r, g, b) => {
|
2016-07-20 14:21:29 -08:00
|
|
|
|
return '#' + r + r + g + g + b + b;
|
2016-07-19 09:46:26 -08:00
|
|
|
|
});
|
|
|
|
|
|
}
|
2016-09-21 06:27:11 -08:00
|
|
|
|
const ary = hex.match(hex24);
|
|
|
|
|
|
if (!ary) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
2016-07-19 09:46:26 -08:00
|
|
|
|
|
2016-07-20 14:21:29 -08:00
|
|
|
|
return 'rgb(' +
|
|
|
|
|
|
parseInt(ary[1], 16) + ', ' +
|
|
|
|
|
|
parseInt(ary[2], 16) + ', ' +
|
|
|
|
|
|
parseInt(ary[3], 16) +
|
|
|
|
|
|
')';
|
2016-07-19 09:46:26 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (arg instanceof Array) {
|
2016-09-21 06:27:11 -08:00
|
|
|
|
for (let i = 0; i < arg.length; i++) {
|
2016-07-19 09:46:26 -08:00
|
|
|
|
arg[i] = convert(arg[i]);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
arg = convert(arg);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return arg;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2016-07-13 12:44:24 -08:00
|
|
|
|
export default hterm;
|
2016-09-21 06:27:11 -08:00
|
|
|
|
export {lib};
|