widget.ts 16 KB

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