widget.ts 24 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015
  1. /*-----------------------------------------------------------------------------
  2. | Copyright (c) Jupyter Development Team.
  3. | Distributed under the terms of the Modified BSD License.
  4. |----------------------------------------------------------------------------*/
  5. import {
  6. KernelMessage
  7. } from '@jupyterlab/services';
  8. import {
  9. JSONValue, PromiseDelegate
  10. } from '@phosphor/coreutils';
  11. import {
  12. Message
  13. } from '@phosphor/messaging';
  14. import {
  15. PanelLayout, Panel, Widget
  16. } from '@phosphor/widgets';
  17. import {
  18. IClientSession
  19. } from '@jupyterlab/apputils';
  20. import {
  21. IChangedArgs, ActivityMonitor
  22. } from '@jupyterlab/coreutils';
  23. import {
  24. CodeEditor, CodeEditorWrapper
  25. } from '@jupyterlab/codeeditor';
  26. import {
  27. IRenderMime, MimeModel, RenderMimeRegistry
  28. } from '@jupyterlab/rendermime';
  29. import {
  30. IObservableMap
  31. } from '@jupyterlab/observables';
  32. import {
  33. OutputArea, IOutputPrompt, OutputPrompt, IStdin, Stdin
  34. } from '@jupyterlab/outputarea';
  35. import {
  36. ICellModel, ICodeCellModel,
  37. IMarkdownCellModel, IRawCellModel
  38. } from './model';
  39. import {
  40. InputCollapser, OutputCollapser
  41. } from './collapser';
  42. import {
  43. InputArea, IInputPrompt, InputPrompt
  44. } from './inputarea';
  45. import {
  46. InputPlaceholder, OutputPlaceholder
  47. } from './placeholder';
  48. import {
  49. CellHeader, CellFooter, ICellHeader, ICellFooter
  50. } from './headerfooter';
  51. /**
  52. * The CSS class added to cell widgets.
  53. */
  54. const CELL_CLASS = 'jp-Cell';
  55. /**
  56. * The CSS class added to the cell header.
  57. */
  58. const CELL_HEADER_CLASS = 'jp-Cell-header';
  59. /**
  60. * The CSS class added to the cell footer.
  61. */
  62. const CELL_FOOTER_CLASS = 'jp-Cell-footer';
  63. /**
  64. * The CSS class added to the cell input wrapper.
  65. */
  66. const CELL_INPUT_WRAPPER_CLASS = 'jp-Cell-inputWrapper';
  67. /**
  68. * The CSS class added to the cell output wrapper.
  69. */
  70. const CELL_OUTPUT_WRAPPER_CLASS = 'jp-Cell-outputWrapper';
  71. /**
  72. * The CSS class added to the cell input area.
  73. */
  74. const CELL_INPUT_AREA_CLASS = 'jp-Cell-inputArea';
  75. /**
  76. * The CSS class added to the cell output area.
  77. */
  78. const CELL_OUTPUT_AREA_CLASS = 'jp-Cell-outputArea';
  79. /**
  80. * The CSS class added to the cell input collapser.
  81. */
  82. const CELL_INPUT_COLLAPSER_CLASS = 'jp-Cell-inputCollapser';
  83. /**
  84. * The CSS class added to the cell output collapser.
  85. */
  86. const CELL_OUTPUT_COLLAPSER_CLASS = 'jp-Cell-outputCollapser';
  87. /**
  88. * The class name added to the cell when collapsed.
  89. */
  90. const COLLAPSED_CLASS = 'jp-mod-collapsed';
  91. /**
  92. * The class name added to the cell when readonly.
  93. */
  94. const READONLY_CLASS = 'jp-mod-readOnly';
  95. /**
  96. * The class name added to code cells.
  97. */
  98. const CODE_CELL_CLASS = 'jp-CodeCell';
  99. /**
  100. * The class name added to markdown cells.
  101. */
  102. const MARKDOWN_CELL_CLASS = 'jp-MarkdownCell';
  103. /**
  104. * The class name added to rendered markdown output widgets.
  105. */
  106. const MARKDOWN_OUTPUT_CLASS = 'jp-MarkdownOutput';
  107. /**
  108. * The class name added to raw cells.
  109. */
  110. const RAW_CELL_CLASS = 'jp-RawCell';
  111. /**
  112. * The class name added to a rendered input area.
  113. */
  114. const RENDERED_CLASS = 'jp-mod-rendered';
  115. const NO_OUTPUTS_CLASS = 'jp-mod-noOutputs';
  116. /**
  117. * The text applied to an empty markdown cell.
  118. */
  119. const DEFAULT_MARKDOWN_TEXT = 'Type Markdown and LaTeX: $ α^2 $';
  120. /**
  121. * The timeout to wait for change activity to have ceased before rendering.
  122. */
  123. const RENDER_TIMEOUT = 1000;
  124. /******************************************************************************
  125. * Cell
  126. ******************************************************************************/
  127. /**
  128. * A base cell widget.
  129. */
  130. export
  131. class Cell extends Widget {
  132. /**
  133. * Construct a new base cell widget.
  134. */
  135. constructor(options: Cell.IOptions) {
  136. super();
  137. this.addClass(CELL_CLASS);
  138. let model = this._model = options.model;
  139. let contentFactory = this.contentFactory = (
  140. options.contentFactory || Cell.defaultContentFactory
  141. );
  142. this.layout = new PanelLayout();
  143. // Header
  144. let header = this._header = contentFactory.createCellHeader();
  145. header.addClass(CELL_HEADER_CLASS);
  146. (this.layout as PanelLayout).addWidget(header);
  147. // Input
  148. let inputWrapper = this._inputWrapper = new Panel();
  149. inputWrapper.addClass(CELL_INPUT_WRAPPER_CLASS);
  150. let inputCollapser = this._inputCollapser = new InputCollapser();
  151. inputCollapser.addClass(CELL_INPUT_COLLAPSER_CLASS);
  152. let input = this._input = new InputArea({model, contentFactory });
  153. input.addClass(CELL_INPUT_AREA_CLASS);
  154. inputWrapper.addWidget(inputCollapser);
  155. inputWrapper.addWidget(input);
  156. (this.layout as PanelLayout).addWidget(inputWrapper);
  157. this._inputPlaceholder = new InputPlaceholder(() => {
  158. this.inputHidden = !this.inputHidden;
  159. });
  160. // Footer
  161. let footer = this._footer = this.contentFactory.createCellFooter();
  162. footer.addClass(CELL_FOOTER_CLASS);
  163. (this.layout as PanelLayout).addWidget(footer);
  164. // Editor settings
  165. if (options.editorConfig) {
  166. Object.keys(options.editorConfig)
  167. .forEach((key: keyof CodeEditor.IConfig) => {
  168. this.editor.setOption(key, options.editorConfig[key]);
  169. });
  170. }
  171. }
  172. /**
  173. * The content factory used by the widget.
  174. */
  175. readonly contentFactory: Cell.IContentFactory;
  176. /**
  177. * Get the prompt node used by the cell.
  178. */
  179. get promptNode(): HTMLElement {
  180. if (!this._inputHidden) {
  181. return this._input.promptNode;
  182. } else {
  183. return ((this._inputPlaceholder.node as HTMLElement).firstElementChild as HTMLElement);
  184. }
  185. }
  186. /**
  187. * Get the CodeEditorWrapper used by the cell.
  188. */
  189. get editorWidget(): CodeEditorWrapper {
  190. return this._input.editorWidget;
  191. }
  192. /**
  193. * Get the CodeEditor used by the cell.
  194. */
  195. get editor(): CodeEditor.IEditor {
  196. return this._input.editor;
  197. }
  198. /**
  199. * Get the model used by the cell.
  200. */
  201. get model(): ICellModel {
  202. return this._model;
  203. }
  204. /**
  205. * Get the input area for the cell.
  206. */
  207. get inputArea(): InputArea {
  208. return this._input;
  209. }
  210. /**
  211. * The read only state of the cell.
  212. */
  213. get readOnly(): boolean {
  214. return this._readOnly;
  215. }
  216. set readOnly(value: boolean) {
  217. if (value === this._readOnly) {
  218. return;
  219. }
  220. this._readOnly = value;
  221. this.update();
  222. }
  223. /**
  224. * A promise that resolves when the widget renders for the first time.
  225. */
  226. get ready(): Promise<void> {
  227. return Promise.resolve(undefined);
  228. }
  229. /**
  230. * Set the prompt for the widget.
  231. */
  232. setPrompt(value: string): void {
  233. this._input.setPrompt(value);
  234. }
  235. /**
  236. * The view state of input being hidden.
  237. */
  238. get inputHidden(): boolean {
  239. return this._inputHidden;
  240. }
  241. set inputHidden(value: boolean) {
  242. if (this._inputHidden === value) {
  243. return;
  244. }
  245. let layout = this._inputWrapper.layout as PanelLayout;
  246. if (value) {
  247. this._input.parent = null;
  248. layout.addWidget(this._inputPlaceholder);
  249. } else {
  250. this._inputPlaceholder.parent = null;
  251. layout.addWidget(this._input);
  252. }
  253. this._inputHidden = value;
  254. this.handleInputHidden(value);
  255. }
  256. /**
  257. * Handle the input being hidden.
  258. *
  259. * #### Notes
  260. * This is called by the `inputHidden` setter so that subclasses
  261. * can perform actions upon the input being hidden without accessing
  262. * private state.
  263. */
  264. protected handleInputHidden(value: boolean): void {
  265. return;
  266. }
  267. /**
  268. * Clone the cell, using the same model.
  269. */
  270. clone(): Cell {
  271. let constructor = this.constructor as typeof Cell;
  272. return new constructor({
  273. model: this.model,
  274. contentFactory: this.contentFactory
  275. });
  276. }
  277. /**
  278. * Dispose of the resources held by the widget.
  279. */
  280. dispose() {
  281. // Do nothing if already disposed.
  282. if (this.isDisposed) {
  283. return;
  284. }
  285. this._input = null;
  286. this._model = null;
  287. this._header = null;
  288. this._footer = null;
  289. this._inputCollapser = null;
  290. this._inputWrapper = null;
  291. this._inputPlaceholder = null;
  292. super.dispose();
  293. }
  294. /**
  295. * Handle `after-attach` messages.
  296. */
  297. protected onAfterAttach(msg: Message): void {
  298. this.update();
  299. }
  300. /**
  301. * Handle `'activate-request'` messages.
  302. */
  303. protected onActivateRequest(msg: Message): void {
  304. this.editor.focus();
  305. }
  306. /**
  307. * Handle `update-request` messages.
  308. */
  309. protected onUpdateRequest(msg: Message): void {
  310. if (!this._model) {
  311. return;
  312. }
  313. // Handle read only state.
  314. if (this.editor.getOption('readOnly') !== this._readOnly) {
  315. this.editor.setOption('readOnly', this._readOnly);
  316. this.toggleClass(READONLY_CLASS, this._readOnly);
  317. }
  318. }
  319. private _readOnly = false;
  320. private _model: ICellModel = null;
  321. private _header: ICellHeader = null;
  322. private _footer: ICellFooter = null;
  323. private _inputHidden = false;
  324. private _input: InputArea = null;
  325. private _inputCollapser: InputCollapser = null;
  326. private _inputWrapper: Widget = null;
  327. private _inputPlaceholder: InputPlaceholder = null;
  328. }
  329. /**
  330. * The namespace for the `Cell` class statics.
  331. */
  332. export
  333. namespace Cell {
  334. /**
  335. * An options object for initializing a cell widget.
  336. */
  337. export
  338. interface IOptions {
  339. /**
  340. * The model used by the cell.
  341. */
  342. model: ICellModel;
  343. /**
  344. * The factory object for customizable cell children.
  345. */
  346. contentFactory?: IContentFactory;
  347. /**
  348. * The configuration options for the text editor widget.
  349. */
  350. editorConfig?: Partial<CodeEditor.IConfig>;
  351. }
  352. /**
  353. * The factory object for customizable cell children.
  354. *
  355. * This is used to allow users of cells to customize child content.
  356. *
  357. * This inherits from `OutputArea.IContentFactory` to avoid needless nesting and
  358. * provide a single factory object for all notebook/cell/outputarea related
  359. * widgets.
  360. */
  361. export
  362. interface IContentFactory extends OutputArea.IContentFactory, InputArea.IContentFactory {
  363. /**
  364. * Create a new cell header for the parent widget.
  365. */
  366. createCellHeader(): ICellHeader;
  367. /**
  368. * Create a new cell header for the parent widget.
  369. */
  370. createCellFooter(): ICellFooter;
  371. }
  372. /**
  373. * The default implementation of an `IContentFactory`.
  374. *
  375. * This includes a CodeMirror editor factory to make it easy to use out of the box.
  376. */
  377. export
  378. class ContentFactory implements IContentFactory {
  379. /**
  380. * Create a content factory for a cell.
  381. */
  382. constructor(options: ContentFactory.IOptions = {}) {
  383. this._editorFactory = (options.editorFactory || InputArea.defaultEditorFactory);
  384. }
  385. /**
  386. * The readonly editor factory that create code editors
  387. */
  388. get editorFactory(): CodeEditor.Factory {
  389. return this._editorFactory;
  390. }
  391. /**
  392. * Create a new cell header for the parent widget.
  393. */
  394. createCellHeader(): ICellHeader {
  395. return new CellHeader();
  396. }
  397. /**
  398. * Create a new cell header for the parent widget.
  399. */
  400. createCellFooter(): ICellFooter {
  401. return new CellFooter();
  402. }
  403. /**
  404. * Create an input prompt.
  405. */
  406. createInputPrompt(): IInputPrompt {
  407. return new InputPrompt();
  408. }
  409. /**
  410. * Create the output prompt for the widget.
  411. */
  412. createOutputPrompt(): IOutputPrompt {
  413. return new OutputPrompt();
  414. }
  415. /**
  416. * Create an stdin widget.
  417. */
  418. createStdin(options: Stdin.IOptions): IStdin {
  419. return new Stdin(options);
  420. }
  421. private _editorFactory: CodeEditor.Factory = null;
  422. }
  423. /**
  424. * A namespace for cell content factory.
  425. */
  426. export
  427. namespace ContentFactory {
  428. /**
  429. * Options for the content factory.
  430. */
  431. export
  432. interface IOptions {
  433. /**
  434. * The editor factory used by the content factory.
  435. *
  436. * If this is not passed, a default CodeMirror editor factory
  437. * will be used.
  438. */
  439. editorFactory?: CodeEditor.Factory;
  440. }
  441. }
  442. /**
  443. * The default content factory for cells.
  444. */
  445. export
  446. const defaultContentFactory = new ContentFactory();
  447. }
  448. /******************************************************************************
  449. * CodeCell
  450. ******************************************************************************/
  451. /**
  452. * A widget for a code cell.
  453. */
  454. export
  455. class CodeCell extends Cell {
  456. /**
  457. * Construct a code cell widget.
  458. */
  459. constructor(options: CodeCell.IOptions) {
  460. super(options);
  461. this.addClass(CODE_CELL_CLASS);
  462. // Only save options not handled by parent constructor.
  463. let rendermime = this._rendermime = options.rendermime;
  464. let contentFactory = this.contentFactory;
  465. let model = this.model;
  466. // Insert the output before the cell footer.
  467. let outputWrapper = this._outputWrapper = new Panel();
  468. outputWrapper.addClass(CELL_OUTPUT_WRAPPER_CLASS);
  469. let outputCollapser = this._outputCollapser = new OutputCollapser();
  470. outputCollapser.addClass(CELL_OUTPUT_COLLAPSER_CLASS);
  471. let output = this._output = new OutputArea({
  472. model: model.outputs,
  473. rendermime,
  474. contentFactory: contentFactory
  475. });
  476. output.addClass(CELL_OUTPUT_AREA_CLASS);
  477. // Set a CSS if there are no outputs, and connect a signal for future
  478. // changes to the number of outputs. This is for conditional styling
  479. // if there are no outputs.
  480. if (model.outputs.length === 0) {
  481. this.addClass(NO_OUTPUTS_CLASS);
  482. }
  483. output.outputLengthChanged.connect(this._outputLengthHandler, this);
  484. outputWrapper.addWidget(outputCollapser);
  485. outputWrapper.addWidget(output);
  486. (this.layout as PanelLayout).insertWidget(2, outputWrapper);
  487. this._outputPlaceholder = new OutputPlaceholder(() => {
  488. this.outputHidden = !this.outputHidden;
  489. });
  490. // Modify state
  491. this.setPrompt(`${model.executionCount || ''}`);
  492. model.stateChanged.connect(this.onStateChanged, this);
  493. model.metadata.changed.connect(this.onMetadataChanged, this);
  494. }
  495. /**
  496. * The model used by the widget.
  497. */
  498. readonly model: ICodeCellModel;
  499. /**
  500. * Get the output area for the cell.
  501. */
  502. get outputArea(): OutputArea {
  503. return this._output;
  504. }
  505. /**
  506. * The view state of output being collapsed.
  507. */
  508. get outputHidden(): boolean {
  509. return this._outputHidden;
  510. }
  511. set outputHidden(value: boolean) {
  512. if (this._outputHidden === value) {
  513. return;
  514. }
  515. let layout = this._outputWrapper.layout as PanelLayout;
  516. if (value) {
  517. layout.removeWidget(this._output);
  518. layout.addWidget(this._outputPlaceholder);
  519. if (this.inputHidden && !this._outputWrapper.isHidden) {
  520. this._outputWrapper.hide();
  521. }
  522. } else {
  523. if (this._outputWrapper.isHidden) {
  524. this._outputWrapper.show();
  525. }
  526. layout.removeWidget(this._outputPlaceholder);
  527. layout.addWidget(this._output);
  528. }
  529. this._outputHidden = value;
  530. }
  531. /**
  532. * Whether the output is in a scrolled state?
  533. */
  534. get outputsScrolled(): boolean {
  535. return this._outputsScrolled;
  536. }
  537. set outputsScrolled(value: boolean) {
  538. this.toggleClass('jp-mod-outputsScrolled', value);
  539. this._outputsScrolled = value;
  540. }
  541. /**
  542. * Handle the input being hidden.
  543. *
  544. * #### Notes
  545. * This method is called by the case cell implementation and is
  546. * subclasses here so the code cell can watch to see when input
  547. * is hidden without accessing private state.
  548. */
  549. protected handleInputHidden(value: boolean): void {
  550. if (!value && this._outputWrapper.isHidden) {
  551. this._outputWrapper.show();
  552. } else if (value && !this._outputWrapper.isHidden && this._outputHidden) {
  553. this._outputWrapper.hide();
  554. }
  555. }
  556. /**
  557. * Clone the cell, using the same model.
  558. */
  559. clone(): CodeCell {
  560. let constructor = this.constructor as typeof CodeCell;
  561. return new constructor({
  562. model: this.model,
  563. contentFactory: this.contentFactory,
  564. rendermime: this._rendermime
  565. });
  566. }
  567. /**
  568. * Clone the OutputArea alone, using the same model.
  569. */
  570. cloneOutputArea(): OutputArea {
  571. return new OutputArea({
  572. model: this.model.outputs,
  573. contentFactory: this.contentFactory,
  574. rendermime: this._rendermime,
  575. });
  576. }
  577. /**
  578. * Dispose of the resources used by the widget.
  579. */
  580. dispose(): void {
  581. if (this.isDisposed) {
  582. return;
  583. }
  584. this._output.outputLengthChanged.disconnect(this._outputLengthHandler, this);
  585. this._rendermime = null;
  586. this._output = null;
  587. this._outputWrapper = null;
  588. this._outputCollapser = null;
  589. this._outputPlaceholder = null;
  590. super.dispose();
  591. }
  592. /**
  593. * Handle `update-request` messages.
  594. */
  595. protected onUpdateRequest(msg: Message): void {
  596. let value = this.model.metadata.get('collapsed') as boolean;
  597. this.toggleClass(COLLAPSED_CLASS, value);
  598. if (this._output) {
  599. // TODO: handle scrolled state.
  600. }
  601. super.onUpdateRequest(msg);
  602. }
  603. /**
  604. * Handle changes in the model.
  605. */
  606. protected onStateChanged(model: ICellModel, args: IChangedArgs<any>): void {
  607. switch (args.name) {
  608. case 'executionCount':
  609. this.setPrompt(`${(model as ICodeCellModel).executionCount || ''}`);
  610. break;
  611. default:
  612. break;
  613. }
  614. }
  615. /**
  616. * Handle changes in the metadata.
  617. */
  618. protected onMetadataChanged(model: IObservableMap<JSONValue>, args: IObservableMap.IChangedArgs<JSONValue>): void {
  619. switch (args.key) {
  620. case 'collapsed':
  621. case 'scrolled':
  622. this.update();
  623. break;
  624. default:
  625. break;
  626. }
  627. }
  628. /**
  629. * Handle changes in the number of outputs in the output area.
  630. */
  631. private _outputLengthHandler(sender: OutputArea, args: number) {
  632. let force = args === 0 ? true : false;
  633. this.toggleClass(NO_OUTPUTS_CLASS, force);
  634. /* Turn off scrolling outputs if there are none */
  635. if (force) {
  636. this.outputsScrolled = false;
  637. }
  638. }
  639. private _rendermime: RenderMimeRegistry = null;
  640. private _outputHidden = false;
  641. private _outputsScrolled = false;
  642. private _outputWrapper: Widget = null;
  643. private _outputCollapser: OutputCollapser = null;
  644. private _outputPlaceholder: OutputPlaceholder = null;
  645. private _output: OutputArea = null;
  646. }
  647. /**
  648. * The namespace for the `CodeCell` class statics.
  649. */
  650. export
  651. namespace CodeCell {
  652. /**
  653. * An options object for initializing a base cell widget.
  654. */
  655. export
  656. interface IOptions extends Cell.IOptions {
  657. /**
  658. * The model used by the cell.
  659. */
  660. model: ICodeCellModel;
  661. /**
  662. * The mime renderer for the cell widget.
  663. */
  664. rendermime: RenderMimeRegistry;
  665. }
  666. /**
  667. * Execute a cell given a client session.
  668. */
  669. export
  670. function execute(cell: CodeCell, session: IClientSession): Promise<KernelMessage.IExecuteReplyMsg> {
  671. let model = cell.model;
  672. let code = model.value.text;
  673. if (!code.trim() || !session.kernel) {
  674. model.executionCount = null;
  675. model.outputs.clear();
  676. return Promise.resolve(void 0);
  677. }
  678. model.executionCount = null;
  679. cell.outputHidden = false;
  680. cell.setPrompt('*');
  681. model.trusted = true;
  682. return OutputArea.execute(code, cell.outputArea, session).then(msg => {
  683. model.executionCount = msg.content.execution_count;
  684. return msg;
  685. }).catch(e => {
  686. if (e.message === 'Canceled') {
  687. cell.setPrompt('');
  688. }
  689. throw e;
  690. });
  691. }
  692. }
  693. /******************************************************************************
  694. * MarkdownCell
  695. ******************************************************************************/
  696. /**
  697. * A widget for a Markdown cell.
  698. *
  699. * #### Notes
  700. * Things get complicated if we want the rendered text to update
  701. * any time the text changes, the text editor model changes,
  702. * or the input area model changes. We don't support automatically
  703. * updating the rendered text in all of these cases.
  704. */
  705. export
  706. class MarkdownCell extends Cell {
  707. /**
  708. * Construct a Markdown cell widget.
  709. */
  710. constructor(options: MarkdownCell.IOptions) {
  711. super(options);
  712. this.addClass(MARKDOWN_CELL_CLASS);
  713. this._rendermime = options.rendermime;
  714. // Throttle the rendering rate of the widget.
  715. this._monitor = new ActivityMonitor({
  716. signal: this.model.contentChanged,
  717. timeout: RENDER_TIMEOUT
  718. });
  719. this._monitor.activityStopped.connect(() => {
  720. if (this._rendered) {
  721. this.update();
  722. }
  723. }, this);
  724. this._updateRenderedInput().then(() => {
  725. this._ready.resolve(void 0);
  726. });
  727. }
  728. /**
  729. * The model used by the widget.
  730. */
  731. readonly model: IMarkdownCellModel;
  732. /**
  733. * A promise that resolves when the widget renders for the first time.
  734. */
  735. get ready(): Promise<void> {
  736. return this._ready.promise;
  737. }
  738. /**
  739. * Whether the cell is rendered.
  740. */
  741. get rendered(): boolean {
  742. return this._rendered;
  743. }
  744. set rendered(value: boolean) {
  745. if (value === this._rendered) {
  746. return;
  747. }
  748. this._rendered = value;
  749. this._handleRendered();
  750. }
  751. /**
  752. * Render an input instead of the text editor.
  753. */
  754. protected renderInput(widget: Widget): void {
  755. this.addClass(RENDERED_CLASS);
  756. this.inputArea.renderInput(widget);
  757. }
  758. /**
  759. * Show the text editor instead of rendered input.
  760. */
  761. protected showEditor(): void {
  762. this.removeClass(RENDERED_CLASS);
  763. this.inputArea.showEditor();
  764. }
  765. /*
  766. * Handle `update-request` messages.
  767. */
  768. protected onUpdateRequest(msg: Message): void {
  769. // Make sure we are properly rendered.
  770. this._handleRendered();
  771. super.onUpdateRequest(msg);
  772. }
  773. /**
  774. * Handle the rendered state.
  775. */
  776. private _handleRendered(): void {
  777. if (!this._rendered) {
  778. this.showEditor();
  779. } else {
  780. this._updateRenderedInput();
  781. this.renderInput(this._renderer);
  782. }
  783. }
  784. /**
  785. * Update the rendered input.
  786. */
  787. private _updateRenderedInput(): Promise<void> {
  788. let model = this.model;
  789. let text = model && model.value.text || DEFAULT_MARKDOWN_TEXT;
  790. // Do not re-render if the text has not changed.
  791. if (text !== this._prevText) {
  792. let mimeModel = new MimeModel({ data: { 'text/markdown': text }});
  793. if (!this._renderer) {
  794. this._renderer = this._rendermime.createRenderer('text/markdown');
  795. this._renderer.addClass(MARKDOWN_OUTPUT_CLASS);
  796. }
  797. this._prevText = text;
  798. return this._renderer.renderModel(mimeModel);
  799. }
  800. return Promise.resolve(void 0);
  801. }
  802. /**
  803. * Clone the cell, using the same model.
  804. */
  805. clone(): MarkdownCell {
  806. let constructor = this.constructor as typeof MarkdownCell;
  807. return new constructor({
  808. model: this.model,
  809. contentFactory: this.contentFactory,
  810. rendermime: this._rendermime
  811. });
  812. }
  813. private _monitor: ActivityMonitor<any, any> = null;
  814. private _renderer: IRenderMime.IRenderer = null;
  815. private _rendermime: RenderMimeRegistry;
  816. private _rendered = true;
  817. private _prevText = '';
  818. private _ready = new PromiseDelegate<void>();
  819. }
  820. /**
  821. * The namespace for the `CodeCell` class statics.
  822. */
  823. export
  824. namespace MarkdownCell {
  825. /**
  826. * An options object for initializing a base cell widget.
  827. */
  828. export
  829. interface IOptions extends Cell.IOptions {
  830. /**
  831. * The model used by the cell.
  832. */
  833. model: IMarkdownCellModel;
  834. /**
  835. * The mime renderer for the cell widget.
  836. */
  837. rendermime: RenderMimeRegistry;
  838. }
  839. }
  840. /******************************************************************************
  841. * RawCell
  842. ******************************************************************************/
  843. /**
  844. * A widget for a raw cell.
  845. */
  846. export
  847. class RawCell extends Cell {
  848. /**
  849. * Construct a raw cell widget.
  850. */
  851. constructor(options: Cell.IOptions) {
  852. super(options);
  853. this.addClass(RAW_CELL_CLASS);
  854. }
  855. /**
  856. * Clone the cell, using the same model.
  857. */
  858. clone(): RawCell {
  859. let constructor = this.constructor as typeof RawCell;
  860. return new constructor({
  861. model: this.model,
  862. contentFactory: this.contentFactory
  863. });
  864. }
  865. /**
  866. * The model used by the widget.
  867. */
  868. readonly model: IRawCellModel;
  869. }
  870. /**
  871. * The namespace for the `RawCell` class statics.
  872. */
  873. export
  874. namespace RawCell {
  875. /**
  876. * An options object for initializing a base cell widget.
  877. */
  878. export
  879. interface IOptions extends Cell.IOptions {
  880. /**
  881. * The model used by the cell.
  882. */
  883. model: IRawCellModel;
  884. }
  885. }