mirror of
https://github.com/quine-global/hyper.git
synced 2026-01-13 04:28:41 -09:00
* Allow the `color` config to be an object It only covers the ANSI 16 as named colors, but allows for an array to be used if the full color palette wants to be overridden. * Better handling for array color configs vs. object configs
249 lines
6.6 KiB
JavaScript
249 lines
6.6 KiB
JavaScript
/* global Blob,URL,requestAnimationFrame */
|
|
import React from 'react';
|
|
import Color from 'color';
|
|
import hterm from '../hterm';
|
|
import Component from '../component';
|
|
import { getColorList } from '../utils/colors'
|
|
|
|
export default class Term extends Component {
|
|
|
|
constructor (props) {
|
|
super(props);
|
|
this.onWheel = this.onWheel.bind(this);
|
|
this.onScrollEnter = this.onScrollEnter.bind(this);
|
|
this.onScrollLeave = this.onScrollLeave.bind(this);
|
|
props.ref_(this);
|
|
}
|
|
|
|
componentDidMount () {
|
|
const { props } = this;
|
|
this.term = new hterm.Terminal();
|
|
|
|
// the first term that's created has unknown size
|
|
// subsequent new tabs have size
|
|
if (props.cols && props.rows) {
|
|
this.term.realizeSize_(props.cols, props.rows);
|
|
}
|
|
|
|
this.term.prefs_.set('font-family', props.fontFamily);
|
|
this.term.prefs_.set('font-size', props.fontSize);
|
|
this.term.prefs_.set('font-smoothing', props.fontSmoothing);
|
|
this.term.prefs_.set('cursor-color', Color(props.cursorColor).rgbString());
|
|
this.term.prefs_.set('enable-clipboard-notice', false);
|
|
this.term.prefs_.set('foreground-color', props.foregroundColor);
|
|
this.term.prefs_.set('background-color', props.backgroundColor);
|
|
this.term.prefs_.set('color-palette-overrides', getColorList(props.colors));
|
|
this.term.prefs_.set('user-css', this.getStylesheet(props.customCSS));
|
|
this.term.prefs_.set('scrollbar-visible', false);
|
|
this.term.prefs_.set('receive-encoding', 'raw');
|
|
this.term.prefs_.set('send-encoding', 'raw');
|
|
this.term.prefs_.set('alt-sends-what', 'browser-key');
|
|
|
|
this.term.onTerminalReady = () => {
|
|
const io = this.term.io.push();
|
|
io.onVTKeystroke = io.sendString = props.onData;
|
|
io.onTerminalResize = (cols, rows) => {
|
|
if (cols !== this.props.cols || rows !== this.props.rows) {
|
|
props.onResize(cols, rows);
|
|
}
|
|
};
|
|
};
|
|
this.term.decorate(this.refs.term);
|
|
this.term.installKeyboard();
|
|
if (this.props.onTerminal) this.props.onTerminal(this.term);
|
|
|
|
const iframeWindow = this.getTermDocument().defaultView;
|
|
iframeWindow.addEventListener('wheel', this.onWheel);
|
|
}
|
|
|
|
onWheel () {
|
|
this.term.prefs_.set('scrollbar-visible', true);
|
|
clearTimeout(this.scrollbarsHideTimer);
|
|
if (!this.scrollMouseEnter) {
|
|
this.scrollbarsHideTimer = setTimeout(() => {
|
|
this.term.prefs_.set('scrollbar-visible', false);
|
|
}, 1000);
|
|
}
|
|
}
|
|
|
|
onScrollEnter () {
|
|
clearTimeout(this.scrollbarsHideTimer);
|
|
this.term.prefs_.set('scrollbar-visible', true);
|
|
this.scrollMouseEnter = true;
|
|
}
|
|
|
|
onScrollLeave () {
|
|
this.term.prefs_.set('scrollbar-visible', false);
|
|
this.scrollMouseEnter = false;
|
|
}
|
|
|
|
write (data) {
|
|
requestAnimationFrame(() => {
|
|
this.term.io.writeUTF8(data);
|
|
});
|
|
}
|
|
|
|
focus () {
|
|
this.term.focus();
|
|
}
|
|
|
|
clear () {
|
|
this.term.clearPreserveCursorRow();
|
|
|
|
// If cursor is still not at the top, a command is probably
|
|
// running and we'd like to delete the whole screen.
|
|
// Move cursor to top
|
|
if (this.term.getCursorRow() !== 0) {
|
|
this.term.io.writeUTF8('\x1B[0;0H\x1B[2J');
|
|
}
|
|
}
|
|
|
|
moveWordLeft () {
|
|
this.term.onVTKeystroke('\x1bb');
|
|
}
|
|
|
|
moveWordRight () {
|
|
this.term.onVTKeystroke('\x1bf');
|
|
}
|
|
|
|
deleteWordLeft () {
|
|
this.term.onVTKeystroke('\x1b\x7f');
|
|
}
|
|
|
|
deleteWordRight () {
|
|
this.term.onVTKeystroke('\x1bd');
|
|
}
|
|
|
|
deleteLine () {
|
|
this.term.onVTKeystroke('\x1bw');
|
|
}
|
|
|
|
moveToStart () {
|
|
this.term.onVTKeystroke('\x01');
|
|
}
|
|
|
|
moveToEnd () {
|
|
this.term.onVTKeystroke('\x05');
|
|
}
|
|
|
|
getTermDocument () {
|
|
return this.term.document_;
|
|
}
|
|
|
|
getStylesheet (css) {
|
|
const blob = new Blob([`
|
|
.cursor-node[focus="false"] {
|
|
border-width: 1px !important;
|
|
}
|
|
${css}
|
|
`]);
|
|
return URL.createObjectURL(blob, { type: 'text/css' });
|
|
}
|
|
|
|
componentWillReceiveProps (nextProps) {
|
|
if (this.props.url !== nextProps.url) {
|
|
// when the url prop changes, we make sure
|
|
// the terminal starts or stops ignoring
|
|
// key input so that it doesn't conflict
|
|
// with the <webview>
|
|
if (nextProps.url) {
|
|
const io = this.term.io.push();
|
|
io.onVTKeystroke = io.sendString = (str) => {
|
|
if (1 === str.length && 3 === str.charCodeAt(0) /* Ctrl + C */) {
|
|
this.props.onURLAbort();
|
|
}
|
|
};
|
|
} else {
|
|
this.term.io.pop();
|
|
}
|
|
}
|
|
|
|
if (!this.props.cleared && nextProps.cleared) {
|
|
this.clear();
|
|
}
|
|
|
|
if (this.props.fontSize !== nextProps.fontSize) {
|
|
this.term.prefs_.set('font-size', nextProps.fontSize);
|
|
}
|
|
|
|
if (this.props.foregroundColor !== nextProps.foregroundColor) {
|
|
this.term.prefs_.set('foreground-color', nextProps.foregroundColor);
|
|
}
|
|
|
|
if (this.props.backgroundColor !== nextProps.backgroundColor) {
|
|
this.term.prefs_.set('background-color', nextProps.backgroundColor);
|
|
}
|
|
|
|
if (this.props.fontFamily !== nextProps.fontFamily) {
|
|
this.term.prefs_.set('font-family', nextProps.fontFamily);
|
|
}
|
|
|
|
if (this.props.fontSmoothing !== nextProps.fontSmoothing) {
|
|
this.term.prefs_.set('font-smoothing', nextProps.fontSmoothing);
|
|
}
|
|
|
|
if (this.props.cursorColor !== nextProps.cursorColor) {
|
|
this.term.prefs_.set('cursor-color', Color(nextProps.cursorColor).rgbString());
|
|
}
|
|
|
|
if (this.props.colors !== nextProps.colors) {
|
|
this.term.prefs_.set('color-palette-overrides', getColorList(nextProps.colors));
|
|
}
|
|
|
|
if (this.props.customCSS !== nextProps.customCSS) {
|
|
this.term.prefs_.set('user-css', this.getStylesheet(nextProps.customCSS));
|
|
}
|
|
}
|
|
|
|
componentWillUnmount () {
|
|
clearTimeout(this.scrollbarsHideTimer);
|
|
this.props.ref_(null);
|
|
}
|
|
|
|
template (css) {
|
|
return <div className={ css('fit') }>
|
|
{ this.props.customChildrenBefore }
|
|
<div ref='term' className={ css('fit', 'term') } />
|
|
{ this.props.url
|
|
? <webview
|
|
src={this.props.url}
|
|
style={{
|
|
background: '#000',
|
|
position: 'absolute',
|
|
top: 0,
|
|
left: 0,
|
|
display: 'inline-flex',
|
|
width: '100%',
|
|
height: '100%'
|
|
}}></webview>
|
|
: <div
|
|
className={ css('scrollbarShim') }
|
|
onMouseEnter={ this.onScrollEnter }
|
|
onMouseLeave={ this.onScrollLeave } />
|
|
}
|
|
{ this.props.customChildren }
|
|
</div>;
|
|
}
|
|
|
|
styles () {
|
|
return {
|
|
fit: {
|
|
width: '100%',
|
|
height: '100%'
|
|
},
|
|
|
|
term: {
|
|
position: 'relative'
|
|
},
|
|
|
|
scrollbarShim: {
|
|
position: 'fixed',
|
|
right: 0,
|
|
width: '50px',
|
|
top: 0,
|
|
bottom: 0
|
|
}
|
|
};
|
|
}
|
|
|
|
}
|