"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;
exports.getSelectionText = getSelectionText;
var _uuid = require("uuid");
var _react = _interopRequireDefault(require("react"));
var _SplitInjector = _interopRequireDefault(require("./SplitInjector"));
var _core = require("@kui-shell/core");
var _Block = _interopRequireDefault(require("./Block"));
var _getSize = _interopRequireDefault(require("./getSize"));
var _Snapshot = require("./Snapshot");
var _SplitHeader = _interopRequireDefault(require("./SplitHeader"));
var _ClipboardTransfer = require("./ClipboardTransfer");
var _BlockModel = require("./Block/BlockModel");
var _visible = _interopRequireDefault(require("./visible"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/*
 * Copyright 2020 The Kubernetes Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
var __awaiter = void 0 && (void 0).__awaiter || function (thisArg, _arguments, P, generator) {
  function adopt(value) {
    return value instanceof P ? value : new P(function (resolve) {
      resolve(value);
    });
  }
  return new (P || (P = Promise))(function (resolve, reject) {
    function fulfilled(value) {
      try {
        step(generator.next(value));
      } catch (e) {
        reject(e);
      }
    }
    function rejected(value) {
      try {
        step(generator["throw"](value));
      } catch (e) {
        reject(e);
      }
    }
    function step(result) {
      result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
    }
    step((generator = generator.apply(thisArg, _arguments || [])).next());
  });
};
const strings = (0, _core.i18n)('plugin-client-common');
/** Hard limit on the number of Terminal splits */
const MAX_TERMINALS = 5;
/** Remember the welcomed count in localStorage, using this key */
const NUM_WELCOMED = 'kui-shell.org/ScrollableTerminal/NumWelcomed';
/**
 * Seed UUID for v5 sequences. THe particular value here does not
 * matter, but if you change this, you will invalidate scrollback
 * history for all users. Only change this if that is what you
 * intended.
 */
const UUID_NAMESPACE = '5a04bbd1-fb7e-44f7-a5ea-16c0331772e3';
/** get the selected texts in window */
function getSelectionText() {
  let text = '';
  if (window.getSelection) {
    text = window.getSelection().toString();
  }
  return text;
}
/** Type guard that the given evt.relatedTarget is an HTML Element */
function isElement(target) {
  return target && target.tagName !== undefined;
}
class ScrollableTerminal extends _react.default.PureComponent {
  constructor(props) {
    super(props);
    /**
     * For UUID generation, keep a running counter of the total number
     * of scrollbacks created. This index will be fed to the uuid.v5()
     * generator. Note: we can't use the scrollback's index in the
     * current split array, because the user might reorder them, e.g. by
     * creating two splits, then deleting the *first*, then creating
     * (again) a second split. What was the second split is now in the
     * first position, and the new split will have the same ID as the
     * (now) first-ordinal split. Thus, instead we keep a running
     * counter. This will preserve cross-session UUID sequence equality,
     * while avoiding the reordering dilemma.
     */
    this.scrollbackCounter = 0;
    this.cleaners = [];
    /**
     * This is the `inject` handler for the `SplitInjector`
     * interface. Injects the given React `node` into the given
     * `position`. Uses `uuid` to determine whether the a prior version
     * of the node already exists in that position.
     */
    this.inject = splitSpecs => setTimeout(() => splitSpecs.forEach(this.injectOne));
    this.injectOne = ({
      uuid,
      node,
      position,
      count,
      opts: {
        maximized,
        hasActiveInput,
        inverseColors
      }
    }) => {
      const split = (position !== 'default' ? this.state.splits.find(_ => _.position === position) : this.state.splits.filter(_ => _.position === 'default')[count]) || this.makePositionedSplit(position, {
        createdBy: 'kui',
        hasActiveInput,
        maximized
      });
      if (split) {
        this.splice(split.uuid, curState => {
          // have we already inserted the given node?
          const execUUID = `${uuid}-${position}`;
          const alreadyIdx = curState.blocks.findIndex(_ => (0, _BlockModel.isAnnouncement)(_) && _.execUUID === execUUID);
          // in either case, we will use this new BlockModel
          const newBlock = (0, _BlockModel.Announcement)({
            react: node
          }, execUUID, maximized);
          if (alreadyIdx >= 0) {
            // yup! so splice out with the old, and in with the new!
            return {
              blocks: [...curState.blocks.slice(0, alreadyIdx), newBlock, ...curState.blocks.slice(alreadyIdx + 1) // skip over the existing version...
              ]
            };
          } else {
            // then we splice in the new
            const insertIdx = (0, _BlockModel.isActive)(split.blocks[split.blocks.length - 1]) ? split.blocks.length - 1 : split.blocks.length;
            return {
              maximized: maximized === true || curState.maximized,
              inverseColors: inverseColors === true || curState.inverseColors,
              blocks: [...curState.blocks.slice(0, insertIdx), newBlock, ...curState.blocks.slice(insertIdx)]
            };
          }
        }, {
          focus: !!hasActiveInput
        });
      }
    };
    /** This is the `modify` handler for the `SplitInjector`
     * interface. Modifies the properties of an existing split as
     * specified by `sbuuid`, and then returns the given `node`
     * unchanged. */
    this.modify = (sbuuid, node, {
      hasActiveInput,
      inverseColors,
      maximized = false
    }) => {
      setTimeout(() => this.setState(curState => {
        const sbidx = this.findSplit(curState, sbuuid);
        if (sbidx < 0) {
          return null;
        } else {
          const splits = curState.splits.slice();
          if (typeof maximized === 'boolean') {
            splits[sbidx].maximized = maximized;
          }
          if (typeof inverseColors === 'boolean') {
            splits[sbidx].inverseColors = inverseColors;
          }
          if (typeof hasActiveInput === 'boolean') {
            splits[sbidx].hasActiveInput = hasActiveInput;
          }
          return {
            splits
          };
        }
      }));
      return node;
    };
    this.mounted = false;
    this.initClipboardEvents();
    this.state = this.initialState();
    this.initSnapshotEvents();
  }
  initialState() {
    const splits = [this.scrollbackWithWelcome()];
    return {
      focusedIdx: 0,
      splits
    };
  }
  /** Listen for copy and paste (TODO: cut) events to facilitate moving blocks */
  initClipboardEvents() {
    const paste = _ClipboardTransfer.onPaste.bind(this);
    document.addEventListener('paste', paste);
    this.cleaners.push(() => document.removeEventListener('paste', paste));
    const copy = _ClipboardTransfer.onCopy.bind(this);
    document.addEventListener('copy', copy);
    this.cleaners.push(() => document.removeEventListener('copy', copy));
    const cut = _ClipboardTransfer.onCut.bind(this);
    document.addEventListener('cut', cut);
    this.cleaners.push(() => document.removeEventListener('cut', cut));
  }
  /** Listen for snapshot request events */
  initSnapshotEvents() {
    const onSnapshot = evt => __awaiter(this, void 0, void 0, function* () {
      if (evt.execUUID && evt.cb) {
        // capture just one block
        this.state.splits.forEach(split => {
          const block = split.blocks.find(_ => (0, _BlockModel.hasUUID)(_) && _.execUUID === evt.execUUID);
          if (block && (0, _BlockModel.isFinished)(block)) {
            const replacer = (key, value) => {
              if (key === 'tab') {
                return undefined;
              } else if (key === 'block') {
                return undefined;
              } else {
                return value;
              }
            };
            evt.cb(Buffer.from(JSON.stringify((0, _Snapshot.snapshot)(block), replacer)));
          } else {
            evt.cb(null);
          }
        });
      } else {
        // request that all commentary in this tab save themselves
        yield Promise.all(this.state.splits.map(({
          blocks
        }) => __awaiter(this, void 0, void 0, function* () {
          yield Promise.all(blocks.map(block => __awaiter(this, void 0, void 0, function* () {
            if ((0, _BlockModel.isWithCompleteEvent)(block) && (0, _core.isCommentaryResponse)(block.completeEvent.response)) {
              yield _core.Events.eventChannelUnsafe.emit(`/kui/snapshot/request/${block.execUUID}`);
            }
          })));
        })));
      }
    });
    _core.Events.eventBus.onSnapshotRequest(onSnapshot, (0, _core.getTabId)(this.props.tab));
    this.cleaners.push(() => _core.Events.eventBus.offSnapshotRequest(onSnapshot, (0, _core.getTabId)(this.props.tab)));
  }
  /** add welcome blocks at the top of scrollback */
  scrollbackWithWelcome() {
    const scrollback = this.scrollback(undefined, {
      createdBy: 'default'
    });
    const welcomeMax = this.props.config.showWelcomeMax;
    if (this.props.sessionInit === 'Done' && this.props.config.loadingDone && welcomeMax !== undefined) {
      const welcomed = parseInt(localStorage.getItem(NUM_WELCOMED)) || 0;
      if ((welcomeMax === -1 || welcomed < welcomeMax) && this.props.config.loadingDone) {
        const announcement = this.props.config.loadingDone(this.props.tab.REPL);
        if (announcement) {
          _core.Events.eventBus.emitCommandComplete({
            tab: this.props.tab,
            historyIdx: -1,
            command: 'welcome',
            completeTime: Date.now(),
            argvNoOptions: ['welcome'],
            parsedOptions: {},
            execOptions: {},
            pipeStages: {
              stages: [['welcome']]
            },
            execUUID: '',
            execType: _core.ExecType.Nested,
            cancelled: false,
            echo: true,
            evaluatorOptions: {},
            response: {
              react: announcement
            },
            responseType: 'ScalarResponse'
          });
          const welcomeBlocks = !announcement ? [] : [(0, _BlockModel.Announcement)({
            react: this.props.config.loadingDone && _react.default.createElement("div", {
              className: "kui--repl-message kui--session-init-done"
            }, announcement)
          })];
          scrollback.blocks = welcomeBlocks.concat(scrollback.blocks);
          if (welcomeMax !== -1) {
            localStorage.setItem(NUM_WELCOMED, (welcomed + 1).toString());
          }
        }
      }
    }
    return scrollback;
  }
  /** Create a split with the given position and coloration */
  makePositionedSplit(position, opts = {}, inverseColors = position === 'left-strip') {
    const split = this.scrollback(undefined, Object.assign({}, opts, {
      position,
      inverseColors
    }));
    this.setState(curState => ({
      splits: curState.splits.concat([split])
    }));
    if (position === 'left-strip') {
      this.props.willToggleLeftStripMode();
    } else if (position === 'right-strip') {
      this.props.willToggleRightStripMode();
    }
    return split;
  }
  allocateUUIDForScrollback() {
    // this.props.uuid is the uuid for the whole tab
    // on top of that, we allocate a "v5" uuid for this scrollback
    const sbidx = this.scrollbackCounter++;
    const tabPart = this.props.uuid;
    const scrollbackPart = (0, _uuid.v5)(sbidx.toString(), UUID_NAMESPACE);
    return `${tabPart}_${scrollbackPart}`;
  }
  /** Restore from localStorage for a given tab UUID */
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  restoreBlocks(sbuuid) {
    // TODO:
    /* return History(sbuuid)
      .slice(-20)
      .filter(_ => _.execUUID !== undefined && isScalarResponse(_.response) && _.isCurrentlyShown)
      .map(_ => Finished(Processing(Active(), _.raw, _.execUUID), _.response as ScalarResponse, false, _.historyIdx)) */
    return [];
  }
  /** @return the number of splits with `position=default` */
  numDefaultSplits() {
    return this.state.splits.reduce((N, _) => N += _.position === 'default' ? 1 : 0, 0);
  }
  scrollback(sbuuid = this.allocateUUIDForScrollback(), opts = {}) {
    const state = {
      uuid: sbuuid,
      cleaners: [],
      inverseColors: opts.inverseColors,
      blocks: this.restoreBlocks(sbuuid).concat([(0, _BlockModel.Active)()]),
      remove: undefined,
      clear: undefined,
      invert: undefined,
      onClick: undefined,
      onMouseDown: undefined,
      onFocus: undefined,
      onOutputRender: undefined,
      setActiveBlock: undefined,
      willFocusBlock: undefined,
      willRemoveBlock: undefined,
      willUpdateCommand: undefined,
      tabRefFor: undefined,
      scrollableRef: undefined,
      hasActiveInput: opts.hasActiveInput,
      createdBy: opts.createdBy || 'user',
      position: opts.position || 'default',
      willToggleSplitPosition: undefined
    };
    const getBlockIndexFromEvent = (evt, doNotComplain = false) => {
      const idxAttr = evt.currentTarget.getAttribute('data-input-count');
      if (!idxAttr) {
        if (!doNotComplain) {
          console.error('Failed to focus, due to missing data-input-count attribute', evt.currentTarget, evt.target);
        }
        return -1;
      } else {
        return parseInt(idxAttr, 10);
      }
    };
    state.remove = () => this.removeSplit(sbuuid);
    state.clear = () => this.clear(sbuuid);
    state.invert = () => this.invert(sbuuid);
    /**
     * For inline-input clients, we want empty-space clicks to steal
     * focus away from the other split, if we are switching splits;
     * otherwise, we don't want empty-space clicks in the focused
     * split to steal focus away from that split's active input
     */
    state.onClick = () => {
      if (getSelectionText().length === 0) {
        this.doFocus(state);
      }
    };
    /**
     * For bottom-input clients, we don't want empty-space clicks to
     * steal focus away from the bottom input
     */
    state.onMouseDown = evt => {
      evt.preventDefault();
    };
    /** Output.tsx finished rendering something */
    state.onOutputRender = () => {
      if (!this.props.noActiveInput) {
        // if we are using inline input, then scroll to the bottom
        // whenever an output is rendered in this split
        // problematic, see https://github.com/kubernetes-sigs/kui/issues/8174
        // setTimeout(() => state.facade.scrollToBottom())
      }
    };
    /** Update the active block */
    state.setActiveBlock = c => {
      if (c && c.props && c.props.model && (0, _BlockModel.isActive)(c.props.model)) {
        const split = this.state.splits[this.findSplit(this.state, sbuuid)];
        split._activeBlock = c;
      }
    };
    /**
     * User clicked to focus a block.
     *
     * @param uuid scrollback UUID
     * @param focusedBlockIdx index into the ScrollbackState of that scrollback
     *
     */
    state.willFocusBlock = evt => {
      const sbidx = this.findSplit(this.state, sbuuid);
      if (sbidx >= 0) {
        const idx = getBlockIndexFromEvent(evt);
        evt.stopPropagation();
        const split = typeof sbuuid === 'string' ? this.state.splits[this.findSplit(this.state, sbuuid)] : sbuuid;
        this.setFocusOnScrollback(split, idx);
      }
    };
    state.willUpdateCommand = (idx, command) => {
      return this.splice(sbuuid, curState => {
        const block = Object.assign({}, curState.blocks[idx]);
        if ((0, _BlockModel.hasCommand)(block)) {
          block.command = command;
        }
        if ((0, _BlockModel.hasStartEvent)(block)) {
          block.startEvent.command = command;
        }
        if ((0, _BlockModel.isWithCompleteEvent)(block)) {
          block.completeEvent.command = command;
        }
        return {
          blocks: curState.blocks.slice(0, idx).concat([block]).concat(curState.blocks.slice(idx + 1))
        };
      });
    };
    /** The focus event is coming
     * from the browser, e.g. when the user hits tab to
     * navigate between the <li> of each block */
    state.onFocus = evt => {
      const sbidx = this.findSplit(this.state, sbuuid);
      if (sbidx >= 0) {
        const scrollback = this.state.splits[sbidx];
        const idx = getBlockIndexFromEvent(evt);
        if (isElement(evt.relatedTarget) && /li/i.test(evt.relatedTarget.tagName) && /li/i.test(evt.target.tagName) && scrollback.focusedBlockIdx !== idx) {
          scrollback.willFocusBlock(evt);
        }
      }
    };
    /** remove the block at the given index */
    state.willRemoveBlock = (evt, idx = getBlockIndexFromEvent(evt)) => {
      if (idx >= 0) {
        this.splice(sbuuid, curState => {
          this.removeWatchableBlock(curState.blocks[idx]);
          const blocks = curState.blocks.slice(0, idx).concat(curState.blocks.slice(idx + 1)).concat(this.hasActiveBlock(curState) ? [] : [(0, _BlockModel.Active)()]); // plus a new block, if needed
          const focusedBlockIdx = curState.focusedBlockIdx >= idx ? this.findActiveBlock({
            blocks
          }) : curState.focusedBlockIdx;
          return {
            blocks,
            focusedBlockIdx
          };
        });
      }
    };
    /** Reference for the scrollable part of the Split; helpful for scrollToTop/Bottom */
    state.scrollableRef = ref => {
      if (ref) {
        state.facade.scrollToTop = () => ref.scrollTop = 0;
        /**
         * If given the optional parameter, only scroll into view if the
         * specified block (identified by its execUUID) is the last block in
         * this tab
         *
         */
        state.facade.scrollToBottom = execUUID => {
          const sbidx = this.findSplit(this.state, sbuuid);
          if (sbidx >= 0) {
            const {
              blocks
            } = this.state.splits[sbidx];
            const lastBlock = blocks[blocks.length - 1];
            if (!execUUID || (0, _BlockModel.hasUUID)(lastBlock) && lastBlock.execUUID === execUUID) {
              ref.scrollTop = ref.scrollHeight;
            }
          }
        };
        state.facade.show = sel => {
          const elt = this.props.tab.querySelector(sel);
          if (elt) {
            return elt.scrollIntoView();
          }
        };
      }
    };
    /** Reference for the entire Split */
    state.tabRefFor = ref => {
      const scrollback = state;
      if (ref) {
        ref['facade'] = scrollback.facade;
        scrollback.facade.getSize = _getSize.default.bind(ref);
        scrollback.facade.splitCount = () => this.state.splits.length;
        scrollback.facade.hasSideBySideTerminals = () => this.numDefaultSplits() > 1;
        scrollback.facade.addTopClass = this.props.tab.addClass;
        scrollback.facade.addClass = cls => {
          ref.classList.add(cls);
        };
        scrollback.facade.removeTopClass = this.props.tab.removeClass;
        scrollback.facade.removeClass = cls => {
          ref.classList.remove(cls);
        };
      }
    };
    state.willToggleSplitPosition = () => {
      const sbidx = this.findSplit(this.state, sbuuid);
      if (sbidx >= 0) {
        const scrollback = this.state.splits[sbidx];
        if (scrollback.position === 'default') {
          if (this.props.hasRightStrip) {
            // this split is default, and we have a right split; make this a left split
            scrollback.position = 'left-strip';
            this.props.willToggleLeftStripMode();
          } else {
            // this split is default, and we don't have a right split; make this a right split
            scrollback.position = 'right-strip';
            this.props.willToggleRightStripMode();
          }
        } else if (scrollback.position === 'right-strip') {
          if (this.props.hasLeftStrip) {
            // this split is right, and we have a left split; revert this to default
            scrollback.position = 'default';
            this.props.willToggleRightStripMode();
          } else {
            // this split is right, and we don't have a left split; make this a left split
            scrollback.position = 'left-strip';
            this.props.willToggleLeftStripMode();
            this.props.willToggleRightStripMode();
          }
        } else {
          // this split is left; always return to default
          this.props.willToggleLeftStripMode();
          scrollback.position = 'default';
        }
      }
    };
    // prefetch command history; this helps with master history
    (0, _core.History)(sbuuid);
    // associate a tab facade with the split
    this.tabFor(state);
    const onTabCloseRequest = () => __awaiter(this, void 0, void 0, function* () {
      // async, to allow for e.g. command completion events to finish
      // propagating to the split before we remove it
      setTimeout(() => this.removeSplit(sbuuid));
    });
    _core.Events.eventBus.onceWithTabId('/tab/close/request', sbuuid, onTabCloseRequest);
    state.cleaners.push(() => _core.Events.eventBus.offWithTabId('/tab/close/request', sbuuid, onTabCloseRequest));
    if (opts.cmdline) {
      setTimeout(() => {
        const execUUID = this.execUUIDOfLastActiveBlock(state);
        state.facade.REPL.pexec(opts.cmdline, {
          masquerade: opts.masquerade,
          data: opts.data,
          execUUID
        });
      });
    }
    return this.initEvents(state);
  }
  execUUIDOfLastActiveBlock(sb) {
    for (let idx = sb.blocks.length - 1; idx >= 0; idx--) {
      const block = sb.blocks[idx];
      if ((0, _BlockModel.isActive)(block)) {
        return block.execUUID;
      }
    }
  }
  /** @return a reasonable default split */
  findMainSplit(excludedIndex) {
    return this.state.splits.slice().reverse().find((split, idx) => {
      const originalIdx = this.state.splits.length - idx - 1;
      return split && this.isOkSplitForContent(originalIdx) && (excludedIndex === undefined || originalIdx !== excludedIndex);
    });
  }
  /** @return a reasonable default split */
  get current() {
    return this.findMainSplit();
  }
  /** @return the uuid of a reasonable default split */
  get currentUUID() {
    const cur = this.current;
    return cur ? cur.uuid : undefined;
  }
  /** Invert colors of the given Scrollback uuid */
  invert(uuid) {
    return this.splice(uuid, sbState => Object.assign({}, sbState, {
      inverseColors: !sbState.inverseColors
    }));
  }
  /** Clear Terminal; TODO: also clear persisted state, when we have it */
  clear(uuid) {
    this.setState(curState => {
      const focusedIdx = this.findSplit(curState, uuid);
      return Object.assign({
        focusedIdx: focusedIdx < 0 ? curState.focusedIdx : focusedIdx
      }, this.spliceMutate(curState, uuid, scrollback => {
        const residualBlocks = scrollback.blocks.filter(_ => {
          this.removeWatchableBlock(_);
          return false;
        }).concat([(0, _BlockModel.Active)(scrollback._activeBlock ? scrollback._activeBlock.inputValue() : '')]);
        return Object.assign(scrollback, {
          blocks: residualBlocks,
          focusedBlockIdx: residualBlocks.length - 1
        });
      }));
    });
    if (this.props.onClear) {
      setTimeout(() => this.props.onClear());
    }
  }
  /** Some splits may not be ideal receptacles for arbitrary content */
  isOkSplitForContent(idx) {
    return this.state.splits[idx].position === 'default';
  }
  /**
   * We want to direct the command execution UI to a default-position
   * terminal.
   *
   * @return the sbuuid of a default-position split
   *
   */
  redirectToPlainSplitIfNeeded(sbuuid, {
    execType
  }) {
    if (execType === _core.ExecType.ClickHandler) {
      // <-- this is a click handler event
      const idx = this.findSplit(this.state, sbuuid);
      // note: idx may be < 0 if we are executing a command in-flight,
      // e.g. executing a command in another split
      if (idx >= 0 && !this.isOkSplitForContent(idx)) {
        // then this is not a preferred target for displaying content
        const plainSplit = this.findMainSplit();
        if (plainSplit) {
          // <-- we found a plain split!
          return plainSplit.uuid;
        }
      }
    }
    // otherwise, we are stuck with what we have
    return sbuuid;
  }
  /** the REPL started executing a command */
  onExecStart(uuid = this.currentUUID, asReplay, event, _insertIdx) {
    if (event.execOptions && event.execOptions.echo === false) {
      return;
    }
    const processing = block => {
      return [(0, _BlockModel.Processing)(block, event, event.evaluatorOptions.isExperimental, asReplay || undefined)];
    };
    // uuid might be undefined if the split is going away
    if (uuid) {
      uuid = this.redirectToPlainSplitIfNeeded(uuid, event);
      this.splice(uuid, curState => {
        const idx = curState.blocks.length - 1;
        const insertIdx = typeof _insertIdx === 'number' ? _insertIdx : event.execOptions && event.execOptions.insertIdx;
        if (insertIdx !== undefined) {
          // we were asked to splice in the startEvent at a particular index
          return {
            focusedBlockIdx: insertIdx,
            blocks: (insertIdx === 0 ? [] : curState.blocks.slice(0, insertIdx)).concat([(0, _BlockModel.Processing)((0, _BlockModel.Active)(), event, event.evaluatorOptions.isExperimental)]).concat(curState.blocks.slice(insertIdx))
          };
        }
        const rerunIdx = event.execType === _core.ExecType.Rerun ? curState.blocks.findIndex(_ => {
          return (0, _BlockModel.hasOriginalUUID)(_) && _.originalExecUUID === event.execUUID || (0, _BlockModel.hasUUID)(_) && _.execUUID === event.execUUID;
        }) : -1;
        if (event.execType === _core.ExecType.Rerun) {
          if (rerunIdx < 0) {
            console.error('Cannot find block for rerun', event.execType === _core.ExecType.Rerun, event, curState.blocks.map(_ => {
              return (0, _BlockModel.hasOriginalUUID)(_) && _.originalExecUUID || (0, _BlockModel.hasUUID)(_) && _.execUUID;
            }), curState.blocks);
          } else if ((0, _BlockModel.isBeingRerun)(curState.blocks[rerunIdx])) {
            console.error('Block already being rerun', event);
          }
        }
        // we are now pre-assigning a block's execUUID up front (see
        // BlockModel.active); this may be falsely detected as a
        // rerun, hence the guard here
        if (rerunIdx >= 0 && !(0, _BlockModel.isActive)(curState.blocks[rerunIdx])) {
          const block = curState.blocks[rerunIdx];
          this.removeWatchableBlock(block);
          // The use case here is that the user clicked the Rerun
          // button in the UI or clicked on an Input and hit Enter. In
          // either case, the command execution will reuse the
          // execUUID, hence the `findIndex` logic just above, which
          // scans the blocks for an existing execUUID. So: we
          // Transform the rerun block to Processing
          return {
            blocks: curState.blocks.slice(0, rerunIdx) // everything before
            .concat((0, _BlockModel.isRerunable)(block) ? [(0, _BlockModel.Rerun)(block, event)] : []).concat(curState.blocks.slice(rerunIdx + 1)) // everything after
          };
        } else if ((0, _BlockModel.isProcessing)(curState.blocks[idx])) {
          // the last block is Processing; this can handle if the user
          // causes a pexec to be sent to a split that is already
          // processing
          return {
            blocks: curState.blocks.concat(processing((0, _BlockModel.Active)()))
          };
        } else {
          // Transform the last block to Processing
          const blocks = curState.blocks.slice(0, idx).concat(processing(curState.blocks[idx]));
          return {
            blocks
          };
        }
      });
    }
  }
  /** Format a MarkdownResponse */
  markdown(key) {
    return {
      content: strings(key),
      contentType: 'text/markdown'
    };
  }
  /** the REPL finished executing a command */
  onExecEnd(uuid = this.currentUUID, asReplay, event, insertIdx) {
    return __awaiter(this, void 0, void 0, function* () {
      if (!uuid) return;else {
        uuid = this.redirectToPlainSplitIfNeeded(uuid, event);
      }
      if ((0, _core.isTabLayoutModificationResponse)(event.response)) {
        const updatedResponse = yield this.onTabLayoutModificationRequest(event.response, uuid);
        if (updatedResponse) {
          event.response = updatedResponse;
        }
      }
      if (event.execOptions && event.execOptions.echo === false) return;
      // note: even if the command registration asked for
      // `outputOnly`, we ignore that if the response is a plain
      // `true`; e.g. the `commentary` controller uses this to
      // indicate an empty comment
      const outputOnly = event.evaluatorOptions && event.evaluatorOptions.outputOnly && event.response !== true;
      const findBlock = blocks => {
        return blocks.findIndex(_ => {
          return (0, _BlockModel.isBeingRerun)(_) && _.originalExecUUID === event.execUUID || ((0, _BlockModel.isBeingRerun)(_) || (0, _BlockModel.isProcessing)(_)) && _.execUUID === event.execUUID;
        });
      };
      // special case: replace all current content?
      const replace = event.response && (0, _core.isCommentaryResponse)(event.response) && event.response.props.replace;
      if (replace && !event.cancelled) {
        const state = this.initialState();
        const split = this.state.splits[this.findSplit(this.state, uuid)];
        const inProcess = split.blocks[findBlock(split.blocks)];
        if ((0, _BlockModel.isProcessing)(inProcess) || (0, _BlockModel.isBeingRerun)(inProcess)) {
          const finishedBlock = (0, _BlockModel.Finished)(inProcess, event, outputOnly, asReplay || undefined);
          state.splits[0].blocks.splice(0, 0, finishedBlock);
          this.setState(state);
          this.props.resetSplitLayout();
          return;
        }
      }
      this.splice(uuid, curState => {
        const inProcessIdx = findBlock(curState.blocks);
        if (inProcessIdx >= 0) {
          const inProcess = curState.blocks[inProcessIdx];
          if ((0, _BlockModel.isProcessing)(inProcess) || (0, _BlockModel.isBeingRerun)(inProcess)) {
            const finishedBlock = (0, _BlockModel.Finished)(inProcess, event, outputOnly, asReplay || undefined);
            try {
              const blocks = (replace ? [finishedBlock] : curState.blocks.slice(0, inProcessIdx) // everything before
              .concat([finishedBlock]) // mark as finished
              .concat(curState.blocks.slice(inProcessIdx + 1)) // everything after
              ).concat(!(0, _BlockModel.isBeingRerun)(inProcess) && inProcessIdx === curState.blocks.length - 1 ? [(0, _BlockModel.Active)()] : []); // plus a new block!
              return {
                focusedBlockIdx: insertIdx === undefined ? blocks.length - 1 : insertIdx,
                blocks
              };
            } catch (err) {
              console.error('error updating state', err);
              throw err;
            }
          } else {
            console.error('invalid state: got a command completion event for a block that is not processing', event);
          }
        } else if (event.cancelled) {
          // we get here if the user just types ctrl+c without having executed any command. add a new block if needed!
          const inProcessIdx = this.hasActiveBlock(curState) ? this.findActiveBlock(curState) : curState.blocks.length - 1;
          const inProcess = curState.blocks[inProcessIdx];
          const blocks = curState.blocks.slice(0, inProcessIdx).concat([(0, _BlockModel.Cancelled)(inProcess, event.command, event)]) // mark as cancelled
          .concat(curState.blocks.slice(inProcessIdx + 1)).concat(inProcessIdx === curState.blocks.length - 1 ? [(0, _BlockModel.Active)()] : []); // plus a new block if needed
          return {
            focusedBlockIdx: blocks.length - 1,
            blocks
          };
        } else {
          console.error('invalid state: got a command completion event, but never got command start event', event);
        }
      });
    });
  }
  /** Only set focus if we haven't, yet */
  doFocusIfNeeded() {
    if (this.state.focusedIdx < 0) {
      this.doFocus(this.current);
    } else {
      this.doFocus(this.state.splits[this.state.focusedIdx]);
    }
  }
  doFocus(scrollback) {
    if (this._focusDebouncer) {
      clearTimeout(this._focusDebouncer);
      this._focusDebouncer = undefined;
    }
    const ourDebouncer = setTimeout(() => {
      if (this._focusDebouncer !== ourDebouncer) {
        return;
      } else {
        this._focusDebouncer = undefined;
      }
      const {
        _activeBlock
      } = scrollback;
      if (_activeBlock) {
        if (_activeBlock.state._block && (0, _visible.default)(_activeBlock.state._block)) {
          // re: isInViewport, see https://github.com/IBM/kui/issues/4739
          _activeBlock.doFocus();
        }
      } else {
        // a bit of a data abstraction violation; we should figure out how to solve this better
        // see https://github.com/IBM/kui/issues/3945
        const xterm = document.querySelector('textarea.xterm-helper-textarea');
        if (xterm) {
          xterm.focus();
        }
      }
      this.setFocusOnScrollback(scrollback);
    }, 10);
    this._focusDebouncer = ourDebouncer;
  }
  setFocusOnScrollback(scrollback, focusedBlockIdx) {
    return __awaiter(this, void 0, void 0, function* () {
      if (!this.mounted) {
        return;
      }
      const focusedIdx = this.findSplit(this.state, scrollback.uuid);
      if (this.state.focusedIdx !== focusedIdx) {
        const currentSplit = this.state.splits[this.state.focusedIdx];
        if (currentSplit && currentSplit.facade) {
          yield currentSplit.facade.state.switchTo(scrollback.facade.state);
        } else {
          scrollback.facade.state.restore();
        }
        _core.Events.eventBus.emitSplitSwitch(scrollback.facade);
      }
      this.setState(curState => {
        return Object.assign(this.spliceMutate(curState, scrollback.uuid, ({
          blocks
        }) => ({
          blocks,
          focusedBlockIdx: focusedBlockIdx !== undefined && focusedBlockIdx >= 0 ? focusedBlockIdx : blocks.length - 1
        })), {
          focusedIdx
        });
      });
    });
  }
  /**
   * Handle CommandStart/Complete events directed at the given
   * scrollback region.
   *
   */
  hookIntoREPL(state) {
    const onStartForSplitFromUser = this.onExecStart.bind(this, state.uuid, false);
    const onStartForSplitFromReplay = this.onExecStart.bind(this, state.uuid, true);
    const onCompleteForSplitFromUser = this.onExecEnd.bind(this, state.uuid, false);
    const onCompleteForSplitFromReplay = this.onExecEnd.bind(this, state.uuid, true);
    _core.Events.eventBus.onCommandStart(state.uuid, onStartForSplitFromUser);
    _core.Events.eventBus.onCommandStart(state.uuid, onStartForSplitFromReplay, true);
    _core.Events.eventBus.onCommandComplete(state.uuid, onCompleteForSplitFromUser);
    _core.Events.eventBus.onCommandComplete(state.uuid, onCompleteForSplitFromReplay, true);
    state.cleaners.push(() => _core.Events.eventBus.offCommandStart(state.uuid, onStartForSplitFromUser));
    state.cleaners.push(() => _core.Events.eventBus.offCommandStart(state.uuid, onStartForSplitFromReplay));
    state.cleaners.push(() => _core.Events.eventBus.offCommandComplete(state.uuid, onCompleteForSplitFromUser));
    state.cleaners.push(() => _core.Events.eventBus.offCommandComplete(state.uuid, onCompleteForSplitFromReplay));
  }
  /**
   * Handle events directed at the given scrollback region.
   *
   */
  initEvents(state) {
    this.hookIntoREPL(state);
    const clear = this.clear.bind(this, state.uuid);
    _core.Events.eventChannelUnsafe.on(`/terminal/clear/${state.uuid}`, clear);
    state.cleaners.push(() => {
      _core.Events.eventChannelUnsafe.off(`/terminal/clear/${state.uuid}`, clear);
    });
    // terminate watchable jobs when the window is closed
    const termAll = this.terminateAllWatchables.bind(this);
    window.addEventListener('beforeunload', termAll);
    this.cleaners.push(() => window.removeEventListener('beforeunload', termAll));
    return state;
  }
  /** A controller has requested a tab layout modification */
  onTabLayoutModificationRequest(request, sbuuid) {
    if ((0, _core.isNewSplitRequest)(request)) {
      return this.onSplit(request, sbuuid);
    }
  }
  /** Swap the position of the two splits; a and b are 1-indexed from the controller */
  swapSplits(a, b) {
    const aIdx = a - 1; // 1-indexed from the controller
    const bIdx = b - 1; // 1-indexed from the controller
    if (!this.state.splits[aIdx]) {
      console.error(`Cannot find split index ${a}`, aIdx, this.state.splits);
      return new Error(`Cannot find split index ${a}`);
    } else if (!this.state.splits[bIdx]) {
      console.error(`Cannot find split index ${b}`, bIdx, this.state.splits);
      return new Error(`Cannot find split index ${b}`);
    } else {
      const firstIdx = Math.min(aIdx, bIdx);
      const secondIdx = Math.max(aIdx, bIdx);
      const splits = this.state.splits.slice(0, firstIdx).concat([this.state.splits[secondIdx]]).concat(this.state.splits.slice(firstIdx + 1, secondIdx)).concat([this.state.splits[firstIdx]]).concat(this.state.splits.slice(secondIdx + 1));
      this.setState({
        splits,
        focusedIdx: this.state.focusedIdx === aIdx ? bIdx : this.state.focusedIdx === bIdx ? aIdx : this.state.focusedIdx
      });
      return true;
    }
  }
  /** Split the view */
  onSplit(request, sbuuid) {
    return __awaiter(this, void 0, void 0, function* () {
      const nTerminals = this.state.splits.length;
      if (request.spec.options.swap) {
        // swap to splits
        return this.swapSplits(request.spec.options.swap[0], request.spec.options.swap[1]);
      } else if (request.spec.options.cmdline && (request.spec.options.if || request.spec.options.ifnot)) {
        const thisSplitIdx = this.findSplit(this.state, sbuuid);
        const thisSplit = this.state.splits[thisSplitIdx];
        const respIf = !request.spec.options.if ? true : yield thisSplit.facade.REPL.qexec(request.spec.options.if).catch(() => false);
        const respIfNot = !request.spec.options.ifnot ? true : !(yield thisSplit.facade.REPL.qexec(request.spec.options.ifnot).catch(() => true));
        if (!respIf || !respIfNot) {
          const {
            cmdline,
            data,
            masquerade
          } = request.spec.options;
          const mainSplit = this.findMainSplit(thisSplitIdx) || thisSplit;
          const execUUID = this.execUUIDOfLastActiveBlock(mainSplit);
          request.spec.options.cmdline = undefined; // null this out, since we got it!
          mainSplit.facade.REPL.pexec(cmdline, {
            data,
            masquerade,
            execUUID
          });
          return;
        }
      }
      if (nTerminals === MAX_TERMINALS) {
        return new Error(strings('No more splits allowed'));
      } else {
        const newScrollback = this.scrollback(undefined, request.spec.options);
        this.setState(({
          splits
        }) => {
          // this says: 1) place the split at the end; and 2) focus the
          // new split
          const insertIdx = request.spec.options && request.spec.options.index !== undefined ? request.spec.options.index : splits.length;
          const newFocusedIdx = insertIdx;
          const newSplits = splits.slice(0, insertIdx).concat(newScrollback).concat(splits.slice(insertIdx));
          const opts = {
            isSidecarNowHidden: false,
            isWidthConstrained: this.isWidthConstrained(newScrollback)
          };
          _core.Events.eventBus.emitTabLayoutChange(sbuuid, opts);
          _core.Events.eventBus.emitTabLayoutChange(this.props.tab.uuid, opts);
          return {
            focusedIdx: newFocusedIdx,
            splits: newSplits
          };
        });
        return request.spec.ok;
      }
    });
  }
  /** Detach hooks that might have been registered */
  uninitEvents() {
    // clean up per-split event handlers
    this.state.splits.forEach(({
      cleaners
    }) => {
      cleaners.forEach(cleaner => cleaner());
    });
    this.cleaners.forEach(cleaner => cleaner());
  }
  componentDidMount() {
    this.mounted = true;
    if (this.props.onReady) {
      this.props.onReady();
    }
  }
  componentWillUnmount() {
    this.mounted = false;
    this.uninitEvents();
    this.terminateAllWatchables();
  }
  /**
   * @return the index of the given scrollback, in the context of the
   * current (given) state
   *
   */
  findSplit(curState, uuid) {
    return curState.splits.findIndex(_ => _.uuid === uuid);
  }
  /**
   * @return the index of the given scrollback, in the context of the
   * current (given) state
   *
   */
  findAvailableSplit(curState, uuid) {
    const focusedIdx = this.findSplit(curState, uuid);
    const availableSplitIdx = focusedIdx !== -1 ? focusedIdx : 0;
    return availableSplitIdx;
  }
  /** If the block has watchable response, abort the job */
  removeWatchableBlock(block) {
    if ((0, _BlockModel.isOk)(block)) {
      if ((0, _core.isWatchable)(block.response)) {
        if (typeof block.response.watch.abort === 'function') {
          // safe-guarding here
          block.response.watch.abort();
        }
      } else if ((0, _core.isAbortableResponse)(block.response)) {
        if (typeof block.response.abort === 'function') {
          // safe-guarding here
          block.response.abort();
        }
      }
    }
  }
  /** As per `removeWatchableBlock`, but for all blocks */
  terminateAllWatchables() {
    this.state.splits.forEach(scrollback => {
      scrollback.blocks.forEach(_ => this.removeWatchableBlock(_));
    });
  }
  /**
   * Remove the given split (identified by `sbuuid`) from the state.
   *
   */
  removeSplit(sbuuid) {
    this.setState(curState => {
      const idx = this.findSplit(this.state, sbuuid);
      if (idx >= 0) {
        // if the user typed exit/ctrl+D in a 2-split scenario where
        // the split being closed is a default split, and the other is
        // a right strip... we have a choice: either turn the right
        // strip into a default split, or refuse the request to
        // close. For now, we opt for the former
        if ((this.props.hasRightStrip || this.props.hasLeftStrip) && curState.splits.length === 2 && curState.splits[idx].position === 'default') {
          const otherSplit = curState.splits[1 - idx];
          otherSplit.position = 'default';
          setTimeout(() => {
            if (this.props.hasRightStrip) {
              this.props.willToggleRightStripMode();
            } else {
              this.props.willToggleLeftStripMode();
            }
          });
        }
        _core.Events.eventBus.emitTabLayoutChange(sbuuid);
        _core.Events.eventBus.emitTabLayoutChange(this.props.tab.uuid);
        if (idx === curState.splits.length - 1) {
          // If we are removing the last split, we can safely
          // decrement the running counter. The reordering problem
          // described in the `scrollbackCounter` comment above does
          // not occur when removing the last split
          this.scrollbackCounter--;
        }
        if (curState.splits[idx].position === 'left-strip') {
          // then we are closing the left strip
          setTimeout(() => this.props.willToggleLeftStripMode());
        } else if (curState.splits[idx].position === 'right-strip') {
          // then we are closing the right strip
          setTimeout(() => this.props.willToggleRightStripMode());
        }
        // remove any watchers from the blocks of the split we are
        // about to remove
        curState.splits[idx].blocks.forEach(this.removeWatchableBlock);
        // clean up per-split event handlers
        curState.splits[idx].cleaners.forEach(cleaner => cleaner());
        // splice out this split from the list of all splits in this tab
        const splits = curState.splits.slice(0, idx).concat(curState.splits.slice(idx + 1));
        if (splits.length === 0) {
          // the last split was removed; notify parent
          const parent = this.props.tab;
          _core.Events.eventBus.emitWithTabId('/tab/close/request', parent.uuid, parent);
        }
        const focusedIdx = idx === 0 ? 0 : idx - 1;
        if (splits.length === 1) {
          const opts = {
            isSidecarNowHidden: false,
            isWidthConstrained: false
          };
          _core.Events.eventBus.emitTabLayoutChange(splits[focusedIdx].uuid, opts);
          _core.Events.eventBus.emitTabLayoutChange(this.props.tab.uuid, opts);
        }
        return {
          splits,
          focusedIdx
        };
      }
    });
    setTimeout(() => _core.Events.eventChannelUnsafe.emit('/zoom'));
  }
  /**
   * Splice in an update to the given split (identified by `sbuuid`),
   * using the giving ScrollbackState mutator.
   *
   */
  splice(sbuuid, mutator, opts = {}) {
    this.setState(curState => this.spliceMutate(curState, sbuuid, mutator, opts));
  }
  /** Helper for splice; i.e. splice() does the setState, while this method does them mutate */
  spliceMutate(curState, sbuuid, mutator, opts = {}) {
    const focusedIdx = this.findAvailableSplit(curState, sbuuid);
    const splits = curState.splits.slice(0, focusedIdx).concat([Object.assign({}, curState.splits[focusedIdx], mutator(curState.splits[focusedIdx]))]).concat(curState.splits.slice(focusedIdx + 1));
    return {
      focusedIdx: opts.focus ? focusedIdx : curState.focusedIdx,
      splits
    };
  }
  /** whether the given scrollback has Active Block */
  hasActiveBlock(scrollback) {
    return scrollback.blocks.findIndex(b => (0, _BlockModel.isActive)(b)) > -1;
  }
  /** return the index of the Active Block from a scrollback */
  findActiveBlock({
    blocks
  }) {
    return blocks.findIndex(b => (0, _BlockModel.isActive)(b));
  }
  tabFor(scrollback) {
    if (!scrollback.facade) {
      const {
        uuid
      } = scrollback;
      let facade; // eslint-disable-line prefer-const
      const focusedSplit = this.state ? this.state.splits[this.state.focusedIdx] : undefined;
      const focusedState = focusedSplit && focusedSplit.facade && focusedSplit.facade.state || this.props.tab.state;
      const tabFacade = Object.assign({}, this.props.tab, {
        // clone tab state
        state: focusedState.cloneWithUUID(uuid),
        // create a REPL controller that uses our uuid
        REPL: Object.assign({}, this.props.tab.REPL, {
          pexec: (command, execOptions) => {
            return this.props.tab.REPL.pexec(command, Object.assign({
              tab: facade
            }, execOptions));
          },
          reexec: (command, execOptions) => {
            return this.props.tab.REPL.reexec(command, Object.assign({
              tab: facade
            }, execOptions));
          },
          rexec: (command, execOptions) => {
            return this.props.tab.REPL.rexec(command, Object.assign({
              tab: facade
            }, execOptions));
          },
          qexec: (command, b1, b2, execOptions, b3) => {
            return this.props.tab.REPL.qexec(command, b1, b2, Object.assign({
              tab: facade
            }, execOptions), b3);
          }
        })
      });
      facade = Object.assign({}, tabFacade, {
        uuid
      });
      scrollback.facade = facade;
    }
    return scrollback.facade;
  }
  /** Is this scrollback not-100% width? */
  isWidthConstrained(scrollback) {
    return this.props.config.isPopup || scrollback.position === 'left-strip' || scrollback.position === 'right-strip' || scrollback.position === 'default' && this.numDefaultSplits() > 1;
  }
  /** Render the blocks in one split */
  blocks(tab, scrollback, sbidx) {
    const blocks = scrollback.blocks;
    const isWidthConstrained = this.isWidthConstrained(scrollback);
    // running tally for In[_idx_]
    let displayedIdx = 0;
    return blocks.map((_, idx) => {
      if (!(0, _BlockModel.isAnnouncement)(_) && !(0, _BlockModel.isOutputOnly)(_)) {
        displayedIdx++;
      }
      if (scrollback.maximized && _core.Capabilities.inBrowser() && (0, _BlockModel.isAnnouncement)(_) && idx === 0) {
        return null;
      }
      /** To find the focused block, we check:
       *  1. the block is in a focused scrollback
       *  2. the block idx matches scrollback.focusedBlockIdx (considering blocks that were hidden)
       *  3. return the active block if there's no scrollback.focusedBlockIdx */
      const isFocused = sbidx === this.state.focusedIdx && (idx === scrollback.focusedBlockIdx || scrollback.focusedBlockIdx === undefined && idx === this.findActiveBlock(scrollback));
      const isExecutable = (0, _core.isExecutableClient)();
      return _react.default.createElement(_Block.default, {
        key: (0, _BlockModel.hasOriginalUUID)(_) ? _.originalExecUUID : (0, _BlockModel.hasUUID)(_) ? _.execUUID : `${idx}-${(0, _BlockModel.isActive)(_)}-${(0, _BlockModel.isCancelled)(_)}`,
        idx: idx,
        isExecutable: isExecutable,
        displayedIdx: displayedIdx,
        model: _,
        uuid: scrollback.uuid,
        tab: tab,
        splitPosition: scrollback.position,
        nSplits: this.state.splits.length,
        noActiveInput: scrollback.hasActiveInput === false || scrollback.hasActiveInput !== true && this.props.noActiveInput,
        onFocus: scrollback.onFocus,
        willRemove: scrollback.willRemoveBlock,
        willFocusBlock: scrollback.willFocusBlock,
        willUpdateCommand: scrollback.willUpdateCommand,
        isExperimental: (0, _BlockModel.hasCommand)(_) && _.isExperimental,
        isFocused: isFocused,
        isWidthConstrained: isWidthConstrained,
        onOutputRender: scrollback.onOutputRender,
        ref: scrollback.setActiveBlock
      });
    });
  }
  /** Render one split */
  split(scrollback, sbidx) {
    const tab = this.tabFor(scrollback);
    const isWidthConstrained = this.isWidthConstrained(scrollback);
    const props = {
      className: 'kui--scrollback' + (scrollback.inverseColors ? ' kui--inverted-color-context' : ''),
      'data-is-width-constrained': isWidthConstrained || undefined,
      'data-is-focused': sbidx === this.state.focusedIdx || undefined,
      'data-position': scrollback.position,
      key: tab.uuid,
      'data-scrollback-id': tab.uuid,
      'data-is-maximized': scrollback.maximized || undefined,
      'data-has-maximized-block': !!scrollback.blocks.find(_BlockModel.isMaximized) || undefined,
      ref: scrollback.tabRefFor,
      onClick: scrollback.onClick,
      onMouseDown: scrollback.position === 'right-strip' /* || this.props.noActiveInput */ ? scrollback.onMouseDown : undefined
    };
    const children = _react.default.createElement(_react.default.Fragment, null, this.state.splits.length > 1 && _react.default.createElement(_SplitHeader.default, {
      onRemove: scrollback.remove,
      onClear: scrollback.clear,
      onInvert: scrollback.invert,
      createdBy: scrollback.createdBy
    }), _react.default.createElement("div", {
      className: "kui--scrollback-block-list"
    }, _react.default.createElement("ul", {
      className: "kui--scrollback-block-list-for-sizing",
      ref: scrollback.scrollableRef
    }, this.blocks(tab, scrollback, sbidx))));
    return _react.default.createElement(scrollback.position === 'default' ? 'div' : 'span', props, children);
  }
  render() {
    return _react.default.createElement(_SplitInjector.default.Provider, {
      value: this
    }, _react.default.createElement("div", {
      className: "repl",
      id: "main-repl",
      "data-session-init-status": this.props.sessionInit
    }, _react.default.createElement("div", {
      className: "repl-inner zoomable kui--terminal-split-container",
      "data-split-count": this.state.splits.length
    }, this.state.splits.map((scrollback, sbidx) => this.split(scrollback, sbidx)))));
  }
}
exports.default = ScrollableTerminal;