mirror of
https://github.com/quine-global/hyper.git
synced 2026-01-13 04:28:41 -09:00
* npm: add .npmrc with save-exact=true * split panes: create initial implementation This allows users to split their Hyperterm terms into multiple nested splits, both vertical and horizontal. Fixes #56 * split panes: suport closing tabs and individual panes * split panes: ensure new splits are placed at the correct index New split panes should be placed after the currently active pane, not at the end like they were previously. * split panes: add explicit dependency to uuid * split panes: implement split pane cycling This adds menu buttons for moving back and forward between open split panes in the currect terminal tab. Doesn't add a hotkey yet, needs some bikeshedding. * split panes: move activeSessionUid to its own object It made little sense to have so many objects with `activeSessionUid` set to `null` when it only mattered on the top level. Now it's an object mapping term-group `uid` to `sessionUid` instead. * split panes: make sure closing the last split pane exits the app * split panes: fix a crash after closing specific panes Sometimes the terminal would crash when a specific split pane was closed, because the `activeSessions` mapping wasn't updated correctly. * split panes: fix a bug that caused initial session sizing to be wrong * fix all our focus / blur issues in one fell swoop :O (famous last words) * get rid of react warning * hterm: make sure not to lose focus when VT listens on clicks * term: restore onactive callback * add missing `return` to override (just in case) * split pane: new split pane implementation * goodbye react-split-pane * added term group resizing action and reducer * terms: supply border color so that we can use it for splits * term-group: add resizing hook * term-groups: add resizing constant * remove split pane css side-effect * split panes: pass existing hterm instances to Term * split panes: add keybindings for split pane cycling * split panes: remove unused action * split panes: remove unused styling * split-pane: remove `console.log` * split-pane: remove `console.log` * split panes: rebalance sizes on insert/removal * split panes: pass existing hterm instances to Term * split panes: add keybindings for split pane cycling * split panes: remove unused action * split panes: remove unused styling * split panes: rebalance sizes on insert/removal * split panes: set a minimum size for resizing * split-pane: fix vertical splits * css :| * package: bump electron * split panes: attach onFocus listener to webviews * 1.4.1 and 1.4.2 are broken. they have the following regression: - open google.com on the main window - open a new tab - come back to previous tab. webview is gone :| * split panes: handle PTY exits * split panes: add linux friendly keybindings
274 lines
7.7 KiB
JavaScript
274 lines
7.7 KiB
JavaScript
import {hterm, lib} from 'hterm-umdjs';
|
||
import fromCharCode from './utils/key-code';
|
||
|
||
const selection = require('./utils/selection');
|
||
|
||
// Keys that are used in combination
|
||
// with ctrl based hotkeys:
|
||
const IGNORED_CTRL_KEYS = [
|
||
'Tab',
|
||
'KeyO',
|
||
'KeyE'
|
||
];
|
||
|
||
hterm.defaultStorage = new lib.Storage.Memory();
|
||
|
||
// 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);
|
||
};
|
||
|
||
// override double click behavior to copy
|
||
const oldMouse = hterm.Terminal.prototype.onMouse_;
|
||
hterm.Terminal.prototype.onMouse_ = function (e) {
|
||
if (e.type === 'dblclick') {
|
||
selection.extend(this);
|
||
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 () {
|
||
const text = this.getSelectionText();
|
||
if (text !== null && text !== '') {
|
||
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) {
|
||
const modifierKeysConf = this.terminal.modifierKeys;
|
||
|
||
/**
|
||
* 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.
|
||
*/
|
||
if (e.key === 'Dead') {
|
||
if (e.code === 'Quote' && e.shiftKey === false) {
|
||
this.terminal.onVTKeystroke('\'');
|
||
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
|
||
if (e.code === 'KeyN' && e.altKey === true && modifierKeysConf.altIsMeta === false) {
|
||
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
|
||
if (e.code === 'Digit9' && e.altKey === true && modifierKeysConf.altIsMeta === false) {
|
||
this.terminal.onVTKeystroke('`');
|
||
return;
|
||
}
|
||
if (e.code === 'Digit8' && e.altKey === true && modifierKeysConf.altIsMeta === false) {
|
||
this.terminal.onVTKeystroke('´');
|
||
// To fix issue with changing the terminal prompt
|
||
e.preventDefault();
|
||
return;
|
||
}
|
||
// French keyboard layout
|
||
if (e.code === 'BracketLeft') {
|
||
this.terminal.onVTKeystroke('^');
|
||
return;
|
||
}
|
||
if (e.code === 'Backslash') {
|
||
this.terminal.onVTKeystroke('`');
|
||
return;
|
||
}
|
||
console.warn('Uncaught dead key on international keyboard', e);
|
||
}
|
||
|
||
if (e.altKey &&
|
||
e.which !== 16 && // Ignore other modifer keys
|
||
e.which !== 17 &&
|
||
e.which !== 18 &&
|
||
e.which !== 91 &&
|
||
modifierKeysConf.altIsMeta) {
|
||
const char = fromCharCode(e);
|
||
this.terminal.onVTKeystroke('\x1b' + char);
|
||
e.preventDefault();
|
||
}
|
||
|
||
if (e.metaKey &&
|
||
e.code !== 'MetaLeft' &&
|
||
e.code !== 'MetaRight' &&
|
||
e.which !== 16 &&
|
||
e.which !== 17 &&
|
||
e.which !== 18 &&
|
||
e.which !== 91 &&
|
||
modifierKeysConf.cmdIsMeta) {
|
||
const char = fromCharCode(e);
|
||
this.terminal.onVTKeystroke('\x1b' + char);
|
||
e.preventDefault();
|
||
}
|
||
|
||
if (e.metaKey || e.altKey || (e.ctrlKey && IGNORED_CTRL_KEYS.includes(e.code))) {
|
||
return;
|
||
}
|
||
if ((!e.ctrlKey || e.code !== 'ControlLeft') && !e.shiftKey && e.code !== 'CapsLock') {
|
||
// Test for valid keys in order to clear the terminal selection
|
||
selection.clear(this.terminal);
|
||
}
|
||
return oldKeyDown.call(this, e);
|
||
};
|
||
|
||
const oldKeyPress = hterm.Keyboard.prototype.onKeyPress_;
|
||
hterm.Keyboard.prototype.onKeyPress_ = function (e) {
|
||
if (e.metaKey) {
|
||
return;
|
||
}
|
||
selection.clear(this.terminal);
|
||
return oldKeyPress.call(this, e);
|
||
};
|
||
|
||
// 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();
|
||
|
||
[this.primaryScreen_, this.alternateScreen_].forEach(screen => {
|
||
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`
|
||
// hterm API doesn't send the scroll to the top
|
||
this.scrollPort_.redraw_();
|
||
};
|
||
|
||
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_;
|
||
});
|
||
|
||
// fixes a bug in hterm, where the shorthand hex
|
||
// is not properly converted to rgb
|
||
lib.colors.hexToRGB = function (arg) {
|
||
const hex16 = lib.colors.re_.hex16;
|
||
const hex24 = lib.colors.re_.hex24;
|
||
|
||
function convert(hex) {
|
||
if (hex.length === 4) {
|
||
hex = hex.replace(hex16, (h, r, g, b) => {
|
||
return '#' + r + r + g + g + b + b;
|
||
});
|
||
}
|
||
const ary = hex.match(hex24);
|
||
if (!ary) {
|
||
return null;
|
||
}
|
||
|
||
return 'rgb(' +
|
||
parseInt(ary[1], 16) + ', ' +
|
||
parseInt(ary[2], 16) + ', ' +
|
||
parseInt(ary[3], 16) +
|
||
')';
|
||
}
|
||
|
||
if (arg instanceof Array) {
|
||
for (let i = 0; i < arg.length; i++) {
|
||
arg[i] = convert(arg[i]);
|
||
}
|
||
} else {
|
||
arg = convert(arg);
|
||
}
|
||
|
||
return arg;
|
||
};
|
||
|
||
export default hterm;
|
||
export {lib};
|