default.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import {
  4. Mode
  5. } from '@jupyterlab/codemirror';
  6. import {
  7. Contents
  8. } from '@jupyterlab/services';
  9. import {
  10. JSONObject, JSONValue, PromiseDelegate
  11. } from '@phosphor/coreutils';
  12. import {
  13. Message
  14. } from '@phosphor/messaging';
  15. import {
  16. ISignal, Signal
  17. } from '@phosphor/signaling';
  18. import {
  19. PanelLayout, Widget
  20. } from '@phosphor/widgets';
  21. import {
  22. CodeEditor
  23. } from '@jupyterlab/codeeditor';
  24. import {
  25. ActivityMonitor, IChangedArgs, IModelDB
  26. } from '@jupyterlab/coreutils';
  27. import {
  28. IRenderMime, RenderMime, MimeModel
  29. } from '@jupyterlab/rendermime';
  30. import {
  31. DocumentRegistry
  32. } from './index';
  33. /**
  34. * The default implementation of a document model.
  35. */
  36. export
  37. class DocumentModel extends CodeEditor.Model implements DocumentRegistry.ICodeModel {
  38. /**
  39. * Construct a new document model.
  40. */
  41. constructor(languagePreference?: string, modelDB?: IModelDB) {
  42. super({modelDB});
  43. this._defaultLang = languagePreference || '';
  44. this.value.changed.connect(this.triggerContentChange, this);
  45. }
  46. /**
  47. * A signal emitted when the document content changes.
  48. */
  49. get contentChanged(): ISignal<this, void> {
  50. return this._contentChanged;
  51. }
  52. /**
  53. * A signal emitted when the document state changes.
  54. */
  55. get stateChanged(): ISignal<this, IChangedArgs<any>> {
  56. return this._stateChanged;
  57. }
  58. /**
  59. * The dirty state of the document.
  60. */
  61. get dirty(): boolean {
  62. return this._dirty;
  63. }
  64. set dirty(newValue: boolean) {
  65. if (newValue === this._dirty) {
  66. return;
  67. }
  68. let oldValue = this._dirty;
  69. this._dirty = newValue;
  70. this.triggerStateChange({ name: 'dirty', oldValue, newValue });
  71. }
  72. /**
  73. * The read only state of the document.
  74. */
  75. get readOnly(): boolean {
  76. return this._readOnly;
  77. }
  78. set readOnly(newValue: boolean) {
  79. if (newValue === this._readOnly) {
  80. return;
  81. }
  82. let oldValue = this._readOnly;
  83. this._readOnly = newValue;
  84. this.triggerStateChange({ name: 'readOnly', oldValue, newValue });
  85. }
  86. /**
  87. * The default kernel name of the document.
  88. *
  89. * #### Notes
  90. * This is a read-only property.
  91. */
  92. get defaultKernelName(): string {
  93. return '';
  94. }
  95. /**
  96. * The default kernel language of the document.
  97. *
  98. * #### Notes
  99. * This is a read-only property.
  100. */
  101. get defaultKernelLanguage(): string {
  102. return this._defaultLang;
  103. }
  104. /**
  105. * Serialize the model to a string.
  106. */
  107. toString(): string {
  108. return this.value.text;
  109. }
  110. /**
  111. * Deserialize the model from a string.
  112. *
  113. * #### Notes
  114. * Should emit a [contentChanged] signal.
  115. */
  116. fromString(value: string): void {
  117. this.value.text = value;
  118. }
  119. /**
  120. * Serialize the model to JSON.
  121. */
  122. toJSON(): JSONValue {
  123. return JSON.parse(this.value.text);
  124. }
  125. /**
  126. * Deserialize the model from JSON.
  127. *
  128. * #### Notes
  129. * Should emit a [contentChanged] signal.
  130. */
  131. fromJSON(value: JSONValue): void {
  132. this.fromString(JSON.stringify(value));
  133. }
  134. /**
  135. * Trigger a state change signal.
  136. */
  137. protected triggerStateChange(args: IChangedArgs<any>): void {
  138. this._stateChanged.emit(args);
  139. }
  140. /**
  141. * Trigger a content changed signal.
  142. */
  143. protected triggerContentChange(): void {
  144. this._contentChanged.emit(void 0);
  145. this.dirty = true;
  146. }
  147. private _defaultLang = '';
  148. private _dirty = false;
  149. private _readOnly = false;
  150. private _contentChanged = new Signal<this, void>(this);
  151. private _stateChanged = new Signal<this, IChangedArgs<any>>(this);
  152. }
  153. /**
  154. * An implementation of a model factory for text files.
  155. */
  156. export
  157. class TextModelFactory implements DocumentRegistry.CodeModelFactory {
  158. /**
  159. * The name of the model type.
  160. *
  161. * #### Notes
  162. * This is a read-only property.
  163. */
  164. get name(): string {
  165. return 'text';
  166. }
  167. /**
  168. * The type of the file.
  169. *
  170. * #### Notes
  171. * This is a read-only property.
  172. */
  173. get contentType(): Contents.ContentType {
  174. return 'file';
  175. }
  176. /**
  177. * The format of the file.
  178. *
  179. * This is a read-only property.
  180. */
  181. get fileFormat(): Contents.FileFormat {
  182. return 'text';
  183. }
  184. /**
  185. * Get whether the model factory has been disposed.
  186. */
  187. get isDisposed(): boolean {
  188. return this._isDisposed;
  189. }
  190. /**
  191. * Dispose of the resources held by the model factory.
  192. */
  193. dispose(): void {
  194. this._isDisposed = true;
  195. }
  196. /**
  197. * Create a new model.
  198. *
  199. * @param languagePreference - An optional kernel language preference.
  200. *
  201. * @returns A new document model.
  202. */
  203. createNew(languagePreference?: string, modelDB?: IModelDB): DocumentRegistry.ICodeModel {
  204. return new DocumentModel(languagePreference, modelDB);
  205. }
  206. /**
  207. * Get the preferred kernel language given an extension.
  208. */
  209. preferredLanguage(ext: string): string {
  210. let mode = Mode.findByExtension(ext.slice(1));
  211. return mode && mode.mode;
  212. }
  213. private _isDisposed = false;
  214. }
  215. /**
  216. * An implementation of a model factory for base64 files.
  217. */
  218. export
  219. class Base64ModelFactory extends TextModelFactory {
  220. /**
  221. * The name of the model type.
  222. *
  223. * #### Notes
  224. * This is a read-only property.
  225. */
  226. get name(): string {
  227. return 'base64';
  228. }
  229. /**
  230. * The type of the file.
  231. *
  232. * #### Notes
  233. * This is a read-only property.
  234. */
  235. get contentType(): Contents.ContentType {
  236. return 'file';
  237. }
  238. /**
  239. * The format of the file.
  240. *
  241. * This is a read-only property.
  242. */
  243. get fileFormat(): Contents.FileFormat {
  244. return 'base64';
  245. }
  246. }
  247. /**
  248. * The default implemetation of a widget factory.
  249. */
  250. export
  251. abstract class ABCWidgetFactory<T extends DocumentRegistry.IReadyWidget, U extends DocumentRegistry.IModel> implements DocumentRegistry.IWidgetFactory<T, U> {
  252. /**
  253. * Construct a new `ABCWidgetFactory`.
  254. */
  255. constructor(options: DocumentRegistry.IWidgetFactoryOptions) {
  256. this._name = options.name;
  257. this._readOnly = options.readOnly === undefined ? false : options.readOnly;
  258. this._defaultFor = options.defaultFor ? options.defaultFor.slice() : [];
  259. this._fileExtensions = options.fileExtensions.slice();
  260. this._modelName = options.modelName || 'text';
  261. this._preferKernel = !!options.preferKernel;
  262. this._canStartKernel = !!options.canStartKernel;
  263. }
  264. /**
  265. * A signal emitted when a widget is created.
  266. */
  267. get widgetCreated(): ISignal<DocumentRegistry.IWidgetFactory<T, U>, T> {
  268. return this._widgetCreated;
  269. }
  270. /**
  271. * Get whether the model factory has been disposed.
  272. */
  273. get isDisposed(): boolean {
  274. return this._isDisposed;
  275. }
  276. /**
  277. * Dispose of the resources held by the document manager.
  278. */
  279. dispose(): void {
  280. this._isDisposed = true;
  281. }
  282. /**
  283. * Whether the widget factory is read only.
  284. */
  285. get readOnly(): boolean {
  286. return this._readOnly;
  287. }
  288. /**
  289. * The name of the widget to display in dialogs.
  290. */
  291. get name(): string {
  292. return this._name;
  293. }
  294. /**
  295. * The file extensions the widget can view.
  296. */
  297. get fileExtensions(): string[] {
  298. return this._fileExtensions.slice();
  299. }
  300. /**
  301. * The registered name of the model type used to create the widgets.
  302. */
  303. get modelName(): string {
  304. return this._modelName;
  305. }
  306. /**
  307. * The file extensions for which the factory should be the default.
  308. */
  309. get defaultFor(): string[] {
  310. return this._defaultFor.slice();
  311. }
  312. /**
  313. * Whether the widgets prefer having a kernel started.
  314. */
  315. get preferKernel(): boolean {
  316. return this._preferKernel;
  317. }
  318. /**
  319. * Whether the widgets can start a kernel when opened.
  320. */
  321. get canStartKernel(): boolean {
  322. return this._canStartKernel;
  323. }
  324. /**
  325. * Create a new widget given a document model and a context.
  326. *
  327. * #### Notes
  328. * It should emit the [widgetCreated] signal with the new widget.
  329. */
  330. createNew(context: DocumentRegistry.IContext<U>): T {
  331. let widget = this.createNewWidget(context);
  332. this._widgetCreated.emit(widget);
  333. return widget;
  334. }
  335. /**
  336. * Create a widget for a context.
  337. */
  338. protected abstract createNewWidget(context: DocumentRegistry.IContext<U>): T;
  339. private _isDisposed = false;
  340. private _name: string;
  341. private _readOnly: boolean;
  342. private _canStartKernel: boolean;
  343. private _preferKernel: boolean;
  344. private _modelName: string;
  345. private _fileExtensions: string[];
  346. private _defaultFor: string[];
  347. private _widgetCreated = new Signal<DocumentRegistry.IWidgetFactory<T, U>, T>(this);
  348. }
  349. /**
  350. * A widget for rendered mimetype.
  351. */
  352. export
  353. class MimeRenderer extends Widget implements DocumentRegistry.IReadyWidget {
  354. /**
  355. * Construct a new markdown widget.
  356. */
  357. constructor(options: MimeRenderer.IOptions) {
  358. super();
  359. this.addClass('jp-MimeRenderer');
  360. let layout = this.layout = new PanelLayout();
  361. let toolbar = new Widget();
  362. toolbar.addClass('jp-Toolbar');
  363. layout.addWidget(toolbar);
  364. let context = options.context;
  365. this.title.label = context.path.split('/').pop();
  366. this.rendermime = options.rendermime.clone({ resolver: context });
  367. this._context = context;
  368. this._mimeType = options.mimeType;
  369. this._dataType = options.dataType;
  370. context.pathChanged.connect(this._onPathChanged, this);
  371. this._context.ready.then(() => {
  372. if (this.isDisposed) {
  373. return;
  374. }
  375. return this._render().then();
  376. }).then(() => {
  377. // Throttle the rendering rate of the widget.
  378. this._monitor = new ActivityMonitor({
  379. signal: context.model.contentChanged,
  380. timeout: options.renderTimeout
  381. });
  382. this._monitor.activityStopped.connect(this.update, this);
  383. this._ready.resolve(undefined);
  384. });
  385. }
  386. /**
  387. * The markdown widget's context.
  388. */
  389. get context(): DocumentRegistry.Context {
  390. return this._context;
  391. }
  392. /**
  393. * The rendermime instance associated with the widget.
  394. */
  395. readonly rendermime: RenderMime;
  396. /**
  397. * A promise that resolves when the widget is ready.
  398. */
  399. get ready(): Promise<void> {
  400. return this._ready.promise;
  401. }
  402. /**
  403. * Dispose of the resources held by the widget.
  404. */
  405. dispose(): void {
  406. if (this.isDisposed) {
  407. return;
  408. }
  409. if (this._monitor) {
  410. this._monitor.dispose();
  411. }
  412. super.dispose();
  413. }
  414. /**
  415. * Handle `'activate-request'` messages.
  416. */
  417. protected onActivateRequest(msg: Message): void {
  418. this.node.tabIndex = -1;
  419. this.node.focus();
  420. }
  421. /**
  422. * Handle an `update-request` message to the widget.
  423. */
  424. protected onUpdateRequest(msg: Message): void {
  425. this._render();
  426. }
  427. /**
  428. * Render the mime content.
  429. */
  430. private _render(): Promise<void> {
  431. let context = this._context;
  432. let model = context.model;
  433. let data: JSONObject = {};
  434. if (this._dataType === 'string') {
  435. data[this._mimeType] = model.toString();
  436. } else {
  437. data[this._mimeType] = model.toJSON();
  438. }
  439. let mimeModel = new MimeModel({ data });
  440. if (!this._renderer) {
  441. this._renderer = this.rendermime.createRenderer(this._mimeType);
  442. (this.layout as PanelLayout).addWidget(this._renderer);
  443. }
  444. return this._renderer.renderModel(mimeModel);
  445. }
  446. /**
  447. * Handle a path change.
  448. */
  449. private _onPathChanged(): void {
  450. this.title.label = this._context.path.split('/').pop();
  451. }
  452. private _context: DocumentRegistry.Context = null;
  453. private _monitor: ActivityMonitor<any, any> = null;
  454. private _renderer: IRenderMime.IRenderer;
  455. private _mimeType: string;
  456. private _ready = new PromiseDelegate<void>();
  457. private _dataType: 'string' | 'json';
  458. }
  459. /**
  460. * The namespace for MimeRenderer class statics.
  461. */
  462. export
  463. namespace MimeRenderer {
  464. /**
  465. * The options used to initialize a MimeRenderer.
  466. */
  467. export
  468. interface IOptions {
  469. /**
  470. * The document context.
  471. */
  472. context: DocumentRegistry.Context;
  473. /**
  474. * The rendermime instance.
  475. */
  476. rendermime: RenderMime;
  477. /**
  478. * The mime type.
  479. */
  480. mimeType: string;
  481. /**
  482. * The render timeout.
  483. */
  484. renderTimeout: number;
  485. /**
  486. * Preferred data type from the model.
  487. */
  488. dataType?: 'string' | 'json';
  489. }
  490. }
  491. /**
  492. * An implementation of a widget factory for a rendered mimetype.
  493. */
  494. export
  495. class MimeRendererFactory extends ABCWidgetFactory<MimeRenderer, DocumentRegistry.IModel> {
  496. /**
  497. * Construct a new markdown widget factory.
  498. */
  499. constructor(options: MimeRendererFactory.IOptions) {
  500. super(Private.createRegistryOptions(options));
  501. this._rendermime = options.rendermime;
  502. this._mimeType = options.mimeType;
  503. this._renderTimeout = options.renderTimeout || 1000;
  504. this._dataType = options.dataType || 'string';
  505. this._iconClass = options.iconClass || '';
  506. this._iconLabel = options.iconLabel || '';
  507. }
  508. /**
  509. * Create a new widget given a context.
  510. */
  511. protected createNewWidget(context: DocumentRegistry.Context): MimeRenderer {
  512. let widget = new MimeRenderer({
  513. context,
  514. rendermime: this._rendermime.clone(),
  515. mimeType: this._mimeType,
  516. renderTimeout: this._renderTimeout,
  517. dataType: this._dataType,
  518. });
  519. widget.title.iconClass = this._iconClass;
  520. widget.title.iconLabel = this._iconLabel;
  521. return widget;
  522. }
  523. private _rendermime: RenderMime = null;
  524. private _mimeType: string;
  525. private _renderTimeout: number;
  526. private _dataType: 'string' | 'json';
  527. private _iconLabel: string;
  528. private _iconClass: string;
  529. }
  530. /**
  531. * The namespace for MimeRendererFactory class statics.
  532. */
  533. export
  534. namespace MimeRendererFactory {
  535. /**
  536. * The options used to initialize a MimeRendererFactory.
  537. */
  538. export
  539. interface IOptions extends DocumentRegistry.IWidgetFactoryOptions {
  540. /**
  541. * The rendermime instance.
  542. */
  543. rendermime: RenderMime;
  544. /**
  545. * The mime type.
  546. */
  547. mimeType: string;
  548. /**
  549. * The render timeout.
  550. */
  551. renderTimeout?: number;
  552. /**
  553. * The icon class name for the widget.
  554. */
  555. iconClass?: string;
  556. /**
  557. * The icon label for the widget.
  558. */
  559. iconLabel?: string;
  560. /**
  561. * Preferred data type from the model.
  562. */
  563. dataType?: 'string' | 'json';
  564. }
  565. }
  566. /**
  567. * The namespace for the module implementation details.
  568. */
  569. namespace Private {
  570. /**
  571. * Create the document registry options.
  572. */
  573. export
  574. function createRegistryOptions(options: MimeRendererFactory.IOptions): DocumentRegistry.IWidgetFactoryOptions {
  575. return { ...options, readOnly: true } as DocumentRegistry.IWidgetFactoryOptions;
  576. }
  577. }