model.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import {
  4. each
  5. } from '@phosphor/algorithm';
  6. import {
  7. utils
  8. } from '@jupyterlab/services';
  9. import {
  10. DocumentModel, DocumentRegistry
  11. } from '@jupyterlab/docregistry';
  12. import {
  13. ICellModel, ICodeCellModel, IRawCellModel, IMarkdownCellModel,
  14. CodeCellModel, RawCellModel, MarkdownCellModel, CellModel
  15. } from '@jupyterlab/cells';
  16. import {
  17. IObservableJSON, IObservableUndoableVector,
  18. IObservableVector, ObservableVector, nbformat, IModelDB
  19. } from '@jupyterlab/coreutils';
  20. import {
  21. CellList
  22. } from './celllist';
  23. /**
  24. * The definition of a model object for a notebook widget.
  25. */
  26. export
  27. interface INotebookModel extends DocumentRegistry.IModel {
  28. /**
  29. * The list of cells in the notebook.
  30. */
  31. readonly cells: IObservableUndoableVector<ICellModel>;
  32. /**
  33. * The cell model factory for the notebook.
  34. */
  35. readonly contentFactory: NotebookModel.IContentFactory;
  36. /**
  37. * The major version number of the nbformat.
  38. */
  39. readonly nbformat: number;
  40. /**
  41. * The minor version number of the nbformat.
  42. */
  43. readonly nbformatMinor: number;
  44. /**
  45. * The metadata associated with the notebook.
  46. */
  47. readonly metadata: IObservableJSON;
  48. }
  49. /**
  50. * An implementation of a notebook Model.
  51. */
  52. export
  53. class NotebookModel extends DocumentModel implements INotebookModel {
  54. /**
  55. * Construct a new notebook model.
  56. */
  57. constructor(options: NotebookModel.IOptions = {}) {
  58. super(options.languagePreference, options.modelDB);
  59. let factory = (
  60. options.contentFactory || NotebookModel.defaultContentFactory
  61. );
  62. let cellDB = this.modelDB.view('cells');
  63. factory.modelDB = cellDB;
  64. this.contentFactory = factory;
  65. this._cells = new CellList(this.modelDB, this.contentFactory);
  66. // Add an initial code cell by default.
  67. if (!this._cells.length) {
  68. this._cells.pushBack(factory.createCodeCell({}));
  69. }
  70. this._cells.changed.connect(this._onCellsChanged, this);
  71. // Handle initial metadata.
  72. let metadata = this.modelDB.createJSON('metadata');
  73. if (!metadata.has('language_info')) {
  74. let name = options.languagePreference || '';
  75. metadata.set('language_info', { name });
  76. }
  77. this._ensureMetadata();
  78. metadata.changed.connect(this.triggerContentChange, this);
  79. }
  80. /**
  81. * The cell model factory for the notebook.
  82. */
  83. readonly contentFactory: NotebookModel.IContentFactory;
  84. /**
  85. * The metadata associated with the notebook.
  86. */
  87. get metadata(): IObservableJSON {
  88. return this.modelDB.get('metadata') as IObservableJSON;
  89. }
  90. /**
  91. * Get the observable list of notebook cells.
  92. */
  93. get cells(): IObservableUndoableVector<ICellModel> {
  94. return this._cells;
  95. }
  96. /**
  97. * The major version number of the nbformat.
  98. */
  99. get nbformat(): number {
  100. return this._nbformat;
  101. }
  102. /**
  103. * The minor version number of the nbformat.
  104. */
  105. get nbformatMinor(): number {
  106. return this._nbformatMinor;
  107. }
  108. /**
  109. * The default kernel name of the document.
  110. */
  111. get defaultKernelName(): string {
  112. let spec = this.metadata.get('kernelspec') as nbformat.IKernelspecMetadata;
  113. return spec ? spec.name : '';
  114. }
  115. /**
  116. * The default kernel language of the document.
  117. */
  118. get defaultKernelLanguage(): string {
  119. let info = this.metadata.get('language_info') as nbformat.ILanguageInfoMetadata;
  120. return info ? info.name : '';
  121. }
  122. /**
  123. * Dispose of the resources held by the model.
  124. */
  125. dispose(): void {
  126. // Do nothing if already disposed.
  127. if (this.cells === null) {
  128. return;
  129. }
  130. let cells = this.cells;
  131. cells.dispose();
  132. super.dispose();
  133. }
  134. /**
  135. * Serialize the model to a string.
  136. */
  137. toString(): string {
  138. return JSON.stringify(this.toJSON());
  139. }
  140. /**
  141. * Deserialize the model from a string.
  142. *
  143. * #### Notes
  144. * Should emit a [contentChanged] signal.
  145. */
  146. fromString(value: string): void {
  147. this.fromJSON(JSON.parse(value));
  148. }
  149. /**
  150. * Serialize the model to JSON.
  151. */
  152. toJSON(): nbformat.INotebookContent {
  153. let cells: nbformat.ICell[] = [];
  154. for (let i = 0; i < this.cells.length; i++) {
  155. let cell = this.cells.at(i);
  156. cells.push(cell.toJSON());
  157. }
  158. this._ensureMetadata();
  159. let metadata = Object.create(null) as nbformat.INotebookMetadata;
  160. for (let key of this.metadata.keys()) {
  161. metadata[key] = JSON.parse(JSON.stringify(this.metadata.get(key)));
  162. }
  163. return {
  164. metadata,
  165. nbformat_minor: this._nbformatMinor,
  166. nbformat: this._nbformat,
  167. cells
  168. };
  169. }
  170. /**
  171. * Deserialize the model from JSON.
  172. *
  173. * #### Notes
  174. * Should emit a [contentChanged] signal.
  175. */
  176. fromJSON(value: nbformat.INotebookContent): void {
  177. let cells: ICellModel[] = [];
  178. let factory = this.contentFactory;
  179. for (let cell of value.cells) {
  180. switch (cell.cell_type) {
  181. case 'code':
  182. cells.push(factory.createCodeCell({ cell }));
  183. break;
  184. case 'markdown':
  185. cells.push(factory.createMarkdownCell({ cell }));
  186. break;
  187. case 'raw':
  188. cells.push(factory.createRawCell({ cell }));
  189. break;
  190. default:
  191. continue;
  192. }
  193. }
  194. this.cells.beginCompoundOperation();
  195. this.cells.clear();
  196. this.cells.pushAll(cells);
  197. this.cells.endCompoundOperation();
  198. let oldValue = 0;
  199. let newValue = 0;
  200. this._nbformatMinor = nbformat.MINOR_VERSION;
  201. this._nbformat = nbformat.MAJOR_VERSION;
  202. if (value.nbformat !== this._nbformat) {
  203. oldValue = this._nbformat;
  204. this._nbformat = newValue = value.nbformat;
  205. this.triggerStateChange({ name: 'nbformat', oldValue, newValue });
  206. }
  207. if (value.nbformat_minor > this._nbformatMinor) {
  208. oldValue = this._nbformatMinor;
  209. this._nbformatMinor = newValue = value.nbformat_minor;
  210. this.triggerStateChange({ name: 'nbformatMinor', oldValue, newValue });
  211. }
  212. // Update the metadata.
  213. this.metadata.clear();
  214. let metadata = value.metadata;
  215. for (let key in metadata) {
  216. // orig_nbformat is not intended to be stored per spec.
  217. if (key === 'orig_nbformat') {
  218. continue;
  219. }
  220. this.metadata.set(key, metadata[key]);
  221. }
  222. this._ensureMetadata();
  223. this.dirty = true;
  224. }
  225. /**
  226. * Handle a change in the cells list.
  227. */
  228. private _onCellsChanged(list: IObservableVector<ICellModel>, change: ObservableVector.IChangedArgs<ICellModel>): void {
  229. switch (change.type) {
  230. case 'add':
  231. each(change.newValues, cell => {
  232. cell.contentChanged.connect(this.triggerContentChange, this);
  233. });
  234. break;
  235. case 'remove':
  236. each(change.oldValues, cell => {
  237. });
  238. break;
  239. case 'set':
  240. each(change.newValues, cell => {
  241. cell.contentChanged.connect(this.triggerContentChange, this);
  242. });
  243. each(change.oldValues, cell => {
  244. });
  245. break;
  246. default:
  247. return;
  248. }
  249. let factory = this.contentFactory;
  250. // Add code cell if there are no cells remaining.
  251. if (!this.cells.length) {
  252. // Add the cell in a new context to avoid triggering another
  253. // cell changed event during the handling of this signal.
  254. requestAnimationFrame(() => {
  255. if (!this.isDisposed && !this.cells.length) {
  256. this.cells.pushBack(factory.createCodeCell({}));
  257. }
  258. });
  259. }
  260. this.triggerContentChange();
  261. }
  262. /**
  263. * Make sure we have the required metadata fields.
  264. */
  265. private _ensureMetadata(): void {
  266. let metadata = this.metadata;
  267. if (!metadata.has('language_info')) {
  268. metadata.set('language_info', { name: '' });
  269. }
  270. if (!metadata.has('kernelspec')) {
  271. metadata.set('kernelspec', { name: '', display_name: '' });
  272. }
  273. }
  274. private _cells: CellList;
  275. private _nbformat = nbformat.MAJOR_VERSION;
  276. private _nbformatMinor = nbformat.MINOR_VERSION;
  277. }
  278. /**
  279. * The namespace for the `NotebookModel` class statics.
  280. */
  281. export
  282. namespace NotebookModel {
  283. /**
  284. * An options object for initializing a notebook model.
  285. */
  286. export
  287. interface IOptions {
  288. /**
  289. * The language preference for the model.
  290. */
  291. languagePreference?: string;
  292. /**
  293. * A factory for creating cell models.
  294. *
  295. * The default is a shared factory instance.
  296. */
  297. contentFactory?: IContentFactory;
  298. /**
  299. * An optional modelDB for storing notebook data.
  300. */
  301. modelDB?: IModelDB;
  302. }
  303. /**
  304. * A factory for creating notebook model content.
  305. */
  306. export
  307. interface IContentFactory {
  308. /**
  309. * The factory for output area models.
  310. */
  311. readonly codeCellContentFactory: CodeCellModel.IContentFactory;
  312. modelDB: IModelDB;
  313. /**
  314. * Create a new code cell.
  315. *
  316. * @param options - The options used to create the cell.
  317. *
  318. * @returns A new code cell. If a source cell is provided, the
  319. * new cell will be intialized with the data from the source.
  320. */
  321. createCodeCell(options: CodeCellModel.IOptions): ICodeCellModel;
  322. /**
  323. * Create a new markdown cell.
  324. *
  325. * @param options - The options used to create the cell.
  326. *
  327. * @returns A new markdown cell. If a source cell is provided, the
  328. * new cell will be intialized with the data from the source.
  329. */
  330. createMarkdownCell(options: CellModel.IOptions): IMarkdownCellModel;
  331. /**
  332. * Create a new raw cell.
  333. *
  334. * @param options - The options used to create the cell.
  335. *
  336. * @returns A new raw cell. If a source cell is provided, the
  337. * new cell will be intialized with the data from the source.
  338. */
  339. createRawCell(options: CellModel.IOptions): IRawCellModel;
  340. }
  341. /**
  342. * The default implementation of an `IContentFactory`.
  343. */
  344. export
  345. class ContentFactory {
  346. /**
  347. * Create a new cell model factory.
  348. */
  349. constructor(options: IContentFactoryOptions) {
  350. this.codeCellContentFactory = (options.codeCellContentFactory ||
  351. CodeCellModel.defaultContentFactory
  352. );
  353. this._modelDB = options.modelDB || null;
  354. }
  355. /**
  356. * The factory for code cell content.
  357. */
  358. readonly codeCellContentFactory: CodeCellModel.IContentFactory;
  359. get modelDB(): IModelDB {
  360. return this._modelDB;
  361. }
  362. set modelDB(db: IModelDB) {
  363. this._modelDB = db;
  364. }
  365. /**
  366. * Create a new code cell.
  367. *
  368. * @param source - The data to use for the original source data.
  369. *
  370. * @returns A new code cell. If a source cell is provided, the
  371. * new cell will be intialized with the data from the source.
  372. * If the contentFactory is not provided, the instance
  373. * `codeCellContentFactory` will be used.
  374. */
  375. createCodeCell(options: CodeCellModel.IOptions): ICodeCellModel {
  376. if (options.contentFactory) {
  377. options.contentFactory = this.codeCellContentFactory;
  378. }
  379. if (this._modelDB) {
  380. if (!options.id) {
  381. options.id = utils.uuid();
  382. }
  383. options.modelDB = this._modelDB.view(options.id);
  384. }
  385. return new CodeCellModel(options);
  386. }
  387. /**
  388. * Create a new markdown cell.
  389. *
  390. * @param source - The data to use for the original source data.
  391. *
  392. * @returns A new markdown cell. If a source cell is provided, the
  393. * new cell will be intialized with the data from the source.
  394. */
  395. createMarkdownCell(options: CellModel.IOptions): IMarkdownCellModel {
  396. if (this._modelDB) {
  397. if (!options.id) {
  398. options.id = utils.uuid();
  399. }
  400. options.modelDB = this._modelDB.view(options.id);
  401. }
  402. return new MarkdownCellModel(options);
  403. }
  404. /**
  405. * Create a new raw cell.
  406. *
  407. * @param source - The data to use for the original source data.
  408. *
  409. * @returns A new raw cell. If a source cell is provided, the
  410. * new cell will be intialized with the data from the source.
  411. */
  412. createRawCell(options: CellModel.IOptions): IRawCellModel {
  413. if (this._modelDB) {
  414. if (!options.id) {
  415. options.id = utils.uuid();
  416. }
  417. options.modelDB = this._modelDB.view(options.id);
  418. }
  419. return new RawCellModel(options);
  420. }
  421. private _modelDB: IModelDB;
  422. }
  423. /**
  424. * The options used to initialize a `ContentFactory`.
  425. */
  426. export
  427. interface IContentFactoryOptions {
  428. /**
  429. * The factory for code cell model content.
  430. */
  431. codeCellContentFactory?: CodeCellModel.IContentFactory;
  432. /**
  433. * The modelDB in which to place new content.
  434. */
  435. modelDB?: IModelDB;
  436. }
  437. /**
  438. * The default `ContentFactory` instance.
  439. */
  440. export
  441. const defaultContentFactory = new ContentFactory({});
  442. }