widget.ts 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import {
  4. KernelMessage
  5. } from '@jupyterlab/services';
  6. import {
  7. map, toArray
  8. } from '@phosphor/algorithm';
  9. import {
  10. Message
  11. } from '@phosphor/messaging';
  12. import {
  13. ISignal, Signal
  14. } from '@phosphor/signaling';
  15. import {
  16. Panel, PanelLayout
  17. } from '@phosphor/widgets';
  18. import {
  19. Widget
  20. } from '@phosphor/widgets';
  21. import {
  22. IClientSession
  23. } from '@jupyterlab/apputils';
  24. import {
  25. IEditorMimeTypeService, CodeEditor
  26. } from '@jupyterlab/codeeditor';
  27. import {
  28. Cell, CodeCell, RawCell,
  29. ICodeCellModel, IRawCellModel, CellModel,
  30. RawCellModel, CodeCellModel
  31. } from '@jupyterlab/cells';
  32. import {
  33. nbformat, IObservableVector, ObservableVector
  34. } from '@jupyterlab/coreutils';
  35. import {
  36. IRenderMime
  37. } from '@jupyterlab/rendermime';
  38. import {
  39. ForeignHandler
  40. } from './foreign';
  41. import {
  42. ConsoleHistory, IConsoleHistory
  43. } from './history';
  44. /**
  45. * The class name added to console widgets.
  46. */
  47. const CONSOLE_CLASS = 'jp-CodeConsole';
  48. /**
  49. * The class name added to the console banner.
  50. */
  51. const BANNER_CLASS = 'jp-CodeConsole-banner';
  52. /**
  53. * The class name of a cell whose input originated from a foreign session.
  54. */
  55. const FOREIGN_CELL_CLASS = 'jp-CodeConsole-foreignCell';
  56. /**
  57. * The class name of the active prompt cell.
  58. */
  59. const PROMPT_CLASS = 'jp-CodeConsole-promptCell';
  60. /**
  61. * The class name of the panel that holds cell content.
  62. */
  63. const CONTENT_CLASS = 'jp-CodeConsole-content';
  64. /**
  65. * The class name of the panel that holds prompts.
  66. */
  67. const INPUT_CLASS = 'jp-CodeConsole-input';
  68. /**
  69. * The timeout in ms for execution requests to the kernel.
  70. */
  71. const EXECUTION_TIMEOUT = 250;
  72. /**
  73. * A widget containing a Jupyter console.
  74. *
  75. * #### Notes
  76. * The CodeConsole class is intended to be used within a ConsolePanel
  77. * instance. Under most circumstances, it is not instantiated by user code.
  78. */
  79. export
  80. class CodeConsole extends Widget {
  81. /**
  82. * Construct a console widget.
  83. */
  84. constructor(options: CodeConsole.IOptions) {
  85. super();
  86. this.addClass(CONSOLE_CLASS);
  87. // Create the panels that hold the content and input.
  88. let layout = this.layout = new PanelLayout();
  89. this._cells = new ObservableVector<Cell>();
  90. this._content = new Panel();
  91. this._input = new Panel();
  92. let contentFactory = this.contentFactory = (
  93. options.contentFactory || CodeConsole.defaultContentFactory
  94. );
  95. let modelFactory = this.modelFactory = (
  96. options.modelFactory || CodeConsole.defaultModelFactory
  97. );
  98. this.rendermime = options.rendermime;
  99. this.session = options.session;
  100. this._mimeTypeService = options.mimeTypeService;
  101. // Add top-level CSS classes.
  102. this._content.addClass(CONTENT_CLASS);
  103. this._input.addClass(INPUT_CLASS);
  104. // Insert the content and input panes into the widget.
  105. layout.addWidget(this._content);
  106. layout.addWidget(this._input);
  107. // Create the banner.
  108. let model = modelFactory.createRawCell({});
  109. model.value.text = '...';
  110. let banner = this._banner = new RawCell({
  111. model,
  112. contentFactory: contentFactory
  113. });
  114. banner.addClass(BANNER_CLASS);
  115. banner.readOnly = true;
  116. this._content.addWidget(banner);
  117. // Set up the foreign iopub handler.
  118. this._foreignHandler = new ForeignHandler({
  119. session: this.session,
  120. parent: this,
  121. cellFactory: () => this._createCodeCell(),
  122. });
  123. this._history = new ConsoleHistory({
  124. session: this.session
  125. });
  126. this._onKernelChanged();
  127. this.session.kernelChanged.connect(this._onKernelChanged, this);
  128. }
  129. /**
  130. * A signal emitted when the console finished executing its prompt cell.
  131. */
  132. get executed(): ISignal<this, Date> {
  133. return this._executed;
  134. }
  135. /**
  136. * A signal emitted when a new prompt cell is created.
  137. */
  138. get promptCellCreated(): ISignal<this, CodeCell> {
  139. return this._promptCellCreated;
  140. }
  141. /**
  142. * The content factory used by the console.
  143. */
  144. readonly contentFactory: CodeConsole.IContentFactory;
  145. /**
  146. * The model factory for the console widget.
  147. */
  148. readonly modelFactory: CodeConsole.IModelFactory;
  149. /**
  150. * The rendermime instance used by the console.
  151. */
  152. readonly rendermime: IRenderMime;
  153. /**
  154. * The client session used by the console.
  155. */
  156. readonly session: IClientSession;
  157. /**
  158. * The list of content cells in the console.
  159. *
  160. * #### Notes
  161. * This list does not include the banner or the prompt for a console.
  162. */
  163. get cells(): IObservableVector<Cell> {
  164. return this._cells;
  165. }
  166. /*
  167. * The console input prompt cell.
  168. */
  169. get promptCell(): CodeCell | null {
  170. let inputLayout = (this._input.layout as PanelLayout);
  171. return inputLayout.widgets[0] as CodeCell || null;
  172. }
  173. /**
  174. * Add a new cell to the content panel.
  175. *
  176. * @param cell - The cell widget being added to the content panel.
  177. *
  178. * #### Notes
  179. * This method is meant for use by outside classes that want to inject content
  180. * into a console. It is distinct from the `inject` method in that it requires
  181. * rendered code cell widgets and does not execute them.
  182. */
  183. addCell(cell: Cell) {
  184. this._content.addWidget(cell);
  185. this._cells.pushBack(cell);
  186. cell.disposed.connect(this._onCellDisposed, this);
  187. this.update();
  188. }
  189. /**
  190. * Clear the code cells.
  191. */
  192. clear(): void {
  193. // Dispose all the content cells except the first, which is the banner.
  194. let cells = this._content.widgets;
  195. while (cells.length > 1) {
  196. cells[1].dispose();
  197. }
  198. }
  199. /**
  200. * Dispose of the resources held by the widget.
  201. */
  202. dispose() {
  203. // Do nothing if already disposed.
  204. if (this._foreignHandler === null) {
  205. return;
  206. }
  207. let cells = this._cells;
  208. let history = this._history;
  209. let foreignHandler = this._foreignHandler;
  210. this._banner = null;
  211. this._cells = null;
  212. this._content = null;
  213. this._input = null;
  214. this._mimeTypeService = null;
  215. this._foreignHandler = null;
  216. this._history = null;
  217. cells.clear();
  218. history.dispose();
  219. foreignHandler.dispose();
  220. super.dispose();
  221. }
  222. /**
  223. * Execute the current prompt.
  224. *
  225. * @param force - Whether to force execution without checking code
  226. * completeness.
  227. *
  228. * @param timeout - The length of time, in milliseconds, that the execution
  229. * should wait for the API to determine whether code being submitted is
  230. * incomplete before attempting submission anyway. The default value is `250`.
  231. */
  232. execute(force = false, timeout = EXECUTION_TIMEOUT): Promise<void> {
  233. if (this.session.status === 'dead') {
  234. return Promise.resolve(void 0);
  235. }
  236. let promptCell = this.promptCell;
  237. promptCell.model.trusted = true;
  238. if (force) {
  239. // Create a new prompt cell before kernel execution to allow typeahead.
  240. this.newPromptCell();
  241. return this._execute(promptCell);
  242. }
  243. // Check whether we should execute.
  244. return this._shouldExecute(timeout).then(should => {
  245. if (this.isDisposed) {
  246. return;
  247. }
  248. if (should) {
  249. // Create a new prompt cell before kernel execution to allow typeahead.
  250. this.newPromptCell();
  251. return this._execute(promptCell);
  252. }
  253. });
  254. }
  255. /**
  256. * Inject arbitrary code for the console to execute immediately.
  257. *
  258. * @param code - The code contents of the cell being injected.
  259. *
  260. * @returns A promise that indicates when the injected cell's execution ends.
  261. */
  262. inject(code: string): Promise<void> {
  263. let cell = this._createCodeCell();
  264. cell.model.value.text = code;
  265. this.addCell(cell);
  266. return this._execute(cell);
  267. }
  268. /**
  269. * Insert a line break in the prompt cell.
  270. */
  271. insertLinebreak(): void {
  272. let promptCell = this.promptCell;
  273. let model = promptCell.model;
  274. let editor = promptCell.editor;
  275. // Insert the line break at the cursor position, and move cursor forward.
  276. let pos = editor.getCursorPosition();
  277. let offset = editor.getOffsetAt(pos);
  278. let text = model.value.text;
  279. model.value.text = text.substr(0, offset) + '\n' + text.substr(offset);
  280. pos = editor.getPositionAt(offset + 1);
  281. editor.setCursorPosition(pos);
  282. }
  283. /**
  284. * Serialize the output.
  285. */
  286. serialize(): nbformat.ICodeCell[] {
  287. let promptCell = this.promptCell;
  288. let layout = this._content.layout as PanelLayout;
  289. // Serialize content.
  290. let output = map(layout.widgets, widget => {
  291. return (widget as CodeCell).model.toJSON() as nbformat.ICodeCell;
  292. });
  293. // Serialize prompt cell and return.
  294. return toArray(output).concat(promptCell.model.toJSON() as nbformat.ICodeCell);
  295. }
  296. /**
  297. * Handle the DOM events for the widget.
  298. *
  299. * @param event - The DOM event sent to the widget.
  300. *
  301. * #### Notes
  302. * This method implements the DOM `EventListener` interface and is
  303. * called in response to events on the notebook panel's node. It should
  304. * not be called directly by user code.
  305. */
  306. handleEvent(event: Event): void {
  307. switch (event.type) {
  308. case 'keydown':
  309. this._evtKeyDown(event as KeyboardEvent);
  310. break;
  311. default:
  312. break;
  313. }
  314. }
  315. /**
  316. * Handle `after_attach` messages for the widget.
  317. */
  318. protected onAfterAttach(msg: Message): void {
  319. let node = this.node;
  320. node.addEventListener('keydown', this, true);
  321. // Create a prompt if necessary.
  322. if (!this.promptCell) {
  323. this.newPromptCell();
  324. } else {
  325. this.promptCell.editor.focus();
  326. this.update();
  327. }
  328. }
  329. /**
  330. * Handle `before-detach` messages for the widget.
  331. */
  332. protected onBeforeDetach(msg: Message): void {
  333. let node = this.node;
  334. node.removeEventListener('keydown', this, true);
  335. }
  336. /**
  337. * Handle `'activate-request'` messages.
  338. */
  339. protected onActivateRequest(msg: Message): void {
  340. this.promptCell.editor.focus();
  341. this.update();
  342. }
  343. /**
  344. * Make a new prompt cell.
  345. */
  346. protected newPromptCell(): void {
  347. let promptCell = this.promptCell;
  348. let input = this._input;
  349. // Make the last prompt read-only, clear its signals, and move to content.
  350. if (promptCell) {
  351. promptCell.readOnly = true;
  352. promptCell.removeClass(PROMPT_CLASS);
  353. Signal.clearData(promptCell.editor);
  354. (input.layout as PanelLayout).removeWidgetAt(0);
  355. this.addCell(promptCell);
  356. }
  357. // Create the new prompt cell.
  358. let factory = this.contentFactory;
  359. let options = this._createCodeCellOptions();
  360. promptCell = factory.createCodeCell(options);
  361. promptCell.model.mimeType = this._mimetype;
  362. promptCell.addClass(PROMPT_CLASS);
  363. this._input.addWidget(promptCell);
  364. // Suppress the default "Enter" key handling.
  365. let editor = promptCell.editor;
  366. editor.addKeydownHandler(this._onEditorKeydown);
  367. this._history.editor = editor;
  368. if (this.isAttached) {
  369. this.activate();
  370. }
  371. this._promptCellCreated.emit(promptCell);
  372. }
  373. /**
  374. * Handle `update-request` messages.
  375. */
  376. protected onUpdateRequest(msg: Message): void {
  377. Private.scrollToBottom(this._content.node);
  378. }
  379. /**
  380. * Handle the `'keydown'` event for the widget.
  381. */
  382. private _evtKeyDown(event: KeyboardEvent): void {
  383. let editor = this.promptCell.editor;
  384. if (event.keyCode === 13 && !editor.hasFocus()) {
  385. event.preventDefault();
  386. editor.focus();
  387. }
  388. }
  389. /**
  390. * Execute the code in the current prompt cell.
  391. */
  392. private _execute(cell: CodeCell): Promise<void> {
  393. this._history.push(cell.model.value.text);
  394. cell.model.contentChanged.connect(this.update, this);
  395. let onSuccess = (value: KernelMessage.IExecuteReplyMsg) => {
  396. if (this.isDisposed) {
  397. return;
  398. }
  399. if (value && value.content.status === 'ok') {
  400. let content = value.content as KernelMessage.IExecuteOkReply;
  401. // Use deprecated payloads for backwards compatibility.
  402. if (content.payload && content.payload.length) {
  403. let setNextInput = content.payload.filter(i => {
  404. return (i as any).source === 'set_next_input';
  405. })[0];
  406. if (setNextInput) {
  407. let text = (setNextInput as any).text;
  408. // Ignore the `replace` value and always set the next cell.
  409. cell.model.value.text = text;
  410. }
  411. }
  412. }
  413. cell.model.contentChanged.disconnect(this.update, this);
  414. this.update();
  415. this._executed.emit(new Date());
  416. };
  417. let onFailure = () => {
  418. if (this.isDisposed) {
  419. return;
  420. }
  421. cell.model.contentChanged.disconnect(this.update, this);
  422. this.update();
  423. };
  424. return cell.execute(this.session).then(onSuccess, onFailure);
  425. }
  426. /**
  427. * Update the console based on the kernel info.
  428. */
  429. private _handleInfo(info: KernelMessage.IInfoReply): void {
  430. let layout = this._content.layout as PanelLayout;
  431. let banner = layout.widgets[0] as RawCell;
  432. banner.model.value.text = info.banner;
  433. let lang = info.language_info as nbformat.ILanguageInfoMetadata;
  434. this._mimetype = this._mimeTypeService.getMimeTypeByLanguage(lang);
  435. if (this.promptCell) {
  436. this.promptCell.model.mimeType = this._mimetype;
  437. }
  438. }
  439. /**
  440. * Create a new foreign cell.
  441. */
  442. private _createCodeCell(): CodeCell {
  443. let factory = this.contentFactory;
  444. let options = this._createCodeCellOptions();
  445. let cell = factory.createCodeCell(options);
  446. cell.readOnly = true;
  447. cell.model.mimeType = this._mimetype;
  448. cell.addClass(FOREIGN_CELL_CLASS);
  449. return cell;
  450. }
  451. /**
  452. * Create the options used to initialize a code cell widget.
  453. */
  454. private _createCodeCellOptions(): CodeCell.IOptions {
  455. let contentFactory = this.contentFactory;
  456. let modelFactory = this.modelFactory;
  457. let model = modelFactory.createCodeCell({ });
  458. let rendermime = this.rendermime;
  459. return { model, rendermime, contentFactory };
  460. }
  461. /**
  462. * Handle cell disposed signals.
  463. */
  464. private _onCellDisposed(sender: Widget, args: void): void {
  465. if (!this.isDisposed) {
  466. this._cells.remove(sender as CodeCell);
  467. }
  468. }
  469. /**
  470. * Test whether we should execute the prompt cell.
  471. */
  472. private _shouldExecute(timeout: number): Promise<boolean> {
  473. let promptCell = this.promptCell;
  474. let model = promptCell.model;
  475. let code = model.value.text + '\n';
  476. return new Promise<boolean>((resolve, reject) => {
  477. let timer = setTimeout(() => { resolve(true); }, timeout);
  478. this.session.kernel.requestIsComplete({ code }).then(isComplete => {
  479. clearTimeout(timer);
  480. if (this.isDisposed) {
  481. resolve(false);
  482. }
  483. if (isComplete.content.status !== 'incomplete') {
  484. resolve(true);
  485. return;
  486. }
  487. model.value.text = code + isComplete.content.indent;
  488. let editor = promptCell.editor;
  489. let pos = editor.getPositionAt(model.value.text.length);
  490. editor.setCursorPosition(pos);
  491. resolve(false);
  492. }).catch(() => { resolve(true); });
  493. });
  494. }
  495. /**
  496. * Handle a keydown event on an editor.
  497. */
  498. private _onEditorKeydown(editor: CodeEditor.IEditor, event: KeyboardEvent) {
  499. // Suppress "Enter" events.
  500. return event.keyCode === 13;
  501. }
  502. /**
  503. * Handle a change to the kernel.
  504. */
  505. private _onKernelChanged(): void {
  506. this.clear();
  507. let kernel = this.session.kernel;
  508. if (!kernel) {
  509. return;
  510. }
  511. kernel.ready.then(() => {
  512. if (this.isDisposed) {
  513. return;
  514. }
  515. this._handleInfo(kernel.info);
  516. });
  517. }
  518. private _banner: RawCell = null;
  519. private _cells: IObservableVector<Cell> = null;
  520. private _content: Panel = null;
  521. private _executed = new Signal<this, Date>(this);
  522. private _foreignHandler: ForeignHandler = null;
  523. private _history: IConsoleHistory = null;
  524. private _input: Panel = null;
  525. private _mimetype = 'text/x-ipython';
  526. private _mimeTypeService: IEditorMimeTypeService = null;
  527. private _promptCellCreated = new Signal<this, CodeCell>(this);
  528. }
  529. /**
  530. * A namespace for CodeConsole statics.
  531. */
  532. export
  533. namespace CodeConsole {
  534. /**
  535. * The initialization options for a console widget.
  536. */
  537. export
  538. interface IOptions {
  539. /**
  540. * The content factory for the console widget.
  541. */
  542. contentFactory: IContentFactory;
  543. /**
  544. * The model factory for the console widget.
  545. */
  546. modelFactory?: IModelFactory;
  547. /**
  548. * The mime renderer for the console widget.
  549. */
  550. rendermime: IRenderMime;
  551. /**
  552. * The client session for the console widget.
  553. */
  554. session: IClientSession;
  555. /**
  556. * The service used to look up mime types.
  557. */
  558. mimeTypeService: IEditorMimeTypeService;
  559. }
  560. /**
  561. * A content factory for console children.
  562. */
  563. export
  564. interface IContentFactory extends Cell.IContentFactory {
  565. /**
  566. * Create a new code cell widget.
  567. */
  568. createCodeCell(options: CodeCell.IOptions): CodeCell;
  569. /**
  570. * Create a new raw cell widget.
  571. */
  572. createRawCell(options: RawCell.IOptions): RawCell;
  573. }
  574. /**
  575. * Default implementation of `IContentFactory`.
  576. */
  577. export
  578. class ContentFactory extends Cell.ContentFactory implements IContentFactory {
  579. /**
  580. * Create a new code cell widget.
  581. *
  582. * #### Notes
  583. * If no cell content factory is passed in with the options, the one on the
  584. * notebook content factory is used.
  585. */
  586. createCodeCell(options: CodeCell.IOptions): CodeCell {
  587. if (!options.contentFactory) {
  588. options.contentFactory = this;
  589. }
  590. return new CodeCell(options);
  591. }
  592. /**
  593. * Create a new raw cell widget.
  594. *
  595. * #### Notes
  596. * If no cell content factory is passed in with the options, the one on the
  597. * notebook content factory is used.
  598. */
  599. createRawCell(options: RawCell.IOptions): RawCell {
  600. if (!options.contentFactory) {
  601. options.contentFactory = this;
  602. }
  603. return new RawCell(options);
  604. }
  605. }
  606. /**
  607. * An initialize options for `ContentFactory`.
  608. */
  609. export
  610. interface IContentFactoryOptions extends Cell.IContentFactory { }
  611. /**
  612. * A default content factory for the code console.
  613. */
  614. export
  615. const defaultContentFactory: IContentFactory = new ContentFactory();
  616. /**
  617. * A model factory for a console widget.
  618. */
  619. export
  620. interface IModelFactory {
  621. /**
  622. * The factory for code cell content.
  623. */
  624. readonly codeCellContentFactory: CodeCellModel.IContentFactory;
  625. /**
  626. * Create a new code cell.
  627. *
  628. * @param options - The options used to create the cell.
  629. *
  630. * @returns A new code cell. If a source cell is provided, the
  631. * new cell will be intialized with the data from the source.
  632. */
  633. createCodeCell(options: CodeCellModel.IOptions): ICodeCellModel;
  634. /**
  635. * Create a new raw cell.
  636. *
  637. * @param options - The options used to create the cell.
  638. *
  639. * @returns A new raw cell. If a source cell is provided, the
  640. * new cell will be intialized with the data from the source.
  641. */
  642. createRawCell(options: CellModel.IOptions): IRawCellModel;
  643. }
  644. /**
  645. * The default implementation of an `IModelFactory`.
  646. */
  647. export
  648. class ModelFactory {
  649. /**
  650. * Create a new cell model factory.
  651. */
  652. constructor(options: IModelFactoryOptions = {}) {
  653. this.codeCellContentFactory = (options.codeCellContentFactory ||
  654. CodeCellModel.defaultContentFactory
  655. );
  656. }
  657. /**
  658. * The factory for output area models.
  659. */
  660. readonly codeCellContentFactory: CodeCellModel.IContentFactory;
  661. /**
  662. * Create a new code cell.
  663. *
  664. * @param source - The data to use for the original source data.
  665. *
  666. * @returns A new code cell. If a source cell is provided, the
  667. * new cell will be intialized with the data from the source.
  668. * If the contentFactory is not provided, the instance
  669. * `codeCellContentFactory` will be used.
  670. */
  671. createCodeCell(options: CodeCellModel.IOptions): ICodeCellModel {
  672. if (!options.contentFactory) {
  673. options.contentFactory = this.codeCellContentFactory;
  674. }
  675. return new CodeCellModel(options);
  676. }
  677. /**
  678. * Create a new raw cell.
  679. *
  680. * @param source - The data to use for the original source data.
  681. *
  682. * @returns A new raw cell. If a source cell is provided, the
  683. * new cell will be intialized with the data from the source.
  684. */
  685. createRawCell(options: CellModel.IOptions): IRawCellModel {
  686. return new RawCellModel(options);
  687. }
  688. }
  689. /**
  690. * The options used to initialize a `ModelFactory`.
  691. */
  692. export
  693. interface IModelFactoryOptions {
  694. /**
  695. * The factory for output area models.
  696. */
  697. codeCellContentFactory?: CodeCellModel.IContentFactory;
  698. }
  699. /**
  700. * The default `ModelFactory` instance.
  701. */
  702. export
  703. const defaultModelFactory = new ModelFactory({});
  704. }
  705. /**
  706. * A namespace for console widget private data.
  707. */
  708. namespace Private {
  709. /**
  710. * Jump to the bottom of a node.
  711. *
  712. * @param node - The scrollable element.
  713. */
  714. export
  715. function scrollToBottom(node: HTMLElement): void {
  716. node.scrollTop = node.scrollHeight - node.clientHeight;
  717. }
  718. }