widget.ts 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import {
  4. IKernel, KernelMessage
  5. } from 'jupyter-js-services';
  6. import {
  7. defineSignal, ISignal
  8. } from 'phosphor/lib/core/signaling';
  9. import {
  10. Message
  11. } from 'phosphor/lib/core/messaging';
  12. import {
  13. PanelLayout
  14. } from 'phosphor/lib/ui/panel';
  15. import {
  16. Widget
  17. } from 'phosphor/lib/ui/widget';
  18. import {
  19. IChangedArgs
  20. } from '../../common/interfaces';
  21. import {
  22. RenderMime
  23. } from '../../rendermime';
  24. import {
  25. IMetadataCursor
  26. } from '../common/metadata';
  27. import {
  28. OutputAreaWidget
  29. } from '../output-area';
  30. import {
  31. ICellEditorWidget
  32. } from './editor';
  33. import {
  34. ICellModel, ICodeCellModel,
  35. IMarkdownCellModel, IRawCellModel
  36. } from './model';
  37. /**
  38. * The class name added to cell widgets.
  39. */
  40. const CELL_CLASS = 'jp-Cell';
  41. /**
  42. * The class name added to the prompt area of cell.
  43. */
  44. const PROMPT_CLASS = 'jp-Cell-prompt';
  45. /**
  46. * The class name added to input area widgets.
  47. */
  48. const INPUT_CLASS = 'jp-InputArea';
  49. /**
  50. * The class name added to the editor area of the cell.
  51. */
  52. const EDITOR_CLASS = 'jp-InputArea-editor';
  53. /**
  54. * The class name added to the cell when collapsed.
  55. */
  56. const COLLAPSED_CLASS = 'jp-mod-collapsed';
  57. /**
  58. * The class name added to the cell when readonly.
  59. */
  60. const READONLY_CLASS = 'jp-mod-readOnly';
  61. /**
  62. * The class name added to code cells.
  63. */
  64. const CODE_CELL_CLASS = 'jp-CodeCell';
  65. /**
  66. * The class name added to markdown cells.
  67. */
  68. const MARKDOWN_CELL_CLASS = 'jp-MarkdownCell';
  69. /**
  70. * The class name added to rendered markdown output widgets.
  71. */
  72. const MARKDOWN_OUTPUT_CLASS = 'jp-MarkdownOutput';
  73. /**
  74. * The class name added to the rendered markdown widget content.
  75. */
  76. const MARKDOWN_CONTENT_CLASS = 'jp-MarkdownOutput-content';
  77. /**
  78. * The class name added to raw cells.
  79. */
  80. const RAW_CELL_CLASS = 'jp-RawCell';
  81. /**
  82. * The class name added to a rendered markdown cell.
  83. */
  84. const RENDERED_CLASS = 'jp-mod-rendered';
  85. /**
  86. * The text applied to an empty markdown cell.
  87. */
  88. const DEFAULT_MARKDOWN_TEXT = 'Type Markdown and LaTeX: $ α^2 $';
  89. /**
  90. * A base cell widget.
  91. */
  92. export
  93. class BaseCellWidget extends Widget {
  94. /**
  95. * Construct a new base cell widget.
  96. */
  97. constructor(options: BaseCellWidget.IOptions) {
  98. super();
  99. this.addClass(CELL_CLASS);
  100. this.layout = new PanelLayout();
  101. let renderer = options.renderer;
  102. this._editor = renderer.createCellEditor();
  103. this._input = renderer.createInputArea(this._editor);
  104. (this.layout as PanelLayout).addWidget(this._input);
  105. }
  106. /**
  107. * A signal emitted when the model of the cell changes.
  108. */
  109. modelChanged: ISignal<BaseCellWidget, void>;
  110. /**
  111. * The model used by the widget.
  112. */
  113. get model(): ICellModel {
  114. return this._model;
  115. }
  116. set model(newValue: ICellModel) {
  117. if (!newValue && !this._model || newValue === this._model) {
  118. return;
  119. }
  120. let oldValue: ICellModel = this._model;
  121. this._model = newValue;
  122. // Trigger private, protected, and public updates.
  123. this._onModelChanged(oldValue, newValue);
  124. this.onModelChanged(oldValue, newValue);
  125. this.modelChanged.emit(void 0);
  126. }
  127. /**
  128. * Get the editor widget used by the cell.
  129. *
  130. * #### Notes
  131. * This is a ready-only property.
  132. */
  133. get editor(): ICellEditorWidget {
  134. return this._editor;
  135. }
  136. /**
  137. * The mimetype used by the cell.
  138. */
  139. get mimetype(): string {
  140. return this._mimetype;
  141. }
  142. set mimetype(value: string) {
  143. if (!value) {
  144. return;
  145. }
  146. if (this._mimetype === value) {
  147. return;
  148. }
  149. this._mimetype = value;
  150. this.editor.setMimeType(value);
  151. }
  152. /**
  153. * The read only state of the cell.
  154. */
  155. get readOnly(): boolean {
  156. return this._readOnly;
  157. }
  158. set readOnly(value: boolean) {
  159. if (value === this._readOnly) {
  160. return;
  161. }
  162. this._readOnly = value;
  163. this.update();
  164. }
  165. /**
  166. * The trusted state of the cell.
  167. */
  168. get trusted(): boolean {
  169. return this._trusted;
  170. }
  171. set trusted(value: boolean) {
  172. if (!this._model) {
  173. return;
  174. }
  175. this._trustedCursor.setValue(value);
  176. this._trusted = value;
  177. }
  178. /**
  179. * Set the prompt for the widget.
  180. */
  181. setPrompt(value: string): void {
  182. this._input.setPrompt(value);
  183. }
  184. /**
  185. * Toggle whether the input area is shown.
  186. */
  187. toggleInput(value: boolean): void {
  188. if (value) {
  189. this._input.show();
  190. } else {
  191. this._input.hide();
  192. }
  193. }
  194. /**
  195. * Dispose of the resources held by the widget.
  196. */
  197. dispose() {
  198. // Do nothing if already disposed.
  199. if (this.isDisposed) {
  200. return;
  201. }
  202. this._model = null;
  203. this._input = null;
  204. this._editor = null;
  205. this._trustedCursor = null;
  206. super.dispose();
  207. }
  208. /**
  209. * Handle `after-attach` messages.
  210. */
  211. protected onAfterAttach(msg: Message): void {
  212. this.update();
  213. }
  214. /**
  215. * Handle `'activate-request'` messages.
  216. */
  217. protected onActivateRequest(msg: Message): void {
  218. this._editor.activate();
  219. }
  220. /**
  221. * Handle `update_request` messages.
  222. */
  223. protected onUpdateRequest(msg: Message): void {
  224. if (!this._model) {
  225. return;
  226. }
  227. // Handle read only state.
  228. this._editor.setReadOnly(this._readOnly);
  229. this.toggleClass(READONLY_CLASS, this._readOnly);
  230. }
  231. /**
  232. * Handle changes in the model.
  233. *
  234. * #### Notes
  235. * Subclasses may reimplement this method as needed.
  236. */
  237. protected onModelStateChanged(model: ICellModel, args: IChangedArgs<any>): void {
  238. // no-op
  239. }
  240. /**
  241. * Handle changes in the model.
  242. */
  243. protected onMetadataChanged(model: ICellModel, args: IChangedArgs<any>): void {
  244. switch (args.name) {
  245. case 'trusted':
  246. this._trusted = !!this._trustedCursor.getValue();
  247. this.update();
  248. break;
  249. default:
  250. break;
  251. }
  252. }
  253. /**
  254. * Handle a new model.
  255. *
  256. * #### Notes
  257. * This method is called after the model change has been handled
  258. * internally and before the `modelChanged` signal is emitted.
  259. * The default implementation is a no-op.
  260. */
  261. protected onModelChanged(oldValue: ICellModel, newValue: ICellModel): void {
  262. // no-op
  263. }
  264. private _onModelChanged(oldValue: ICellModel, newValue: ICellModel): void {
  265. // If the model is being replaced, disconnect the old signal handler.
  266. if (oldValue) {
  267. oldValue.stateChanged.disconnect(this.onModelStateChanged, this);
  268. oldValue.metadataChanged.disconnect(this.onMetadataChanged, this);
  269. }
  270. // Reset the editor model and set its mode to be the default MIME type.
  271. this._editor.model = this._model;
  272. this._editor.setMimeType(this._mimetype);
  273. // Handle trusted cursor.
  274. this._trustedCursor = this._model.getMetadata('trusted');
  275. this._trusted = !!this._trustedCursor.getValue();
  276. // Connect signal handlers.
  277. this._model.metadataChanged.connect(this.onMetadataChanged, this);
  278. this._model.stateChanged.connect(this.onModelStateChanged, this);
  279. }
  280. private _input: InputAreaWidget = null;
  281. private _editor: ICellEditorWidget = null;
  282. private _model: ICellModel = null;
  283. private _mimetype = 'text/plain';
  284. private _readOnly = false;
  285. private _trustedCursor: IMetadataCursor = null;
  286. private _trusted = false;
  287. }
  288. // Define the signals for the `BaseCellWidget` class.
  289. defineSignal(BaseCellWidget.prototype, 'modelChanged');
  290. /**
  291. * The namespace for the `BaseCellWidget` class statics.
  292. */
  293. export
  294. namespace BaseCellWidget {
  295. /**
  296. * An options object for initializing a base cell widget.
  297. */
  298. export
  299. interface IOptions {
  300. /**
  301. * A renderer for creating cell widgets.
  302. *
  303. * The default is a shared renderer instance.
  304. */
  305. renderer: IRenderer;
  306. }
  307. /**
  308. * A renderer for creating cell widgets.
  309. */
  310. export
  311. interface IRenderer {
  312. /**
  313. * Create a new cell editor for the widget.
  314. */
  315. createCellEditor(): ICellEditorWidget;
  316. /**
  317. * Create a new input area for the widget.
  318. */
  319. createInputArea(editor: ICellEditorWidget): InputAreaWidget;
  320. }
  321. /**
  322. * The default implementation of an `IRenderer`.
  323. */
  324. export
  325. abstract class Renderer implements IRenderer {
  326. /**
  327. * Create a new cell editor for the widget.
  328. */
  329. abstract createCellEditor(): ICellEditorWidget;
  330. /**
  331. * Create a new input area for the widget.
  332. */
  333. createInputArea(editor: ICellEditorWidget): InputAreaWidget {
  334. return new InputAreaWidget(editor);
  335. }
  336. }
  337. }
  338. /**
  339. * A widget for a code cell.
  340. */
  341. export
  342. class CodeCellWidget extends BaseCellWidget {
  343. /**
  344. * Construct a code cell widget.
  345. */
  346. constructor(options: CodeCellWidget.IOptions) {
  347. super(options);
  348. this.addClass(CODE_CELL_CLASS);
  349. this._rendermime = options.rendermime;
  350. this._renderer = options.renderer;
  351. }
  352. /**
  353. * The model used by the widget.
  354. */
  355. model: ICodeCellModel;
  356. /**
  357. * Dispose of the resources used by the widget.
  358. */
  359. dispose(): void {
  360. if (this.isDisposed) {
  361. return;
  362. }
  363. this._collapsedCursor = null;
  364. this._scrolledCursor = null;
  365. this._output = null;
  366. super.dispose();
  367. }
  368. /**
  369. * Execute the cell given a kernel.
  370. */
  371. execute(kernel: IKernel): Promise<KernelMessage.IExecuteReplyMsg> {
  372. let model = this.model;
  373. let code = model.source;
  374. if (!code.trim()) {
  375. model.executionCount = null;
  376. model.outputs.clear();
  377. return Promise.resolve(null);
  378. }
  379. model.executionCount = null;
  380. this.setPrompt('*');
  381. this.trusted = true;
  382. let outputs = model.outputs;
  383. return outputs.execute(code, kernel).then(reply => {
  384. let status = reply.content.status;
  385. if (status === 'abort') {
  386. model.executionCount = null;
  387. this.setPrompt(' ');
  388. } else {
  389. model.executionCount = reply.content.execution_count;
  390. }
  391. return reply;
  392. });
  393. }
  394. /**
  395. * Handle `update_request` messages.
  396. */
  397. protected onUpdateRequest(msg: Message): void {
  398. if (this._collapsedCursor) {
  399. this.toggleClass(COLLAPSED_CLASS, this._collapsedCursor.getValue());
  400. }
  401. if (this._output) {
  402. // TODO: handle scrolled state.
  403. this._output.trusted = this.trusted;
  404. }
  405. super.onUpdateRequest(msg);
  406. }
  407. /**
  408. * Handle the widget receiving a new model.
  409. */
  410. protected onModelChanged(oldValue: ICellModel, newValue: ICellModel): void {
  411. let model = newValue as ICodeCellModel;
  412. let renderer = this._renderer;
  413. if (!this._output) {
  414. this._output = renderer.createOutputArea(this._rendermime);
  415. (this.layout as PanelLayout).addWidget(this._output);
  416. }
  417. this._output.model = model.outputs;
  418. this._output.trusted = this.trusted;
  419. this._collapsedCursor = model.getMetadata('collapsed');
  420. this._scrolledCursor = model.getMetadata('scrolled');
  421. this.setPrompt(`${model.executionCount}`);
  422. }
  423. /**
  424. * Handle changes in the model.
  425. */
  426. protected onModelStateChanged(model: ICellModel, args: IChangedArgs<any>): void {
  427. switch (args.name) {
  428. case 'executionCount':
  429. this.setPrompt(`${(model as ICodeCellModel).executionCount}`);
  430. break;
  431. default:
  432. break;
  433. }
  434. super.onModelStateChanged(model, args);
  435. }
  436. /**
  437. * Handle changes in the metadata.
  438. */
  439. protected onMetadataChanged(model: ICellModel, args: IChangedArgs<any>): void {
  440. switch (args.name) {
  441. case 'collapsed':
  442. case 'scrolled':
  443. this.update();
  444. break;
  445. default:
  446. break;
  447. }
  448. super.onMetadataChanged(model, args);
  449. }
  450. private _renderer: CodeCellWidget.IRenderer;
  451. private _rendermime: RenderMime = null;
  452. private _output: OutputAreaWidget = null;
  453. private _collapsedCursor: IMetadataCursor = null;
  454. private _scrolledCursor: IMetadataCursor = null;
  455. }
  456. /**
  457. * The namespace for the `CodeCellWidget` class statics.
  458. */
  459. export
  460. namespace CodeCellWidget {
  461. /**
  462. * An options object for initializing a base cell widget.
  463. */
  464. export
  465. interface IOptions {
  466. /**
  467. * A renderer for creating cell widgets.
  468. *
  469. * The default is a shared renderer instance.
  470. */
  471. renderer: IRenderer;
  472. /**
  473. * The mime renderer for the cell widget.
  474. */
  475. rendermime: RenderMime;
  476. }
  477. /**
  478. * A renderer for creating code cell widgets.
  479. */
  480. export
  481. interface IRenderer extends BaseCellWidget.IRenderer {
  482. /**
  483. * Create a new output area for the widget.
  484. */
  485. createOutputArea(rendermime: RenderMime): OutputAreaWidget;
  486. }
  487. /**
  488. * The default implementation of an `IRenderer`.
  489. */
  490. export
  491. abstract class Renderer extends BaseCellWidget.Renderer implements IRenderer {
  492. /**
  493. * Create an output area widget.
  494. */
  495. createOutputArea(rendermime: RenderMime): OutputAreaWidget {
  496. return new OutputAreaWidget({ rendermime });
  497. }
  498. }
  499. }
  500. /**
  501. * A widget for a Markdown cell.
  502. *
  503. * #### Notes
  504. * Things get complicated if we want the rendered text to update
  505. * any time the text changes, the text editor model changes,
  506. * or the input area model changes. We don't support automatically
  507. * updating the rendered text in all of these cases.
  508. */
  509. export
  510. class MarkdownCellWidget extends BaseCellWidget {
  511. /**
  512. * Construct a Markdown cell widget.
  513. */
  514. constructor(options: MarkdownCellWidget.IOptions) {
  515. super(options);
  516. this.addClass(MARKDOWN_CELL_CLASS);
  517. // Insist on the Github-flavored markdown mode.
  518. this.mimetype = 'text/x-ipythongfm';
  519. this._rendermime = options.rendermime;
  520. this._output = new MarkdownOutput();
  521. (this.layout as PanelLayout).addWidget(this._output);
  522. }
  523. /**
  524. * The model used by the widget.
  525. */
  526. model: IMarkdownCellModel;
  527. /**
  528. * Whether the cell is rendered.
  529. */
  530. get rendered(): boolean {
  531. return this._rendered;
  532. }
  533. set rendered(value: boolean) {
  534. if (value === this._rendered) {
  535. return;
  536. }
  537. this._rendered = value;
  538. this.update();
  539. }
  540. /**
  541. * Dispose of the resource held by the widget.
  542. */
  543. dispose(): void {
  544. if (this.isDisposed) {
  545. return;
  546. }
  547. this._output = null;
  548. super.dispose();
  549. }
  550. /**
  551. * Handle `update_request` messages.
  552. */
  553. protected onUpdateRequest(msg: Message): void {
  554. let model = this.model;
  555. if (this.rendered) {
  556. let text = model && model.source || DEFAULT_MARKDOWN_TEXT;
  557. // Do not re-render if the text has not changed.
  558. if (text !== this._prev) {
  559. let bundle: RenderMime.MimeMap<string> = { 'text/markdown': text };
  560. let trusted = this.trusted;
  561. let widget = this._rendermime.render({ bundle, trusted });
  562. this._output.content = widget || new Widget();
  563. this.update();
  564. } else {
  565. this._output.show();
  566. }
  567. this._prev = text;
  568. this.toggleInput(false);
  569. this.addClass(RENDERED_CLASS);
  570. } else {
  571. this._output.hide();
  572. this.toggleInput(true);
  573. this.removeClass(RENDERED_CLASS);
  574. }
  575. super.onUpdateRequest(msg);
  576. }
  577. private _rendermime: RenderMime = null;
  578. private _output: MarkdownOutput = null;
  579. private _rendered = true;
  580. private _prev = '';
  581. }
  582. /**
  583. * The namespace for the `CodeCellWidget` class statics.
  584. */
  585. export
  586. namespace MarkdownCellWidget {
  587. /**
  588. * An options object for initializing a base cell widget.
  589. */
  590. export
  591. interface IOptions {
  592. /**
  593. * A renderer for creating cell widgets.
  594. *
  595. * The default is a shared renderer instance.
  596. */
  597. renderer: BaseCellWidget.IRenderer;
  598. /**
  599. * The mime renderer for the cell widget.
  600. */
  601. rendermime: RenderMime;
  602. }
  603. }
  604. /**
  605. * A widget that contains rendered markdown output.
  606. */
  607. class MarkdownOutput extends Widget {
  608. /**
  609. * Construct a new markdown output.
  610. */
  611. constructor() {
  612. super();
  613. this.addClass(MARKDOWN_OUTPUT_CLASS);
  614. let prompt = new Widget();
  615. prompt.addClass(PROMPT_CLASS);
  616. let layout = this.layout = new PanelLayout();
  617. layout.addWidget(prompt);
  618. layout.addWidget(new Widget());
  619. }
  620. /**
  621. * The prompt used by the output.
  622. */
  623. get prompt(): Widget {
  624. return (this.layout as PanelLayout).widgets.at(0);
  625. }
  626. /**
  627. * The content used by the widget.
  628. */
  629. get content(): Widget {
  630. return (this.layout as PanelLayout).widgets.at(1);
  631. }
  632. set content(widget: Widget) {
  633. widget.addClass(MARKDOWN_CONTENT_CLASS);
  634. let layout = this.layout as PanelLayout;
  635. layout.removeWidget(this.content);
  636. layout.addWidget(widget);
  637. }
  638. }
  639. /**
  640. * A widget for a raw cell.
  641. */
  642. export
  643. class RawCellWidget extends BaseCellWidget {
  644. /**
  645. * Construct a raw cell widget.
  646. */
  647. constructor(options: BaseCellWidget.IOptions) {
  648. super(options);
  649. this.addClass(RAW_CELL_CLASS);
  650. }
  651. /**
  652. * The model used by the widget.
  653. */
  654. model: IRawCellModel;
  655. }
  656. /**
  657. * An input area widget, which hosts a prompt and an editor widget.
  658. */
  659. export
  660. class InputAreaWidget extends Widget {
  661. /**
  662. * Construct an input area widget.
  663. */
  664. constructor(editor: ICellEditorWidget) {
  665. super();
  666. this.addClass(INPUT_CLASS);
  667. editor.addClass(EDITOR_CLASS);
  668. this.layout = new PanelLayout();
  669. let prompt = new Widget();
  670. prompt.addClass(PROMPT_CLASS);
  671. let layout = this.layout as PanelLayout;
  672. layout.addWidget(prompt);
  673. layout.addWidget(editor);
  674. }
  675. /**
  676. * Set the prompt of the input area.
  677. */
  678. setPrompt(value: string): void {
  679. let prompt = (this.layout as PanelLayout).widgets.at(0);
  680. if (value === 'null') {
  681. value = ' ';
  682. }
  683. let text = `In [${value || ' '}]:`;
  684. prompt.node.textContent = text;
  685. }
  686. }