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