@jupyterlab/cells
Version:
JupyterLab - Notebook Cells
472 lines • 14.2 kB
JavaScript
/* -----------------------------------------------------------------------------
| Copyright (c) Jupyter Development Team.
| Distributed under the terms of the Modified BSD License.
|----------------------------------------------------------------------------*/
import { Signal } from '@lumino/signaling';
import { AttachmentsModel } from '@jupyterlab/attachments';
import { CodeEditor } from '@jupyterlab/codeeditor';
import { OutputAreaModel } from '@jupyterlab/outputarea';
import { createMutex, createStandaloneCell } from '@jupyter/ydoc';
const globalModelDBMutex = createMutex();
export function isCodeCellModel(model) {
return model.type === 'code';
}
export function isMarkdownCellModel(model) {
return model.type === 'markdown';
}
export function isRawCellModel(model) {
return model.type === 'raw';
}
/**
* An implementation of the cell model.
*/
export class CellModel extends CodeEditor.Model {
constructor(options = {}) {
const { cell_type, sharedModel, ...others } = options;
super({
sharedModel: sharedModel !== null && sharedModel !== void 0 ? sharedModel : createStandaloneCell({
cell_type: cell_type !== null && cell_type !== void 0 ? cell_type : 'raw',
id: options.id
}),
...others
});
/**
* A signal emitted when the state of the model changes.
*/
this.contentChanged = new Signal(this);
/**
* A signal emitted when a model state changes.
*/
this.stateChanged = new Signal(this);
this._metadataChanged = new Signal(this);
this._trusted = false;
this.standaloneModel = typeof options.sharedModel === 'undefined';
this.trusted = !!this.getMetadata('trusted') || !!options.trusted;
this.sharedModel.changed.connect(this.onGenericChange, this);
this.sharedModel.metadataChanged.connect(this._onMetadataChanged, this);
}
/**
* Signal emitted when cell metadata changes.
*/
get metadataChanged() {
return this._metadataChanged;
}
/**
* The id for the cell.
*/
get id() {
return this.sharedModel.getId();
}
/**
* The metadata associated with the cell.
*/
get metadata() {
return this.sharedModel.metadata;
}
/**
* The trusted state of the model.
*/
get trusted() {
return this._trusted;
}
set trusted(newValue) {
const oldValue = this.trusted;
if (oldValue !== newValue) {
this._trusted = newValue;
this.onTrustedChanged(this, { newValue, oldValue });
}
}
/**
* Dispose of the resources held by the model.
*/
dispose() {
if (this.isDisposed) {
return;
}
this.sharedModel.changed.disconnect(this.onGenericChange, this);
this.sharedModel.metadataChanged.disconnect(this._onMetadataChanged, this);
super.dispose();
}
/**
* Handle a change to the trusted state.
*
* The default implementation is a no-op.
*/
onTrustedChanged(trusted, args) {
/* no-op */
}
/**
* Delete a metadata
*
* @param key Metadata key
*/
deleteMetadata(key) {
return this.sharedModel.deleteMetadata(key);
}
/**
* Get a metadata
*
* ### Notes
* This returns a copy of the key value.
*
* @param key Metadata key
*/
getMetadata(key) {
return this.sharedModel.getMetadata(key);
}
/**
* Set a metadata
*
* @param key Metadata key
* @param value Metadata value
*/
setMetadata(key, value) {
if (typeof value === 'undefined') {
this.sharedModel.deleteMetadata(key);
}
else {
this.sharedModel.setMetadata(key, value);
}
}
/**
* Serialize the model to JSON.
*/
toJSON() {
return this.sharedModel.toJSON();
}
/**
* Handle a change to the observable value.
*/
onGenericChange() {
this.contentChanged.emit(void 0);
}
_onMetadataChanged(sender, change) {
this._metadataChanged.emit(change);
}
}
/**
* A base implementation for cell models with attachments.
*/
export class AttachmentsCellModel extends CellModel {
/**
* Construct a new cell with optional attachments.
*/
constructor(options) {
var _a;
super(options);
const factory = (_a = options.contentFactory) !== null && _a !== void 0 ? _a : AttachmentsCellModel.defaultContentFactory;
const values = this.sharedModel.getAttachments();
this._attachments = factory.createAttachmentsModel({ values });
this._attachments.stateChanged.connect(this.onGenericChange, this);
this._attachments.changed.connect(this._onAttachmentsChange, this);
this.sharedModel.changed.connect(this._onSharedModelChanged, this);
}
/**
* Get the attachments of the model.
*/
get attachments() {
return this._attachments;
}
/**
* Dispose of the resources held by the model.
*/
dispose() {
if (this.isDisposed) {
return;
}
this._attachments.stateChanged.disconnect(this.onGenericChange, this);
this._attachments.changed.disconnect(this._onAttachmentsChange, this);
this._attachments.dispose();
this.sharedModel.changed.disconnect(this._onSharedModelChanged, this);
super.dispose();
}
/**
* Serialize the model to JSON.
*/
toJSON() {
return super.toJSON();
}
/**
* Handle a change to the cell outputs modelDB and reflect it in the shared model.
*/
_onAttachmentsChange(sender, event) {
const cell = this.sharedModel;
globalModelDBMutex(() => cell.setAttachments(sender.toJSON()));
}
/**
* Handle a change to the code cell value.
*/
_onSharedModelChanged(slot, change) {
if (change.attachmentsChange) {
const cell = this.sharedModel;
globalModelDBMutex(() => { var _a; return this._attachments.fromJSON((_a = cell.getAttachments()) !== null && _a !== void 0 ? _a : {}); });
}
}
}
/**
* The namespace for `AttachmentsCellModel` statics.
*/
(function (AttachmentsCellModel) {
/**
* The default implementation of an `IContentFactory`.
*/
class ContentFactory {
/**
* Create an attachments model.
*/
createAttachmentsModel(options) {
return new AttachmentsModel(options);
}
}
AttachmentsCellModel.ContentFactory = ContentFactory;
/**
* The shared `ContentFactory` instance.
*/
AttachmentsCellModel.defaultContentFactory = new ContentFactory();
})(AttachmentsCellModel || (AttachmentsCellModel = {}));
/**
* An implementation of a raw cell model.
*/
export class RawCellModel extends AttachmentsCellModel {
/**
* Construct a raw cell model from optional shared model.
*/
constructor(options = {}) {
super({
cell_type: 'raw',
...options
});
}
/**
* The type of the cell.
*/
get type() {
return 'raw';
}
/**
* Serialize the model to JSON.
*/
toJSON() {
return super.toJSON();
}
}
/**
* An implementation of a markdown cell model.
*/
export class MarkdownCellModel extends AttachmentsCellModel {
/**
* Construct a markdown cell model from optional shared model.
*/
constructor(options = {}) {
super({
cell_type: 'markdown',
...options
});
// Use the Github-flavored markdown mode.
this.mimeType = 'text/x-ipythongfm';
}
/**
* The type of the cell.
*/
get type() {
return 'markdown';
}
/**
* Serialize the model to JSON.
*/
toJSON() {
return super.toJSON();
}
}
/**
* An implementation of a code cell Model.
*/
export class CodeCellModel extends CellModel {
/**
* Construct a new code cell with optional original cell content.
*/
constructor(options = {}) {
var _a;
super({
cell_type: 'code',
...options
});
this._executedCode = '';
this._isDirty = false;
const factory = (_a = options === null || options === void 0 ? void 0 : options.contentFactory) !== null && _a !== void 0 ? _a : CodeCellModel.defaultContentFactory;
const trusted = this.trusted;
const outputs = this.sharedModel.getOutputs();
this._outputs = factory.createOutputArea({ trusted, values: outputs });
this.sharedModel.changed.connect(this._onSharedModelChanged, this);
this._outputs.changed.connect(this.onGenericChange, this);
this._outputs.changed.connect(this.onOutputsChange, this);
}
/**
* The type of the cell.
*/
get type() {
return 'code';
}
/**
* The execution count of the cell.
*/
get executionCount() {
return this.sharedModel.execution_count || null;
}
set executionCount(newValue) {
this.sharedModel.execution_count = newValue || null;
}
/**
* Whether the cell is dirty or not.
*
* A cell is dirty if it is output is not empty and does not
* result of the input code execution.
*/
get isDirty() {
// Test could be done dynamically with this._executedCode
// but for performance reason, the diff status is stored in a boolean.
return this._isDirty;
}
/**
* The cell outputs.
*/
get outputs() {
return this._outputs;
}
clearExecution() {
this.outputs.clear();
this.executionCount = null;
this._setDirty(false);
this.sharedModel.deleteMetadata('execution');
// We trust this cell as it no longer has any outputs.
this.trusted = true;
}
/**
* Dispose of the resources held by the model.
*/
dispose() {
if (this.isDisposed) {
return;
}
this.sharedModel.changed.disconnect(this._onSharedModelChanged, this);
this._outputs.changed.disconnect(this.onGenericChange, this);
this._outputs.changed.disconnect(this.onOutputsChange, this);
this._outputs.dispose();
this._outputs = null;
super.dispose();
}
/**
* Handle a change to the trusted state.
*/
onTrustedChanged(trusted, args) {
const newTrusted = args.newValue;
if (this._outputs) {
this._outputs.trusted = newTrusted;
}
if (newTrusted) {
const codeCell = this.sharedModel;
const metadata = codeCell.getMetadata();
metadata.trusted = true;
codeCell.setMetadata(metadata);
}
this.stateChanged.emit({
name: 'trusted',
oldValue: args.oldValue,
newValue: newTrusted
});
}
/**
* Serialize the model to JSON.
*/
toJSON() {
return super.toJSON();
}
/**
* Handle a change to the cell outputs modelDB and reflect it in the shared model.
*/
onOutputsChange(sender, event) {
const codeCell = this.sharedModel;
globalModelDBMutex(() => {
switch (event.type) {
case 'add': {
const outputs = event.newValues.map(output => output.toJSON());
codeCell.updateOutputs(event.newIndex, event.newIndex, outputs);
break;
}
case 'set': {
const newValues = event.newValues.map(output => output.toJSON());
codeCell.updateOutputs(event.oldIndex, event.oldIndex + newValues.length, newValues);
break;
}
case 'remove':
codeCell.updateOutputs(event.oldIndex, event.oldValues.length);
break;
default:
throw new Error(`Invalid event type: ${event.type}`);
}
});
}
/**
* Handle a change to the code cell value.
*/
_onSharedModelChanged(slot, change) {
if (change.outputsChange) {
globalModelDBMutex(() => {
this.outputs.clear();
slot.getOutputs().forEach(output => this._outputs.add(output));
});
}
if (change.executionCountChange) {
if (change.executionCountChange.newValue &&
(this.isDirty || !change.executionCountChange.oldValue)) {
this._setDirty(false);
}
this.stateChanged.emit({
name: 'executionCount',
oldValue: change.executionCountChange.oldValue,
newValue: change.executionCountChange.newValue
});
}
if (change.sourceChange && this.executionCount !== null) {
this._setDirty(this._executedCode !== this.sharedModel.getSource().trim());
}
}
/**
* Set whether the cell is dirty or not.
*/
_setDirty(v) {
if (!v) {
this._executedCode = this.sharedModel.getSource().trim();
}
if (v !== this._isDirty) {
this._isDirty = v;
this.stateChanged.emit({
name: 'isDirty',
oldValue: !v,
newValue: v
});
}
}
}
/**
* The namespace for `CodeCellModel` statics.
*/
(function (CodeCellModel) {
/**
* The default implementation of an `IContentFactory`.
*/
class ContentFactory {
/**
* Create an output area.
*/
createOutputArea(options) {
return new OutputAreaModel(options);
}
}
CodeCellModel.ContentFactory = ContentFactory;
/**
* The shared `ContentFactory` instance.
*/
CodeCellModel.defaultContentFactory = new ContentFactory();
})(CodeCellModel || (CodeCellModel = {}));
//# sourceMappingURL=model.js.map