jupyterlab-emrys
Version:
A computational environment for Jupyter. Powered by Emrys
636 lines (635 loc) • 19.8 kB
JavaScript
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
"use strict";
var jupyter_js_services_1 = require('jupyter-js-services');
var phosphor_signaling_1 = require('phosphor-signaling');
var dialog_1 = require('../dialog');
/**
* An implementation of a document context.
*/
var Context = (function () {
/**
* Construct a new document context.
*/
function Context(manager) {
this._id = '';
this._manager = null;
this._manager = manager;
this._id = jupyter_js_services_1.utils.uuid();
}
Object.defineProperty(Context.prototype, "kernelChanged", {
/**
* A signal emitted when the kernel changes.
*/
get: function () {
return Private.kernelChangedSignal.bind(this);
},
enumerable: true,
configurable: true
});
Object.defineProperty(Context.prototype, "pathChanged", {
/**
* A signal emitted when the path changes.
*/
get: function () {
return Private.pathChangedSignal.bind(this);
},
enumerable: true,
configurable: true
});
Object.defineProperty(Context.prototype, "contentsModelChanged", {
/**
* A signal emitted when the model is saved or reverted.
*/
get: function () {
return Private.contentsModelChangedSignal.bind(this);
},
enumerable: true,
configurable: true
});
Object.defineProperty(Context.prototype, "populated", {
/**
* A signal emitted when the context is fully populated for the first time.
*/
get: function () {
return Private.populatedSignal.bind(this);
},
enumerable: true,
configurable: true
});
Object.defineProperty(Context.prototype, "id", {
/**
* The unique id of the context.
*
* #### Notes
* This is a read-only property.
*/
get: function () {
return this._id;
},
enumerable: true,
configurable: true
});
Object.defineProperty(Context.prototype, "model", {
/**
* Get the model associated with the document.
*
* #### Notes
* This is a read-only property
*/
get: function () {
return this._manager.getModel(this._id);
},
enumerable: true,
configurable: true
});
Object.defineProperty(Context.prototype, "kernel", {
/**
* The current kernel associated with the document.
*
* #### Notes
* This is a read-only propery.
*/
get: function () {
return this._manager.getKernel(this._id);
},
enumerable: true,
configurable: true
});
Object.defineProperty(Context.prototype, "path", {
/**
* The current path associated with the document.
*
* #### Notes
* This is a read-only property.
*/
get: function () {
return this._manager.getPath(this._id);
},
enumerable: true,
configurable: true
});
Object.defineProperty(Context.prototype, "contentsModel", {
/**
* The current contents model associated with the document
*
* #### Notes
* This is a read-only property. The model will have an
* empty `contents` field.
*/
get: function () {
return this._manager.getContentsModel(this._id);
},
enumerable: true,
configurable: true
});
Object.defineProperty(Context.prototype, "kernelspecs", {
/**
* Get the kernel spec information.
*
* #### Notes
* This is a read-only property.
*/
get: function () {
return this._manager.getKernelspecs();
},
enumerable: true,
configurable: true
});
Object.defineProperty(Context.prototype, "isPopulated", {
/**
* Test whether the context is fully populated.
*
* #### Notes
* This is a read-only property.
*/
get: function () {
return this._manager.isPopulated(this._id);
},
enumerable: true,
configurable: true
});
Object.defineProperty(Context.prototype, "isDisposed", {
/**
* Test whether the context has been disposed.
*/
get: function () {
return this._manager === null;
},
enumerable: true,
configurable: true
});
/**
* Dispose of the resources held by the context.
*/
Context.prototype.dispose = function () {
if (this.isDisposed) {
return;
}
this._manager = null;
this._id = '';
};
/**
* Change the current kernel associated with the document.
*/
Context.prototype.changeKernel = function (options) {
return this._manager.changeKernel(this._id, options);
};
/**
* Save the document contents to disk.
*/
Context.prototype.save = function () {
return this._manager.save(this._id);
};
/**
* Save the document to a different path chosen by the user.
*/
Context.prototype.saveAs = function () {
return this._manager.saveAs(this._id);
};
/**
* Revert the document contents to disk contents.
*/
Context.prototype.revert = function () {
return this._manager.revert(this._id);
};
/**
* Get the list of running sessions.
*/
Context.prototype.listSessions = function () {
return this._manager.listSessions();
};
/**
* Resolve a url to a correct server path.
*/
Context.prototype.resolveUrl = function (url) {
return this._manager.resolveUrl(this._id, url);
};
/**
* Add a sibling widget to the document manager.
*
* @param widget - The widget to add to the document manager.
*
* @returns A disposable used to remove the sibling if desired.
*
* #### Notes
* It is assumed that the widget has the same model and context
* as the original widget.
*/
Context.prototype.addSibling = function (widget) {
return this._manager.addSibling(this._id, widget);
};
return Context;
}());
/**
* An object which manages the active contexts.
*/
var ContextManager = (function () {
/**
* Construct a new context manager.
*/
function ContextManager(options) {
this._manager = null;
this._contexts = Object.create(null);
this._opener = null;
this._manager = options.manager;
this._opener = options.opener;
}
Object.defineProperty(ContextManager.prototype, "isDisposed", {
/**
* Get whether the context manager has been disposed.
*/
get: function () {
return this._manager === null;
},
enumerable: true,
configurable: true
});
/**
* Dispose of the resources held by the document manager.
*/
ContextManager.prototype.dispose = function () {
if (this.isDisposed) {
return;
}
this._manager = null;
for (var id in this._contexts) {
var contextEx = this._contexts[id];
contextEx.context.dispose();
contextEx.model.dispose();
var session = contextEx.session;
if (session) {
session.dispose();
}
}
this._contexts = null;
this._opener = null;
};
/**
* Create a new context.
*/
ContextManager.prototype.createNew = function (path, model, factory) {
var context = new Context(this);
var id = context.id;
this._contexts[id] = {
context: context,
path: path,
model: model,
modelName: factory.name,
fileType: factory.fileType,
fileFormat: factory.fileFormat,
contentsModel: null,
session: null,
isPopulated: false
};
return id;
};
/**
* Get a context for a given path and model name.
*/
ContextManager.prototype.findContext = function (path, modelName) {
for (var id in this._contexts) {
var contextEx = this._contexts[id];
if (contextEx.path === path && contextEx.modelName === modelName) {
return id;
}
}
};
/**
* Find a context by path.
*/
ContextManager.prototype.getIdsForPath = function (path) {
var ids = [];
for (var id in this._contexts) {
if (this._contexts[id].path === path) {
ids.push(id);
}
}
return ids;
};
/**
* Get a context by id.
*/
ContextManager.prototype.getContext = function (id) {
return this._contexts[id].context;
};
/**
* Get the model associated with a context.
*/
ContextManager.prototype.getModel = function (id) {
return this._contexts[id].model;
};
/**
* Remove a context.
*/
ContextManager.prototype.removeContext = function (id) {
var contextEx = this._contexts[id];
contextEx.model.dispose();
contextEx.context.dispose();
delete this._contexts[id];
};
/**
* Get the current kernel associated with a document.
*/
ContextManager.prototype.getKernel = function (id) {
var session = this._contexts[id].session;
return session ? session.kernel : null;
};
/**
* Get the current path associated with a document.
*/
ContextManager.prototype.getPath = function (id) {
return this._contexts[id].path;
};
/**
* Get the current contents model associated with a document.
*/
ContextManager.prototype.getContentsModel = function (id) {
return this._contexts[id].contentsModel;
};
/**
* Change the current kernel associated with the document.
*
* @param options - If given, change the kernel (starting a session
* if necessary). If falsey, shut down any existing session and return
* a void promise.
*/
ContextManager.prototype.changeKernel = function (id, options) {
var contextEx = this._contexts[id];
var session = contextEx.session;
if (options) {
if (session) {
return session.changeKernel(options);
}
else {
var path = contextEx.path;
var sOptions = {
path: path,
kernelName: options.name,
kernelId: options.id
};
return this._startSession(id, sOptions);
}
}
else {
if (session) {
return session.shutdown().then(function () {
session.dispose();
contextEx.session = null;
contextEx.context.kernelChanged.emit(null);
return void 0;
});
}
else {
return Promise.resolve(void 0);
}
}
};
/**
* Update the path of an open document.
*
* @param id - The id of the context.
*
* @param newPath - The new path.
*/
ContextManager.prototype.handleRename = function (oldPath, newPath) {
// Update all of the paths, but only update one session
// so there is only one REST API call.
var ids = this.getIdsForPath(oldPath);
var sessionUpdated = false;
for (var _i = 0, ids_1 = ids; _i < ids_1.length; _i++) {
var id = ids_1[_i];
var contextEx = this._contexts[id];
contextEx.path = newPath;
contextEx.context.pathChanged.emit(newPath);
if (!sessionUpdated) {
var session = contextEx.session;
if (session) {
session.rename(newPath);
sessionUpdated = true;
}
}
}
};
/**
* Get the current kernelspec information.
*/
ContextManager.prototype.getKernelspecs = function () {
return this._manager.kernelspecs;
};
/**
* Save the document contents to disk.
*/
ContextManager.prototype.save = function (id) {
var _this = this;
var contextEx = this._contexts[id];
var model = contextEx.model;
var contents = contextEx.contentsModel || {};
var path = contextEx.path;
contents.type = contextEx.fileType;
contents.format = contextEx.fileFormat;
if (model.readOnly) {
return Promise.reject(new Error('Read only'));
}
if (contents.format === 'json') {
contents.content = model.toJSON();
}
else {
contents.content = model.toString();
}
return this._manager.contents.save(path, contents).then(function (newContents) {
contextEx.contentsModel = _this._copyContentsModel(newContents);
model.dirty = false;
}).catch(function (err) {
dialog_1.showDialog({
title: 'File Save Error',
body: err.xhr.responseText,
buttons: [dialog_1.okButton]
});
});
};
/**
* Save a document to a new file path chosen by the user.
*
* This results in a new session.
*/
ContextManager.prototype.saveAs = function (id) {
var _this = this;
var contextEx = this._contexts[id];
return Private.saveAs(contextEx.path).then(function (newPath) {
if (!newPath) {
return;
}
contextEx.path = newPath;
contextEx.context.pathChanged.emit(newPath);
if (contextEx.session) {
var options = {
path: newPath,
kernelId: contextEx.session.kernel.id,
kernelName: contextEx.session.kernel.name
};
return _this._startSession(id, options).then(function () {
return _this.save(id);
});
}
return _this.save(id);
});
};
/**
* Revert the contents of a path.
*/
ContextManager.prototype.revert = function (id) {
var _this = this;
var contextEx = this._contexts[id];
var opts = {
format: contextEx.fileFormat,
type: contextEx.fileType,
content: true
};
var path = contextEx.path;
var model = contextEx.model;
return this._manager.contents.get(path, opts).then(function (contents) {
if (contents.format === 'json') {
model.fromJSON(contents.content);
}
else {
model.fromString(contents.content);
}
var contentsModel = _this._copyContentsModel(contents);
// TODO: use deepEqual to check for equality
contextEx.contentsModel = contentsModel;
contextEx.context.contentsModelChanged.emit(contentsModel);
model.dirty = false;
}).catch(function (err) {
dialog_1.showDialog({
title: 'File Load Error',
body: err.xhr.responseText,
buttons: [dialog_1.okButton]
});
});
};
/**
* Test whether the context is fully populated.
*/
ContextManager.prototype.isPopulated = function (id) {
var contextEx = this._contexts[id];
return contextEx.isPopulated;
};
/**
* Finalize a context.
*/
ContextManager.prototype.finalize = function (id) {
var contextEx = this._contexts[id];
if (contextEx.isPopulated) {
return;
}
contextEx.isPopulated = true;
this._contexts[id].context.populated.emit(void 0);
};
/**
* Get the list of running sessions.
*/
ContextManager.prototype.listSessions = function () {
return this._manager.sessions.listRunning();
};
/**
* Resolve a relative url to a correct server path.
*/
ContextManager.prototype.resolveUrl = function (id, url) {
// Ignore urls that have a protocol.
if (jupyter_js_services_1.utils.urlParse(url).protocol || url.indexOf('//') === 0) {
return url;
}
var contextEx = this._contexts[id];
var cwd = jupyter_js_services_1.ContentsManager.dirname(contextEx.path);
var path = jupyter_js_services_1.ContentsManager.getAbsolutePath(url, cwd);
return this._manager.contents.getDownloadUrl(path);
};
/**
* Add a sibling widget to the document manager.
*/
ContextManager.prototype.addSibling = function (id, widget) {
var opener = this._opener;
return opener(id, widget);
};
/**
* Start a session and set up its signals.
*/
ContextManager.prototype._startSession = function (id, options) {
var contextEx = this._contexts[id];
var context = contextEx.context;
return this._manager.sessions.startNew(options).then(function (session) {
if (contextEx.session) {
contextEx.session.dispose();
}
contextEx.session = session;
context.kernelChanged.emit(session.kernel);
session.pathChanged.connect(function (s, path) {
if (path !== contextEx.path) {
contextEx.path = path;
context.pathChanged.emit(path);
}
});
session.kernelChanged.connect(function (s, kernel) {
context.kernelChanged.emit(kernel);
});
return session.kernel;
});
};
/**
* Copy the contents of a contents model, without the content.
*/
ContextManager.prototype._copyContentsModel = function (model) {
return {
path: model.path,
name: model.name,
type: model.type,
writable: model.writable,
created: model.created,
last_modified: model.last_modified,
mimetype: model.mimetype,
format: model.format
};
};
return ContextManager;
}());
exports.ContextManager = ContextManager;
/**
* A namespace for private data.
*/
var Private;
(function (Private) {
/**
* A signal emitted when the kernel changes.
*/
Private.kernelChangedSignal = new phosphor_signaling_1.Signal();
/**
* A signal emitted when the path changes.
*/
Private.pathChangedSignal = new phosphor_signaling_1.Signal();
/**
* A signal emitted when the contentsModel changes.
*/
Private.contentsModelChangedSignal = new phosphor_signaling_1.Signal();
/**
* A signal emitted when the context is fully populated for the first time.
*/
Private.populatedSignal = new phosphor_signaling_1.Signal();
/**
* Get a new file path from the user.
*/
function saveAs(path) {
var input = document.createElement('input');
input.value = path;
return dialog_1.showDialog({
title: 'Save File As..',
body: input,
okText: 'SAVE'
}).then(function (result) {
if (result.text === 'SAVE') {
return input.value;
}
});
}
Private.saveAs = saveAs;
})(Private || (Private = {}));