2016-07-14 15:40:15 -08:00
|
|
|
/* global Blob,URL,requestAnimationFrame */
|
2016-07-13 12:44:24 -08:00
|
|
|
import React from 'react';
|
|
|
|
|
import hterm from '../hterm';
|
|
|
|
|
import Component from '../component';
|
2016-06-30 22:01:04 -08:00
|
|
|
|
|
|
|
|
export default class Term extends Component {
|
|
|
|
|
|
2016-07-13 12:44:24 -08:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2016-06-30 22:01:04 -08:00
|
|
|
componentDidMount () {
|
2016-07-07 07:53:23 -08:00
|
|
|
const { props } = this;
|
2016-07-03 12:35:45 -08:00
|
|
|
this.term = new hterm.Terminal();
|
2016-06-30 22:01:04 -08:00
|
|
|
|
2016-07-03 12:35:45 -08:00
|
|
|
// the first term that's created has unknown size
|
|
|
|
|
// subsequent new tabs have size
|
2016-07-13 12:44:24 -08:00
|
|
|
if (props.cols && props.rows) {
|
2016-07-07 07:53:23 -08:00
|
|
|
this.term.realizeSize_(props.cols, props.rows);
|
2016-06-30 22:01:04 -08:00
|
|
|
}
|
|
|
|
|
|
2016-07-07 07:53:23 -08:00
|
|
|
this.term.prefs_.set('font-family', props.fontFamily);
|
|
|
|
|
this.term.prefs_.set('font-size', props.fontSize);
|
|
|
|
|
this.term.prefs_.set('cursor-color', props.cursorColor);
|
2016-07-03 12:35:45 -08:00
|
|
|
this.term.prefs_.set('enable-clipboard-notice', false);
|
2016-07-13 12:44:24 -08:00
|
|
|
this.term.prefs_.set('foreground-color', props.foregroundColor);
|
2016-07-07 07:53:23 -08:00
|
|
|
this.term.prefs_.set('background-color', props.backgroundColor);
|
|
|
|
|
this.term.prefs_.set('color-palette-overrides', props.colors);
|
2016-07-08 13:27:41 -08:00
|
|
|
this.term.prefs_.set('user-css', this.getStylesheet(props.customCSS));
|
2016-07-13 12:44:24 -08:00
|
|
|
this.term.prefs_.set('scrollbar-visible', false);
|
2016-07-17 09:18:01 -08:00
|
|
|
this.term.prefs_.set('receive-encoding', 'raw');
|
|
|
|
|
this.term.prefs_.set('send-encoding', 'raw');
|
|
|
|
|
this.term.prefs_.set('alt-sends-what', 'browser-key');
|
2016-07-03 12:35:45 -08:00
|
|
|
|
|
|
|
|
this.term.onTerminalReady = () => {
|
|
|
|
|
const io = this.term.io.push();
|
2016-07-13 12:44:24 -08:00
|
|
|
io.onVTKeystroke = io.sendString = props.onData;
|
2016-07-03 12:35:45 -08:00
|
|
|
io.onTerminalResize = (cols, rows) => {
|
2016-07-14 14:02:13 -08:00
|
|
|
if (cols !== this.props.cols || rows !== this.props.rows) {
|
2016-07-13 12:44:24 -08:00
|
|
|
props.onResize(cols, rows);
|
|
|
|
|
}
|
2016-07-03 12:35:45 -08:00
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
this.term.decorate(this.refs.term);
|
|
|
|
|
this.term.installKeyboard();
|
2016-07-13 12:44:24 -08:00
|
|
|
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) {
|
2016-07-14 15:40:15 -08:00
|
|
|
requestAnimationFrame(() => {
|
2016-07-17 09:18:01 -08:00
|
|
|
this.term.io.writeUTF8(data);
|
2016-07-14 15:40:15 -08:00
|
|
|
});
|
2016-07-13 12:44:24 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
focus () {
|
|
|
|
|
this.term.focus();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
clear () {
|
|
|
|
|
this.term.clearPreserveCursorRow();
|
2016-07-17 18:52:29 -08:00
|
|
|
|
|
|
|
|
// 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');
|
|
|
|
|
}
|
2016-07-03 12:35:45 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getTermDocument () {
|
|
|
|
|
return this.term.document_;
|
|
|
|
|
}
|
|
|
|
|
|
2016-07-08 13:27:41 -08:00
|
|
|
getStylesheet (css) {
|
|
|
|
|
const blob = new Blob([`
|
|
|
|
|
.cursor-node[focus="false"] {
|
|
|
|
|
border-width: 1px !important;
|
|
|
|
|
}
|
2016-07-13 12:44:24 -08:00
|
|
|
${css}
|
2016-07-08 13:27:41 -08:00
|
|
|
`]);
|
|
|
|
|
return URL.createObjectURL(blob, { type: 'text/css' });
|
|
|
|
|
}
|
|
|
|
|
|
2016-07-05 16:08:45 -08:00
|
|
|
componentWillReceiveProps (nextProps) {
|
2016-06-30 22:01:04 -08:00
|
|
|
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) {
|
2016-07-03 12:35:45 -08:00
|
|
|
const io = this.term.io.push();
|
|
|
|
|
io.onVTKeystroke = io.sendString = (str) => {
|
|
|
|
|
if (1 === str.length && 3 === str.charCodeAt(0) /* Ctrl + C */) {
|
|
|
|
|
this.props.onURLAbort();
|
|
|
|
|
}
|
|
|
|
|
};
|
2016-06-30 22:01:04 -08:00
|
|
|
} else {
|
2016-07-03 12:35:45 -08:00
|
|
|
this.term.io.pop();
|
2016-06-30 22:01:04 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-07-13 12:44:24 -08:00
|
|
|
if (!this.props.cleared && nextProps.cleared) {
|
|
|
|
|
this.clear();
|
2016-07-05 12:14:30 -08:00
|
|
|
}
|
|
|
|
|
|
2016-07-13 12:44:24 -08:00
|
|
|
if (this.props.fontSize !== nextProps.fontSize) {
|
|
|
|
|
this.term.prefs_.set('font-size', nextProps.fontSize);
|
2016-06-30 22:01:04 -08:00
|
|
|
}
|
2016-07-05 12:14:30 -08:00
|
|
|
|
2016-07-13 12:44:24 -08:00
|
|
|
if (this.props.foregroundColor !== nextProps.foregroundColor) {
|
|
|
|
|
this.term.prefs_.set('foreground-color', nextProps.foregroundColor);
|
|
|
|
|
}
|
2016-07-03 12:35:45 -08:00
|
|
|
|
2016-07-13 12:44:24 -08:00
|
|
|
if (this.props.backgroundColor !== nextProps.backgroundColor) {
|
|
|
|
|
this.term.prefs_.set('background-color', nextProps.backgroundColor);
|
|
|
|
|
}
|
2016-07-03 12:35:45 -08:00
|
|
|
|
2016-07-13 12:44:24 -08:00
|
|
|
if (this.props.fontFamily !== nextProps.fontFamily) {
|
|
|
|
|
this.term.prefs_.set('font-family', nextProps.fontFamily);
|
|
|
|
|
}
|
2016-07-03 13:11:45 -08:00
|
|
|
|
2016-07-13 12:44:24 -08:00
|
|
|
if (this.props.cursorColor !== nextProps.cursorColor) {
|
|
|
|
|
this.term.prefs_.set('cursor-color', nextProps.cursorColor);
|
|
|
|
|
}
|
2016-07-03 12:35:45 -08:00
|
|
|
|
2016-07-13 12:44:24 -08:00
|
|
|
if (this.props.colors !== nextProps.colors) {
|
|
|
|
|
this.term.prefs_.set('color-palette-overrides', nextProps.colors);
|
|
|
|
|
}
|
2016-07-03 12:35:45 -08:00
|
|
|
|
2016-07-13 12:44:24 -08:00
|
|
|
if (this.props.customCSS !== nextProps.customCSS) {
|
|
|
|
|
this.term.prefs_.set('user-css', this.getStylesheet(nextProps.customCSS));
|
|
|
|
|
}
|
2016-06-30 22:01:04 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
componentWillUnmount () {
|
2016-07-13 12:44:24 -08:00
|
|
|
clearTimeout(this.scrollbarsHideTimer);
|
|
|
|
|
this.props.ref_(null);
|
2016-06-30 22:01:04 -08:00
|
|
|
}
|
|
|
|
|
|
2016-07-13 12:44:24 -08:00
|
|
|
template (css) {
|
|
|
|
|
return <div className={ css('fit') }>
|
|
|
|
|
{ this.props.customChildrenBefore }
|
|
|
|
|
<div ref='term' className={ css('fit', 'term') } />
|
2016-06-30 22:01:04 -08:00
|
|
|
{ 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>
|
2016-07-17 12:38:02 -08:00
|
|
|
: <div
|
|
|
|
|
className={ css('scrollbarShim') }
|
|
|
|
|
onMouseEnter={ this.onScrollEnter }
|
|
|
|
|
onMouseLeave={ this.onScrollLeave } />
|
2016-06-30 22:01:04 -08:00
|
|
|
}
|
2016-07-13 12:44:24 -08:00
|
|
|
{ this.props.customChildren }
|
2016-06-30 22:01:04 -08:00
|
|
|
</div>;
|
|
|
|
|
}
|
|
|
|
|
|
2016-07-13 12:44:24 -08:00
|
|
|
styles () {
|
|
|
|
|
return {
|
|
|
|
|
fit: {
|
|
|
|
|
width: '100%',
|
|
|
|
|
height: '100%'
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
term: {
|
|
|
|
|
position: 'relative'
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
scrollbarShim: {
|
|
|
|
|
position: 'fixed',
|
|
|
|
|
right: 0,
|
|
|
|
|
width: '50px',
|
|
|
|
|
top: 0,
|
|
|
|
|
bottom: 0
|
|
|
|
|
}
|
|
|
|
|
};
|
2016-06-30 22:01:04 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|