<commit_before>import Tabs_ from './tabs';
import Term_ from './term';
import RPC from './rpc';
import Mousetrap from 'mousetrap';
import classes from 'classnames';
import shallowCompare from 'react-addons-shallow-compare';
import React, { Component } from 'react';
import decorate from './plugins';

// make subcomponents reload siwth plugin changes
const Tabs = decorate(Tabs_);
const Term = decorate(Term_);

export default class HyperTerm extends Component {
  constructor (props) {
    super();
    this.state = {
      cols: null,
      rows: null,
      hpadding: 12,
      vpadding: 12,
      sessions: [],
      titles: {},
      urls: {},
      active: null,
      activeMarkers: [],
      mac: /Mac/.test(navigator.userAgent),
      resizeIndicatorShowing: false,
      fontSizeIndicatorShowing: false,
      dismissedUpdate: false,
      updateVersion: null,
      fontSize: props.config.fontSize
    };

    // we set this to true when the first tab
    // has been initialized and ack'd by the
    // node server for the *first time*
    this.init = false;

    // we keep track of activity in tabs to avoid
    // placing an activity marker right after
    // opening
    this.tabWasActive = {};

    this.onResize = this.onResize.bind(this);
    this.onChange = this.onChange.bind(this);
    this.openExternal = this.openExternal.bind(this);
    this.quitAndInstall = this.quitAndInstall.bind(this);
    this.focusActive = this.focusActive.bind(this);
    this.closeBrowser = this.closeBrowser.bind(this);
    this.onHeaderMouseDown = this.onHeaderMouseDown.bind(this);
    this.closeTab = this.closeTab.bind(this);

    this.moveLeft = this.moveLeft.bind(this);
    this.moveRight = this.moveRight.bind(this);
    this.resetFontSize = this.resetFontSize.bind(this);
    this.increaseFontSize = this.increaseFontSize.bind(this);
    this.decreaseFontSize = this.decreaseFontSize.bind(this);

    this.dismissUpdate = this.dismissUpdate.bind(this);
    this.onUpdateAvailable = this.onUpdateAvailable.bind(this);

    document.body.style.backgroundColor = props.config.backgroundColor;
  }

  componentWillReceiveProps (props) {
    if (props.config.fontSize !== this.props.config.fontSize) {
      this.changeFontSize(props.config.fontSize);
    }

    if (props.config.backgroundColor !== this.props.config.backgroundColor) {
      document.body.style.backgroundColor = props.config.backgroundColor;
    }
  }

  render () {
    const { backgroundColor, borderColor, css } = this.props.config;
    return <div onClick={ this.focusActive }>
      <div style={{ borderColor }} className={ classes('main', { mac: this.state.mac }) }>
        <header style={{ backgroundColor }} onMouseDown={this.onHeaderMouseDown}>
          <Tabs
            active={this.state.active}
            activeMarkers={this.state.activeMarkers}
            borderColor={borderColor}
            data={this.state.sessions.map((uid) => {
              const title = this.state.titles[uid];
              return null != title ? title : 'Shell';
            })}
            onChange={this.onChange}
            onClose={this.closeTab}
          />
        </header>

        <div
          className='terms'
          style={{ padding: `${this.state.vpadding}px ${this.state.hpadding}px` }}
          ref='termWrapper'>{
            this.state.sessions.map((uid, i) => {
              const active = i === this.state.active;
              const { config } = this.props;
              return <div key={`d${uid}`} className={classes('term', { active })} style={{ width: '100%', height: '100%' }}>
                <Term
                  key={uid}
                  ref={`term-${uid}`}
                  cols={this.state.cols}
                  rows={this.state.rows}
                  customCSS={config.termCSS}
                  fontSize={this.state.fontSize}
                  cursorColor={config.cursorColor}
                  fontFamily={config.fontFamily}
                  backgroundColor={config.backgroundColor}
                  colors={config.colors}
                  url={this.state.urls[uid]}
                  onResize={this.onResize}
                  onTitle={this.setTitle.bind(this, uid)}
                  onData={this.write.bind(this, uid)}
                  onURL={this.onURL.bind(this, uid)}
                  onURLAbort={this.closeBrowser}
                  />
              </div>;
            })
        }</div>
      </div>
      <div className={classes('resize-indicator', { showing: this.state.resizeIndicatorShowing })}>
        {this.state.fontSizeIndicatorShowing && <div>{ this.state.fontSize }px</div>}
        <div>{ this.state.cols }x{ this.state.rows }</div>
      </div>
      <div className={classes('update-indicator', { showing: null !== this.state.updateVersion && !this.state.dismissedUpdate })}>
        Version <b>{ this.state.updateVersion }</b> ready.
        {this.state.updateNote ? ` ${this.state.updateNote}. ` : ' '}
        <a href='' onClick={this.quitAndInstall}>Restart</a>
        to apply <span className='close' onClick={this.dismissUpdate}>[x]</span>
      </div>
      <style dangerouslySetInnerHTML={{ __html: Array.from(css || '').join('\n') }} />
    </div>;
  }

  dismissUpdate () {
    this.setState({ dismissedUpdate: true });
  }

  quitAndInstall (ev) {
    ev.preventDefault();
    this.rpc.emit('quit and install');
  }

  closeUpdateIndicator () {
    // @TODO
  }

  openExternal (ev) {
    ev.preventDefault();
    this.rpc.emit('open external', { url: ev.target.href });
  }

  requestTab () {
    // we send the hterm default size
    this.rpc.emit('new', { cols: this.state.cols, rows: this.state.rows });
  }

  closeActiveTab () {
    this.closeTab(this.state.active);
  }

  closeTab (id) {
    if (this.state.sessions.length) {
      const uid = this.state.sessions[id];
      this.rpc.emit('exit', { uid });
      this.onSessionExit({ uid });
    }
  }

  closeBrowser () {
    const uid = this.state.sessions[this.state.active];
    if (this.state.urls[uid]) {
      const urls = Object.assign({}, this.state.urls);
      delete urls[uid];
      this.setState({ urls });
    }
  }

  write (uid, data) {
    this.rpc.emit('data', { uid, data });
  }

  onURL (uid, url) {
    const urls = Object.assign({}, this.state.urls, { [uid]: url });
    this.setState({ urls });
  }

  onRemoteTitle ({ uid, title }) {
    this.setTitle(uid, title);
  }

  setTitle (uid, title) {
    const { titles: _titles } = this.state;
    const titles = Object.assign({}, _titles, { [uid]: title });
    this.setState({ titles });
  }

  onActive (uid) {
    const i = this.state.sessions.indexOf(uid);

    // we ignore activity markers all the way
    // up to the tab's been active
    const wasActive = this.tabWasActive[uid];
    if (!wasActive) {
      console.log('ignoring active, tab has not been focused', uid);
      this.tabWasActive[uid] = true;
      return;
    }

    if (this.state.active !== i && !~this.state.activeMarkers.indexOf(i)) {
      const activeMarkers = this.state.activeMarkers.slice();
      activeMarkers.push(i);
      this.setState({ activeMarkers });
    }
  }

  shouldComponentUpdate (nextProps, nextState) {
    if (this.state.active !== nextState.active) {
      const curUid = this.state.sessions[this.state.active];
      // make sure that the blurred uid has not been
      // optimistically removed
      if (curUid && ~nextState.sessions.indexOf(curUid)) {
        this.rpc.emit('blur', { uid: curUid });
      }
      const nextUid = nextState.sessions[nextState.active];
      this.rpc.emit('focus', { uid: nextUid });
      this.shouldInitKeys = true;
    } else {
      this.shouldInitKeys = false;
    }

    return shallowCompare(this, nextProps, nextState);
  }

  componentDidMount () {
    this.rpc = new RPC();

    // open a new tab upon mounting
    this.rpc.once('ready', () => this.requestTab());

    this.rpc.on('new session', ({ uid }) => {
      const { sessions: _sessions } = this.state;
      const sessions = _sessions.concat(uid);
      const state = { sessions };
      state.active = sessions.length - 1;
      this.setState(state, () => {
        if (this.state.sessions.length && !this.init) {
          this.rpc.emit('init');
          this.init = true;
        }
      });
    });

    this.rpc.on('clear', this.clearCurrentTerm.bind(this));
    this.rpc.on('exit', this.onSessionExit.bind(this));

    this.rpc.on('data', ({ uid, data }) => {
      if (this.ignoreActivity) {
        // we ignore activity for up to 300ms after triggering<commit_msg>hyperterm: fix race condition<commit_after>import Tabs_ from './tabs';
import Term_ from './term';
import RPC from './rpc';
import Mousetrap from 'mousetrap';
import classes from 'classnames';
import shallowCompare from 'react-addons-shallow-compare';
import React, { Component } from 'react';
import decorate from './plugins';

// make subcomponents reload siwth plugin changes
const Tabs = decorate(Tabs_);
const Term = decorate(Term_);

export default class HyperTerm extends Component {
  constructor (props) {
    super();
    this.state = {
      cols: null,
      rows: null,
      hpadding: 12,
      vpadding: 12,
      sessions: [],
      titles: {},
      urls: {},
      active: null,
      activeMarkers: [],
      mac: /Mac/.test(navigator.userAgent),
      resizeIndicatorShowing: false,
      fontSizeIndicatorShowing: false,
      dismissedUpdate: false,
      updateVersion: null,
      fontSize: props.config.fontSize
    };

    // we set this to true when the first tab
    // has been initialized and ack'd by the
    // node server for the *first time*
    this.init = false;

    // we keep track of activity in tabs to avoid
    // placing an activity marker right after
    // opening
    this.tabWasActive = {};

    this.onResize = this.onResize.bind(this);
    this.onChange = this.onChange.bind(this);
    this.openExternal = this.openExternal.bind(this);
    this.quitAndInstall = this.quitAndInstall.bind(this);
    this.focusActive = this.focusActive.bind(this);
    this.closeBrowser = this.closeBrowser.bind(this);
    this.onHeaderMouseDown = this.onHeaderMouseDown.bind(this);
    this.closeTab = this.closeTab.bind(this);

    this.moveLeft = this.moveLeft.bind(this);
    this.moveRight = this.moveRight.bind(this);
    this.resetFontSize = this.resetFontSize.bind(this);
    this.increaseFontSize = this.increaseFontSize.bind(this);
    this.decreaseFontSize = this.decreaseFontSize.bind(this);

    this.dismissUpdate = this.dismissUpdate.bind(this);
    this.onUpdateAvailable = this.onUpdateAvailable.bind(this);

    document.body.style.backgroundColor = props.config.backgroundColor;
    this.rpc = new RPC();
  }

  componentWillReceiveProps (props) {
    if (props.config.fontSize !== this.props.config.fontSize) {
      this.changeFontSize(props.config.fontSize);
    }

    if (props.config.backgroundColor !== this.props.config.backgroundColor) {
      document.body.style.backgroundColor = props.config.backgroundColor;
    }
  }

  render () {
    const { backgroundColor, borderColor, css } = this.props.config;
    return <div onClick={ this.focusActive }>
      <div style={{ borderColor }} className={ classes('main', { mac: this.state.mac }) }>
        <header style={{ backgroundColor }} onMouseDown={this.onHeaderMouseDown}>
          <Tabs
            active={this.state.active}
            activeMarkers={this.state.activeMarkers}
            borderColor={borderColor}
            data={this.state.sessions.map((uid) => {
              const title = this.state.titles[uid];
              return null != title ? title : 'Shell';
            })}
            onChange={this.onChange}
            onClose={this.closeTab}
          />
        </header>

        <div
          className='terms'
          style={{ padding: `${this.state.vpadding}px ${this.state.hpadding}px` }}
          ref='termWrapper'>{
            this.state.sessions.map((uid, i) => {
              const active = i === this.state.active;
              const { config } = this.props;
              return <div key={`d${uid}`} className={classes('term', { active })} style={{ width: '100%', height: '100%' }}>
                <Term
                  key={uid}
                  ref={`term-${uid}`}
                  cols={this.state.cols}
                  rows={this.state.rows}
                  customCSS={config.termCSS}
                  fontSize={this.state.fontSize}
                  cursorColor={config.cursorColor}
                  fontFamily={config.fontFamily}
                  backgroundColor={config.backgroundColor}
                  colors={config.colors}
                  url={this.state.urls[uid]}
                  onResize={this.onResize}
                  onTitle={this.setTitle.bind(this, uid)}
                  onData={this.write.bind(this, uid)}
                  onURL={this.onURL.bind(this, uid)}
                  onURLAbort={this.closeBrowser}
                  />
              </div>;
            })
        }</div>
      </div>
      <div className={classes('resize-indicator', { showing: this.state.resizeIndicatorShowing })}>
        {this.state.fontSizeIndicatorShowing && <div>{ this.state.fontSize }px</div>}
        <div>{ this.state.cols }x{ this.state.rows }</div>
      </div>
      <div className={classes('update-indicator', { showing: null !== this.state.updateVersion && !this.state.dismissedUpdate })}>
        Version <b>{ this.state.updateVersion }</b> ready.
        {this.state.updateNote ? ` ${this.state.updateNote}. ` : ' '}
        <a href='' onClick={this.quitAndInstall}>Restart</a>
        to apply <span className='close' onClick={this.dismissUpdate}>[x]</span>
      </div>
      <style dangerouslySetInnerHTML={{ __html: Array.from(css || '').join('\n') }} />
    </div>;
  }

  dismissUpdate () {
    this.setState({ dismissedUpdate: true });
  }

  quitAndInstall (ev) {
    ev.preventDefault();
    this.rpc.emit('quit and install');
  }

  closeUpdateIndicator () {
    // @TODO
  }

  openExternal (ev) {
    ev.preventDefault();
    this.rpc.emit('open external', { url: ev.target.href });
  }

  requestTab () {
    // we send the hterm default size
    this.rpc.emit('new', { cols: this.state.cols, rows: this.state.rows });
  }

  closeActiveTab () {
    this.closeTab(this.state.active);
  }

  closeTab (id) {
    if (this.state.sessions.length) {
      const uid = this.state.sessions[id];
      this.rpc.emit('exit', { uid });
      this.onSessionExit({ uid });
    }
  }

  closeBrowser () {
    const uid = this.state.sessions[this.state.active];
    if (this.state.urls[uid]) {
      const urls = Object.assign({}, this.state.urls);
      delete urls[uid];
      this.setState({ urls });
    }
  }

  write (uid, data) {
    this.rpc.emit('data', { uid, data });
  }

  onURL (uid, url) {
    const urls = Object.assign({}, this.state.urls, { [uid]: url });
    this.setState({ urls });
  }

  onRemoteTitle ({ uid, title }) {
    this.setTitle(uid, title);
  }

  setTitle (uid, title) {
    const { titles: _titles } = this.state;
    const titles = Object.assign({}, _titles, { [uid]: title });
    this.setState({ titles });
  }

  onActive (uid) {
    const i = this.state.sessions.indexOf(uid);

    // we ignore activity markers all the way
    // up to the tab's been active
    const wasActive = this.tabWasActive[uid];
    if (!wasActive) {
      console.log('ignoring active, tab has not been focused', uid);
      this.tabWasActive[uid] = true;
      return;
    }

    if (this.state.active !== i && !~this.state.activeMarkers.indexOf(i)) {
      const activeMarkers = this.state.activeMarkers.slice();
      activeMarkers.push(i);
      this.setState({ activeMarkers });
    }
  }

  shouldComponentUpdate (nextProps, nextState) {
    if (this.state.active !== nextState.active) {
      const curUid = this.state.sessions[this.state.active];
      // make sure that the blurred uid has not been
      // optimistically removed
      if (curUid && ~nextState.sessions.indexOf(curUid)) {
        this.rpc.emit('blur', { uid: curUid });
      }
      const nextUid = nextState.sessions[nextState.active];
      this.rpc.emit('focus', { uid: nextUid });
      this.shouldInitKeys = true;
    } else {
      this.shouldInitKeys = false;
    }

    return shallowCompare(this, nextProps, nextState);
  }

  componentDidMount () {
    // open a new tab upon mounting
    this.rpc.once('ready', () => this.requestTab());

    this.rpc.on('new session', ({ uid }) => {
      const { sessions: _sessions } = this.state;
      const sessions = _sessions.concat(uid);
      const state = { sessions };
      state.active = sessions.length - 1;
      this.setState(state, () => {
        if (this.state.sessions.length && !this.init) {
          this.rpc.emit('init');
          this.init = true;
        }
      });
    });

    this.rpc.on('clear', this.clearCurrentTerm.bind(this));
    this.rpc.on('exit', this.onSessionExit.bind(this));

    this.rpc.on('data', ({ uid, data }) => {
      if (this.ignoreActivity) {
        // we ignore activity for up to 300ms after triggering
{'fraction_non_alphanumeric': 0.14696802003844586, 'fraction_numerical': 0.001631036290557465, 'mean_word_length': 2.450854271356784, 'pattern_counts': {'":': 0, '<': 57, '<?xml version=': 0, '>': 69, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4775899', 'n_tokens_mistral': 5293, 'n_tokens_neox': 5179, 'n_words': 1054}