123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609 |
- // Copyright (c) Jupyter Development Team.
- // Distributed under the terms of the Modified BSD License.
- import { DocumentModel, DocumentRegistry } from '@jupyterlab/docregistry';
- import {
- ICellModel,
- ICodeCellModel,
- IRawCellModel,
- IMarkdownCellModel,
- CodeCellModel,
- RawCellModel,
- MarkdownCellModel,
- CellModel
- } from '@jupyterlab/cells';
- import * as nbformat from '@jupyterlab/nbformat';
- import { UUID } from '@lumino/coreutils';
- import {
- IObservableJSON,
- IObservableUndoableList,
- IObservableList,
- IModelDB
- } from '@jupyterlab/observables';
- import { CellList } from './celllist';
- import { showDialog, Dialog } from '@jupyterlab/apputils';
- import {
- nullTranslator,
- ITranslator,
- TranslationBundle
- } from '@jupyterlab/translation';
- /**
- * The definition of a model object for a notebook widget.
- */
- export interface INotebookModel extends DocumentRegistry.IModel {
- /**
- * The list of cells in the notebook.
- */
- readonly cells: IObservableUndoableList<ICellModel>;
- /**
- * The cell model factory for the notebook.
- */
- readonly contentFactory: NotebookModel.IContentFactory;
- /**
- * The major version number of the nbformat.
- */
- readonly nbformat: number;
- /**
- * The minor version number of the nbformat.
- */
- readonly nbformatMinor: number;
- /**
- * The metadata associated with the notebook.
- */
- readonly metadata: IObservableJSON;
- /**
- * The array of deleted cells since the notebook was last run.
- */
- readonly deletedCells: string[];
- }
- /**
- * An implementation of a notebook Model.
- */
- export class NotebookModel extends DocumentModel implements INotebookModel {
- /**
- * Construct a new notebook model.
- */
- constructor(options: NotebookModel.IOptions = {}) {
- super(options.languagePreference, options.modelDB);
- const factory =
- options.contentFactory || NotebookModel.defaultContentFactory;
- this.contentFactory = factory.clone(this.modelDB.view('cells'));
- this._cells = new CellList(this.modelDB, this.contentFactory);
- this._trans = (options.translator || nullTranslator).load('jupyterlab');
- this._cells.changed.connect(this._onCellsChanged, this);
- // Handle initial metadata.
- const metadata = this.modelDB.createMap('metadata');
- if (!metadata.has('language_info')) {
- const name = options.languagePreference || '';
- metadata.set('language_info', { name });
- }
- this._ensureMetadata();
- metadata.changed.connect(this.triggerContentChange, this);
- this._deletedCells = [];
- }
- /**
- * The cell model factory for the notebook.
- */
- readonly contentFactory: NotebookModel.IContentFactory;
- /**
- * The metadata associated with the notebook.
- */
- get metadata(): IObservableJSON {
- return this.modelDB.get('metadata') as IObservableJSON;
- }
- /**
- * Get the observable list of notebook cells.
- */
- get cells(): IObservableUndoableList<ICellModel> {
- return this._cells;
- }
- /**
- * The major version number of the nbformat.
- */
- get nbformat(): number {
- return this._nbformat;
- }
- /**
- * The minor version number of the nbformat.
- */
- get nbformatMinor(): number {
- return this._nbformatMinor;
- }
- /**
- * The default kernel name of the document.
- */
- get defaultKernelName(): string {
- const spec = this.metadata.get(
- 'kernelspec'
- ) as nbformat.IKernelspecMetadata;
- return spec ? spec.name : '';
- }
- /**
- * A list of deleted cells for the notebook..
- */
- get deletedCells(): string[] {
- return this._deletedCells;
- }
- /**
- * The default kernel language of the document.
- */
- get defaultKernelLanguage(): string {
- const info = this.metadata.get(
- 'language_info'
- ) as nbformat.ILanguageInfoMetadata;
- return info ? info.name : '';
- }
- /**
- * Dispose of the resources held by the model.
- */
- dispose(): void {
- // Do nothing if already disposed.
- if (this.isDisposed) {
- return;
- }
- const cells = this.cells;
- this._cells = null!;
- cells.dispose();
- super.dispose();
- }
- /**
- * Serialize the model to a string.
- */
- toString(): string {
- return JSON.stringify(this.toJSON());
- }
- /**
- * Deserialize the model from a string.
- *
- * #### Notes
- * Should emit a [contentChanged] signal.
- */
- fromString(value: string): void {
- this.fromJSON(JSON.parse(value));
- }
- /**
- * Serialize the model to JSON.
- */
- toJSON(): nbformat.INotebookContent {
- const cells: nbformat.ICell[] = [];
- for (let i = 0; i < (this.cells?.length ?? 0); i++) {
- const cell = this.cells.get(i).toJSON();
- if (this._nbformat === 4 && this._nbformatMinor <= 4) {
- // strip cell ids if we have notebook format 4.0-4.4
- delete cell.id;
- }
- cells.push(cell);
- }
- this._ensureMetadata();
- const metadata = Object.create(null) as nbformat.INotebookMetadata;
- for (const key of this.metadata.keys()) {
- metadata[key] = JSON.parse(JSON.stringify(this.metadata.get(key)));
- }
- return {
- metadata,
- nbformat_minor: this._nbformatMinor,
- nbformat: this._nbformat,
- cells
- };
- }
- /**
- * Deserialize the model from JSON.
- *
- * #### Notes
- * Should emit a [contentChanged] signal.
- */
- fromJSON(value: nbformat.INotebookContent): void {
- const cells: ICellModel[] = [];
- const factory = this.contentFactory;
- const useId = value.nbformat === 4 && value.nbformat_minor >= 5;
- for (const cell of value.cells) {
- const options: CellModel.IOptions = { cell };
- if (useId) {
- options.id = (cell as any).id;
- }
- switch (cell.cell_type) {
- case 'code':
- cells.push(factory.createCodeCell(options));
- break;
- case 'markdown':
- cells.push(factory.createMarkdownCell(options));
- break;
- case 'raw':
- cells.push(factory.createRawCell(options));
- break;
- default:
- continue;
- }
- }
- this.cells.beginCompoundOperation();
- this.cells.clear();
- this.cells.pushAll(cells);
- this.cells.endCompoundOperation();
- let oldValue = 0;
- let newValue = 0;
- this._nbformatMinor = nbformat.MINOR_VERSION;
- this._nbformat = nbformat.MAJOR_VERSION;
- const origNbformat = value.metadata.orig_nbformat;
- if (value.nbformat !== this._nbformat) {
- oldValue = this._nbformat;
- this._nbformat = newValue = value.nbformat;
- this.triggerStateChange({ name: 'nbformat', oldValue, newValue });
- }
- if (value.nbformat_minor > this._nbformatMinor) {
- oldValue = this._nbformatMinor;
- this._nbformatMinor = newValue = value.nbformat_minor;
- this.triggerStateChange({ name: 'nbformatMinor', oldValue, newValue });
- }
- // Alert the user if the format changes.
- if (origNbformat !== undefined && this._nbformat !== origNbformat) {
- const newer = this._nbformat > origNbformat;
- let msg: string;
- if (newer) {
- msg = this._trans.__(
- `This notebook has been converted from an older notebook format (v%1)
- to the current notebook format (v%2).
- The next time you save this notebook, the current notebook format (vthis._nbformat) will be used.
- 'Older versions of Jupyter may not be able to read the new format.' To preserve the original format version,
- close the notebook without saving it.`,
- origNbformat,
- this._nbformat
- );
- } else {
- msg = this._trans.__(
- `This notebook has been converted from an newer notebook format (v%1)
- to the current notebook format (v%2).
- The next time you save this notebook, the current notebook format (v%2) will be used.
- Some features of the original notebook may not be available.' To preserve the original format version,
- close the notebook without saving it.`,
- origNbformat,
- this._nbformat
- );
- }
- void showDialog({
- title: this._trans.__('Notebook converted'),
- body: msg,
- buttons: [Dialog.okButton({ label: this._trans.__('Ok') })]
- });
- }
- // Update the metadata.
- this.metadata.clear();
- const metadata = value.metadata;
- for (const key in metadata) {
- // orig_nbformat is not intended to be stored per spec.
- if (key === 'orig_nbformat') {
- continue;
- }
- this.metadata.set(key, metadata[key]);
- }
- this._ensureMetadata();
- this.dirty = true;
- }
- /**
- * Initialize the model with its current state.
- *
- * # Notes
- * Adds an empty code cell if the model is empty
- * and clears undo state.
- */
- initialize(): void {
- super.initialize();
- if (!this.cells.length) {
- const factory = this.contentFactory;
- this.cells.push(factory.createCodeCell({}));
- }
- this.cells.clearUndo();
- }
- /**
- * Handle a change in the cells list.
- */
- private _onCellsChanged(
- list: IObservableList<ICellModel>,
- change: IObservableList.IChangedArgs<ICellModel>
- ): void {
- switch (change.type) {
- case 'add':
- change.newValues.forEach(cell => {
- cell.contentChanged.connect(this.triggerContentChange, this);
- });
- break;
- case 'remove':
- break;
- case 'set':
- change.newValues.forEach(cell => {
- cell.contentChanged.connect(this.triggerContentChange, this);
- });
- break;
- default:
- break;
- }
- this.triggerContentChange();
- }
- /**
- * Make sure we have the required metadata fields.
- */
- private _ensureMetadata(): void {
- const metadata = this.metadata;
- if (!metadata.has('language_info')) {
- metadata.set('language_info', { name: '' });
- }
- if (!metadata.has('kernelspec')) {
- metadata.set('kernelspec', { name: '', display_name: '' });
- }
- }
- private _trans: TranslationBundle;
- private _cells: CellList;
- private _nbformat = nbformat.MAJOR_VERSION;
- private _nbformatMinor = nbformat.MINOR_VERSION;
- private _deletedCells: string[];
- }
- /**
- * The namespace for the `NotebookModel` class statics.
- */
- export namespace NotebookModel {
- /**
- * An options object for initializing a notebook model.
- */
- export interface IOptions {
- /**
- * The language preference for the model.
- */
- languagePreference?: string;
- /**
- * A factory for creating cell models.
- *
- * The default is a shared factory instance.
- */
- contentFactory?: IContentFactory;
- /**
- * A modelDB for storing notebook data.
- */
- modelDB?: IModelDB;
- /**
- * Language translator.
- */
- translator?: ITranslator;
- }
- /**
- * A factory for creating notebook model content.
- */
- export interface IContentFactory {
- /**
- * The factory for output area models.
- */
- readonly codeCellContentFactory: CodeCellModel.IContentFactory;
- /**
- * The IModelDB in which to put data for the notebook model.
- */
- modelDB: IModelDB | undefined;
- /**
- * Create a new cell by cell type.
- *
- * @param type: the type of the cell to create.
- *
- * @param options: the cell creation options.
- *
- * #### Notes
- * This method is intended to be a convenience method to programmaticaly
- * call the other cell creation methods in the factory.
- */
- createCell(type: nbformat.CellType, opts: CellModel.IOptions): ICellModel;
- /**
- * Create a new code cell.
- *
- * @param options - The options used to create the cell.
- *
- * @returns A new code cell. If a source cell is provided, the
- * new cell will be initialized with the data from the source.
- */
- createCodeCell(options: CodeCellModel.IOptions): ICodeCellModel;
- /**
- * Create a new markdown cell.
- *
- * @param options - The options used to create the cell.
- *
- * @returns A new markdown cell. If a source cell is provided, the
- * new cell will be initialized with the data from the source.
- */
- createMarkdownCell(options: CellModel.IOptions): IMarkdownCellModel;
- /**
- * Create a new raw cell.
- *
- * @param options - The options used to create the cell.
- *
- * @returns A new raw cell. If a source cell is provided, the
- * new cell will be initialized with the data from the source.
- */
- createRawCell(options: CellModel.IOptions): IRawCellModel;
- /**
- * Clone the content factory with a new IModelDB.
- */
- clone(modelDB: IModelDB): IContentFactory;
- }
- /**
- * The default implementation of an `IContentFactory`.
- */
- export class ContentFactory {
- /**
- * Create a new cell model factory.
- */
- constructor(options: ContentFactory.IOptions) {
- this.codeCellContentFactory =
- options.codeCellContentFactory || CodeCellModel.defaultContentFactory;
- this.modelDB = options.modelDB;
- }
- /**
- * The factory for code cell content.
- */
- readonly codeCellContentFactory: CodeCellModel.IContentFactory;
- /**
- * The IModelDB in which to put the notebook data.
- */
- readonly modelDB: IModelDB | undefined;
- /**
- * Create a new cell by cell type.
- *
- * @param type: the type of the cell to create.
- *
- * @param options: the cell creation options.
- *
- * #### Notes
- * This method is intended to be a convenience method to programmaticaly
- * call the other cell creation methods in the factory.
- */
- createCell(type: nbformat.CellType, opts: CellModel.IOptions): ICellModel {
- switch (type) {
- case 'code':
- return this.createCodeCell(opts);
- case 'markdown':
- return this.createMarkdownCell(opts);
- case 'raw':
- default:
- return this.createRawCell(opts);
- }
- }
- /**
- * Create a new code cell.
- *
- * @param source - The data to use for the original source data.
- *
- * @returns A new code cell. If a source cell is provided, the
- * new cell will be initialized with the data from the source.
- * If the contentFactory is not provided, the instance
- * `codeCellContentFactory` will be used.
- */
- createCodeCell(options: CodeCellModel.IOptions): ICodeCellModel {
- if (options.contentFactory) {
- options.contentFactory = this.codeCellContentFactory;
- }
- if (this.modelDB) {
- if (!options.id) {
- options.id = UUID.uuid4();
- }
- options.modelDB = this.modelDB.view(options.id);
- }
- return new CodeCellModel(options);
- }
- /**
- * Create a new markdown cell.
- *
- * @param source - The data to use for the original source data.
- *
- * @returns A new markdown cell. If a source cell is provided, the
- * new cell will be initialized with the data from the source.
- */
- createMarkdownCell(options: CellModel.IOptions): IMarkdownCellModel {
- if (this.modelDB) {
- if (!options.id) {
- options.id = UUID.uuid4();
- }
- options.modelDB = this.modelDB.view(options.id);
- }
- return new MarkdownCellModel(options);
- }
- /**
- * Create a new raw cell.
- *
- * @param source - The data to use for the original source data.
- *
- * @returns A new raw cell. If a source cell is provided, the
- * new cell will be initialized with the data from the source.
- */
- createRawCell(options: CellModel.IOptions): IRawCellModel {
- if (this.modelDB) {
- if (!options.id) {
- options.id = UUID.uuid4();
- }
- options.modelDB = this.modelDB.view(options.id);
- }
- return new RawCellModel(options);
- }
- /**
- * Clone the content factory with a new IModelDB.
- */
- clone(modelDB: IModelDB): ContentFactory {
- return new ContentFactory({
- modelDB: modelDB,
- codeCellContentFactory: this.codeCellContentFactory
- });
- }
- }
- /**
- * A namespace for the notebook model content factory.
- */
- export namespace ContentFactory {
- /**
- * The options used to initialize a `ContentFactory`.
- */
- export interface IOptions {
- /**
- * The factory for code cell model content.
- */
- codeCellContentFactory?: CodeCellModel.IContentFactory;
- /**
- * The modelDB in which to place new content.
- */
- modelDB?: IModelDB;
- }
- }
- /**
- * The default `ContentFactory` instance.
- */
- export const defaultContentFactory = new ContentFactory({});
- }
|