|
@@ -10,7 +10,7 @@ import {
|
|
|
} from 'phosphor/lib/algorithm/searching';
|
|
|
|
|
|
import {
|
|
|
- IDisposable
|
|
|
+ IDisposable, DisposableDelegate
|
|
|
} from 'phosphor/lib/core/disposable';
|
|
|
|
|
|
import {
|
|
@@ -36,50 +36,50 @@ import {
|
|
|
|
|
|
/**
|
|
|
* An implementation of a document context.
|
|
|
+ *
|
|
|
+ * This class is typically instantiated by the document manger.
|
|
|
*/
|
|
|
-class Context implements IDocumentContext<IDocumentModel> {
|
|
|
+export
|
|
|
+class Context<T extends IDocumentModel> implements IDocumentContext<T> {
|
|
|
/**
|
|
|
* Construct a new document context.
|
|
|
*/
|
|
|
- constructor(manager: ContextManager) {
|
|
|
- this._manager = manager;
|
|
|
- this._id = utils.uuid();
|
|
|
+ constructor(options: Context.IOptions<T>) {
|
|
|
+ let manager = this._manager = options.manager;
|
|
|
+ this._factory = options.factory;
|
|
|
+ this._opener = options.opener;
|
|
|
+ this._path = options.path;
|
|
|
+ let lang = this._factory.preferredLanguage(this._path);
|
|
|
+ this._model = this._factory.createNew(lang);
|
|
|
+ manager.sessions.runningChanged.connect(this._onSessionsChanged, this);
|
|
|
+ this._saver = new SaveHandler({ context: this, manager });
|
|
|
+ this._saver.start();
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* A signal emitted when the kernel changes.
|
|
|
*/
|
|
|
- kernelChanged: ISignal<Context, IKernel>;
|
|
|
+ kernelChanged: ISignal<IDocumentContext<T>, IKernel>;
|
|
|
|
|
|
/**
|
|
|
* A signal emitted when the path changes.
|
|
|
*/
|
|
|
- pathChanged: ISignal<Context, string>;
|
|
|
+ pathChanged: ISignal<IDocumentContext<T>, string>;
|
|
|
|
|
|
/**
|
|
|
* A signal emitted when the model is saved or reverted.
|
|
|
*/
|
|
|
- contentsModelChanged: ISignal<Context, IContents.IModel>;
|
|
|
+ contentsModelChanged: ISignal<IDocumentContext<T>, IContents.IModel>;
|
|
|
|
|
|
/**
|
|
|
* A signal emitted when the context is fully populated for the first time.
|
|
|
*/
|
|
|
- populated: ISignal<IDocumentContext<IDocumentModel>, void>;
|
|
|
+ populated: ISignal<IDocumentContext<T>, void>;
|
|
|
|
|
|
/**
|
|
|
* A signal emitted when the context is disposed.
|
|
|
*/
|
|
|
- disposed: ISignal<IDocumentContext<IDocumentModel>, void>;
|
|
|
-
|
|
|
- /**
|
|
|
- * The unique id of the context.
|
|
|
- *
|
|
|
- * #### Notes
|
|
|
- * This is a read-only property.
|
|
|
- */
|
|
|
- get id(): string {
|
|
|
- return this._id;
|
|
|
- }
|
|
|
+ disposed: ISignal<IDocumentContext<T>, void>;
|
|
|
|
|
|
/**
|
|
|
* Get the model associated with the document.
|
|
@@ -87,8 +87,8 @@ class Context implements IDocumentContext<IDocumentModel> {
|
|
|
* #### Notes
|
|
|
* This is a read-only property
|
|
|
*/
|
|
|
- get model(): IDocumentModel {
|
|
|
- return this._manager.getModel(this._id);
|
|
|
+ get model(): T {
|
|
|
+ return this._model;
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -98,17 +98,14 @@ class Context implements IDocumentContext<IDocumentModel> {
|
|
|
* This is a read-only propery.
|
|
|
*/
|
|
|
get kernel(): IKernel {
|
|
|
- return this._manager.getKernel(this._id);
|
|
|
+ return this._session ? this._session.kernel : null;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* The current path associated with the document.
|
|
|
- *
|
|
|
- * #### Notes
|
|
|
- * This is a read-only property.
|
|
|
*/
|
|
|
get path(): string {
|
|
|
- return this._manager.getPath(this._id);
|
|
|
+ return this._path;
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -119,7 +116,7 @@ class Context implements IDocumentContext<IDocumentModel> {
|
|
|
* empty `contents` field.
|
|
|
*/
|
|
|
get contentsModel(): IContents.IModel {
|
|
|
- return this._manager.getContentsModel(this._id);
|
|
|
+ return this._contentsModel;
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -129,7 +126,7 @@ class Context implements IDocumentContext<IDocumentModel> {
|
|
|
* This is a read-only property.
|
|
|
*/
|
|
|
get kernelspecs(): IKernel.ISpecModels {
|
|
|
- return this._manager.getKernelspecs();
|
|
|
+ return this._manager.kernelspecs;
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -139,292 +136,63 @@ class Context implements IDocumentContext<IDocumentModel> {
|
|
|
* This is a read-only property.
|
|
|
*/
|
|
|
get isPopulated(): boolean {
|
|
|
- return this._manager.isPopulated(this._id);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Test whether the context has been disposed (read-only).
|
|
|
- */
|
|
|
- get isDisposed(): boolean {
|
|
|
- return this._manager === null;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Dispose of the resources held by the context.
|
|
|
- */
|
|
|
- dispose(): void {
|
|
|
- if (this.isDisposed) {
|
|
|
- return;
|
|
|
- }
|
|
|
- this.disposed.emit(void 0);
|
|
|
- clearSignalData(this);
|
|
|
- this._manager.removeContext(this.id);
|
|
|
- this._manager = null;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Change the current kernel associated with the document.
|
|
|
- */
|
|
|
- changeKernel(options: IKernel.IModel): Promise<IKernel> {
|
|
|
- return this._manager.changeKernel(this._id, options);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Save the document contents to disk.
|
|
|
- */
|
|
|
- save(): Promise<void> {
|
|
|
- return this._manager.save(this._id);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Save the document to a different path chosen by the user.
|
|
|
- */
|
|
|
- saveAs(): Promise<void> {
|
|
|
- return this._manager.saveAs(this._id);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Revert the document contents to disk contents.
|
|
|
- */
|
|
|
- revert(): Promise<void> {
|
|
|
- return this._manager.revert(this._id);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Create a checkpoint for the file.
|
|
|
- */
|
|
|
- createCheckpoint(): Promise<IContents.ICheckpointModel> {
|
|
|
- return this._manager.createCheckpoint(this._id);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Delete a checkpoint for the file.
|
|
|
- */
|
|
|
- deleteCheckpoint(checkpointID: string): Promise<void> {
|
|
|
- return this._manager.deleteCheckpoint(this.id, checkpointID);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Restore the file to a known checkpoint state.
|
|
|
- */
|
|
|
- restoreCheckpoint(checkpointID?: string): Promise<void> {
|
|
|
- return this._manager.restoreCheckpoint(this.id, checkpointID);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * List available checkpoints for the file.
|
|
|
- */
|
|
|
- listCheckpoints(): Promise<IContents.ICheckpointModel[]> {
|
|
|
- return this._manager.listCheckpoints(this.id);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Get the list of running sessions.
|
|
|
- */
|
|
|
- listSessions(): Promise<ISession.IModel[]> {
|
|
|
- return this._manager.listSessions();
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Resolve a url to a correct server path.
|
|
|
- */
|
|
|
- resolveUrl(url: string): string {
|
|
|
- return this._manager.resolveUrl(this._id, url);
|
|
|
+ return this._isPopulated;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 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.
|
|
|
+ * Get the model factory name.
|
|
|
*
|
|
|
* #### Notes
|
|
|
- * It is assumed that the widget has the same model and context
|
|
|
- * as the original widget.
|
|
|
- */
|
|
|
- addSibling(widget: Widget): IDisposable {
|
|
|
- return this._manager.addSibling(this._id, widget);
|
|
|
- }
|
|
|
-
|
|
|
- private _id = '';
|
|
|
- private _manager: ContextManager = null;
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
-// Define the signals for the `Context` class.
|
|
|
-defineSignal(Context.prototype, 'kernelChanged');
|
|
|
-defineSignal(Context.prototype, 'pathChanged');
|
|
|
-defineSignal(Context.prototype, 'contentsModelChanged');
|
|
|
-defineSignal(Context.prototype, 'populated');
|
|
|
-defineSignal(Context.prototype, 'disposed');
|
|
|
-
|
|
|
-
|
|
|
-/**
|
|
|
- * An object which manages the active contexts.
|
|
|
- */
|
|
|
-export
|
|
|
-class ContextManager implements IDisposable {
|
|
|
- /**
|
|
|
- * Construct a new context manager.
|
|
|
+ * This is a read-only property.
|
|
|
*/
|
|
|
- constructor(options: ContextManager.IOptions) {
|
|
|
- let manager = this._manager = options.manager;
|
|
|
- this._opener = options.opener;
|
|
|
- manager.sessions.runningChanged.connect(this._onSessionsChanged, this);
|
|
|
+ get factoryName(): string {
|
|
|
+ return this.isDisposed ? '' : this._factory.name;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Get whether the context manager has been disposed.
|
|
|
+ * Test whether the context has been disposed (read-only).
|
|
|
*/
|
|
|
get isDisposed(): boolean {
|
|
|
return this._manager === null;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Dispose of the resources held by the document manager.
|
|
|
+ * Dispose of the resources held by the context.
|
|
|
*/
|
|
|
dispose(): void {
|
|
|
if (this.isDisposed) {
|
|
|
return;
|
|
|
}
|
|
|
+ this.disposed.emit(void 0);
|
|
|
+ clearSignalData(this);
|
|
|
+ this._model.dispose();
|
|
|
this._manager = null;
|
|
|
- for (let id in this._contexts) {
|
|
|
- this._contexts[id].context.dispose();
|
|
|
- }
|
|
|
- this._contexts = null;
|
|
|
- this._opener = null;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Create a new context.
|
|
|
- */
|
|
|
- createNew(path: string, model: IDocumentModel, factory: IModelFactory): string {
|
|
|
- let context = new Context(this);
|
|
|
- let saveHandler = new SaveHandler({ context, services: this._manager });
|
|
|
- saveHandler.start();
|
|
|
- let id = context.id;
|
|
|
- this._contexts[id] = {
|
|
|
- context,
|
|
|
- path,
|
|
|
- model,
|
|
|
- modelName: factory.name,
|
|
|
- fileType: factory.fileType,
|
|
|
- fileFormat: factory.fileFormat,
|
|
|
- contentsModel: null,
|
|
|
- session: null,
|
|
|
- isPopulated: false,
|
|
|
- saveHandler
|
|
|
- };
|
|
|
- return id;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Get a context for a given path and model name.
|
|
|
- */
|
|
|
- findContext(path: string, modelName: string): string {
|
|
|
- for (let id in this._contexts) {
|
|
|
- let contextEx = this._contexts[id];
|
|
|
- if (contextEx.path === path && contextEx.modelName === modelName) {
|
|
|
- return id;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Find a context by path.
|
|
|
- */
|
|
|
- getIdsForPath(path: string): string[] {
|
|
|
- let ids: string[] = [];
|
|
|
- for (let id in this._contexts) {
|
|
|
- if (this._contexts[id].path === path) {
|
|
|
- ids.push(id);
|
|
|
- }
|
|
|
- }
|
|
|
- return ids;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Get a context by id.
|
|
|
- */
|
|
|
- getContext(id: string): IDocumentContext<IDocumentModel> {
|
|
|
- return this._contexts[id].context;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Get the model associated with a context.
|
|
|
- */
|
|
|
- getModel(id: string): IDocumentModel {
|
|
|
- return this._contexts[id].model;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Remove a context that has been disposed.
|
|
|
- *
|
|
|
- * #### Notes
|
|
|
- * This is called when a context is disposed,
|
|
|
- * and should not be called by user code.
|
|
|
- */
|
|
|
- removeContext(id: string): void {
|
|
|
- let contextEx = this._contexts[id];
|
|
|
- contextEx.model.dispose();
|
|
|
- contextEx.saveHandler.dispose();
|
|
|
- let session = contextEx.session;
|
|
|
- if (session) {
|
|
|
- session.dispose();
|
|
|
- }
|
|
|
- delete this._contexts[id];
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Get the current kernel associated with a document.
|
|
|
- */
|
|
|
- getKernel(id: string): IKernel {
|
|
|
- let session = this._contexts[id].session;
|
|
|
- return session ? session.kernel : null;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Get the current path associated with a document.
|
|
|
- */
|
|
|
- getPath(id: string): string {
|
|
|
- return this._contexts[id].path;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Get the current contents model associated with a document.
|
|
|
- */
|
|
|
- getContentsModel(id: string): IContents.IModel {
|
|
|
- return this._contexts[id].contentsModel;
|
|
|
+ this._factory = null;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 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
|
|
|
*/
|
|
|
- changeKernel(id: string, options: IKernel.IModel): Promise<IKernel> {
|
|
|
- let contextEx = this._contexts[id];
|
|
|
- let session = contextEx.session;
|
|
|
+ changeKernel(options: IKernel.IModel): Promise<IKernel> {
|
|
|
+ let session = this._session;
|
|
|
if (options) {
|
|
|
if (session) {
|
|
|
return session.changeKernel(options);
|
|
|
} else {
|
|
|
- let path = contextEx.path;
|
|
|
+ let path = this._path;
|
|
|
let sOptions: ISession.IOptions = {
|
|
|
- path: path,
|
|
|
+ path,
|
|
|
kernelName: options.name,
|
|
|
kernelId: options.id
|
|
|
};
|
|
|
- return this._startSession(id, sOptions);
|
|
|
+ return this._startSession(sOptions);
|
|
|
}
|
|
|
} else {
|
|
|
if (session) {
|
|
|
return session.shutdown().then(() => {
|
|
|
session.dispose();
|
|
|
- contextEx.session = null;
|
|
|
- contextEx.context.kernelChanged.emit(null);
|
|
|
+ this._session = null;
|
|
|
+ this.kernelChanged.emit(null);
|
|
|
return void 0;
|
|
|
});
|
|
|
} else {
|
|
@@ -434,48 +202,31 @@ class ContextManager implements IDisposable {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Update the path of an open document.
|
|
|
- *
|
|
|
- * @param id - The id of the context.
|
|
|
+ * Set the path of the context.
|
|
|
*
|
|
|
- * @param newPath - The new path.
|
|
|
- */
|
|
|
- handleRename(oldPath: string, newPath: string): void {
|
|
|
- // Update all of the paths, but only update one session
|
|
|
- // so there is only one REST API call.
|
|
|
- let ids = this.getIdsForPath(oldPath);
|
|
|
- let sessionUpdated = false;
|
|
|
- for (let id of ids) {
|
|
|
- let contextEx = this._contexts[id];
|
|
|
- contextEx.path = newPath;
|
|
|
- contextEx.context.pathChanged.emit(newPath);
|
|
|
- if (!sessionUpdated) {
|
|
|
- let session = contextEx.session;
|
|
|
- if (session) {
|
|
|
- session.rename(newPath);
|
|
|
- sessionUpdated = true;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Get the current kernelspec information.
|
|
|
+ * #### Notes
|
|
|
+ * This should only be called by the document manager.
|
|
|
+ * It is assumed that the file has been renamed on the
|
|
|
+ * contents manager outside of this operation.
|
|
|
*/
|
|
|
- getKernelspecs(): IKernel.ISpecModels {
|
|
|
- return this._manager.kernelspecs;
|
|
|
+ setPath(value: string): void {
|
|
|
+ this._path = value;
|
|
|
+ let session = this._session;
|
|
|
+ if (session) {
|
|
|
+ session.rename(value);
|
|
|
+ }
|
|
|
+ this.pathChanged.emit(value);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Save the document contents to disk.
|
|
|
*/
|
|
|
- save(id: string): Promise<void> {
|
|
|
- let contextEx = this._contexts[id];
|
|
|
- let model = contextEx.model;
|
|
|
- let contents = contextEx.contentsModel || {};
|
|
|
- let path = contextEx.path;
|
|
|
- contents.type = contextEx.fileType;
|
|
|
- contents.format = contextEx.fileFormat;
|
|
|
+ save(): Promise<void> {
|
|
|
+ let model = this._model;
|
|
|
+ let contents = this._contentsModel || {};
|
|
|
+ let path = this._path;
|
|
|
+ contents.type = this._factory.fileType;
|
|
|
+ contents.format = this._factory.fileFormat;
|
|
|
if (model.readOnly) {
|
|
|
return Promise.reject(new Error('Read only'));
|
|
|
}
|
|
@@ -485,8 +236,11 @@ class ContextManager implements IDisposable {
|
|
|
contents.content = model.toString();
|
|
|
}
|
|
|
return this._manager.contents.save(path, contents).then(newContents => {
|
|
|
- contextEx.contentsModel = this._copyContentsModel(newContents);
|
|
|
+ this._contentsModel = this._copyContentsModel(newContents);
|
|
|
model.dirty = false;
|
|
|
+ if (!this._isPopulated) {
|
|
|
+ this._populate();
|
|
|
+ }
|
|
|
}).catch(err => {
|
|
|
showDialog({
|
|
|
title: 'File Save Error',
|
|
@@ -497,44 +251,40 @@ class ContextManager implements IDisposable {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Save a document to a new file path chosen by the user.
|
|
|
- *
|
|
|
- * This results in a new session.
|
|
|
+ * Save the document to a different path chosen by the user.
|
|
|
*/
|
|
|
- saveAs(id: string): Promise<void> {
|
|
|
- let contextEx = this._contexts[id];
|
|
|
- return Private.saveAs(contextEx.path).then(newPath => {
|
|
|
+ saveAs(): Promise<void> {
|
|
|
+ return Private.getSavePath(this._path).then(newPath => {
|
|
|
if (!newPath) {
|
|
|
return;
|
|
|
}
|
|
|
- contextEx.path = newPath;
|
|
|
- contextEx.context.pathChanged.emit(newPath);
|
|
|
- if (contextEx.session) {
|
|
|
+ this.setPath(newPath);
|
|
|
+ let session = this._session;
|
|
|
+ if (session) {
|
|
|
let options: ISession.IOptions = {
|
|
|
path: newPath,
|
|
|
- kernelId: contextEx.session.kernel.id,
|
|
|
- kernelName: contextEx.session.kernel.name
|
|
|
+ kernelId: session.kernel.id,
|
|
|
+ kernelName: session.kernel.name
|
|
|
};
|
|
|
- return this._startSession(id, options).then(() => {
|
|
|
- return this.save(id);
|
|
|
+ return this._startSession(options).then(() => {
|
|
|
+ return this.save();
|
|
|
});
|
|
|
}
|
|
|
- return this.save(id);
|
|
|
+ return this.save();
|
|
|
});
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Revert the contents of a path.
|
|
|
+ * Revert the document contents to disk contents.
|
|
|
*/
|
|
|
- revert(id: string): Promise<void> {
|
|
|
- let contextEx = this._contexts[id];
|
|
|
+ revert(): Promise<void> {
|
|
|
let opts: IContents.IFetchOptions = {
|
|
|
- format: contextEx.fileFormat,
|
|
|
- type: contextEx.fileType,
|
|
|
+ format: this._factory.fileFormat,
|
|
|
+ type: this._factory.fileType,
|
|
|
content: true
|
|
|
};
|
|
|
- let path = contextEx.path;
|
|
|
- let model = contextEx.model;
|
|
|
+ let path = this._path;
|
|
|
+ let model = this._model;
|
|
|
return this._manager.contents.get(path, opts).then(contents => {
|
|
|
if (contents.format === 'json') {
|
|
|
model.fromJSON(contents.content);
|
|
@@ -542,10 +292,14 @@ class ContextManager implements IDisposable {
|
|
|
model.fromString(contents.content);
|
|
|
}
|
|
|
let contentsModel = this._copyContentsModel(contents);
|
|
|
- // TODO: use deepEqual to check for equality
|
|
|
- contextEx.contentsModel = contentsModel;
|
|
|
- contextEx.context.contentsModelChanged.emit(contentsModel);
|
|
|
+ this._contentsModel = contentsModel;
|
|
|
+ if (contentsModel.last_modified !== this._contentsModel.last_modified) {
|
|
|
+ this.contentsModelChanged.emit(contentsModel);
|
|
|
+ }
|
|
|
model.dirty = false;
|
|
|
+ if (!this._isPopulated) {
|
|
|
+ this._populate();
|
|
|
+ }
|
|
|
}).catch(err => {
|
|
|
showDialog({
|
|
|
title: 'File Load Error',
|
|
@@ -556,64 +310,42 @@ class ContextManager implements IDisposable {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Test whether the context is fully populated.
|
|
|
- */
|
|
|
- isPopulated(id: string): boolean {
|
|
|
- let contextEx = this._contexts[id];
|
|
|
- return contextEx.isPopulated;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Finalize a context.
|
|
|
- */
|
|
|
- finalize(id: string): void {
|
|
|
- let contextEx = this._contexts[id];
|
|
|
- if (contextEx.isPopulated) {
|
|
|
- return;
|
|
|
- }
|
|
|
- contextEx.isPopulated = true;
|
|
|
- this._contexts[id].context.populated.emit(void 0);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Create a checkpoint for a file.
|
|
|
+ * Create a checkpoint for the file.
|
|
|
*/
|
|
|
- createCheckpoint(id: string): Promise<IContents.ICheckpointModel> {
|
|
|
- let path = this._contexts[id].path;
|
|
|
- return this._manager.contents.createCheckpoint(path);
|
|
|
+ createCheckpoint(): Promise<IContents.ICheckpointModel> {
|
|
|
+ return this._manager.contents.createCheckpoint(this._path);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Delete a checkpoint for a file.
|
|
|
+ * Delete a checkpoint for the file.
|
|
|
*/
|
|
|
- deleteCheckpoint(id: string, checkpointID: string): Promise<void> {
|
|
|
- let path = this._contexts[id].path;
|
|
|
- return this._manager.contents.deleteCheckpoint(path, checkpointID);
|
|
|
+ deleteCheckpoint(checkpointID: string): Promise<void> {
|
|
|
+ return this._manager.contents.deleteCheckpoint(this._path, checkpointID);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Restore a file to a known checkpoint state.
|
|
|
+ * Restore the file to a known checkpoint state.
|
|
|
*/
|
|
|
- restoreCheckpoint(id: string, checkpointID?: string): Promise<void> {
|
|
|
- let path = this._contexts[id].path;
|
|
|
+ restoreCheckpoint(checkpointID?: string): Promise<void> {
|
|
|
+ let contents = this._manager.contents;
|
|
|
+ let path = this._path;
|
|
|
if (checkpointID) {
|
|
|
- return this._manager.contents.restoreCheckpoint(path, checkpointID);
|
|
|
+ return contents.restoreCheckpoint(path, checkpointID);
|
|
|
}
|
|
|
- return this.listCheckpoints(id).then(checkpoints => {
|
|
|
+ return this.listCheckpoints().then(checkpoints => {
|
|
|
if (!checkpoints.length) {
|
|
|
return;
|
|
|
}
|
|
|
checkpointID = checkpoints[checkpoints.length - 1].id;
|
|
|
- return this._manager.contents.restoreCheckpoint(path, checkpointID);
|
|
|
+ return contents.restoreCheckpoint(path, checkpointID);
|
|
|
});
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* List available checkpoints for a file.
|
|
|
*/
|
|
|
- listCheckpoints(id: string): Promise<IContents.ICheckpointModel[]> {
|
|
|
- let path = this._contexts[id].path;
|
|
|
- return this._manager.contents.listCheckpoints(path);
|
|
|
+ listCheckpoints(): Promise<IContents.ICheckpointModel[]> {
|
|
|
+ return this._manager.contents.listCheckpoints(this._path);
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -626,13 +358,12 @@ class ContextManager implements IDisposable {
|
|
|
/**
|
|
|
* Resolve a relative url to a correct server path.
|
|
|
*/
|
|
|
- resolveUrl(id: string, url: string): string {
|
|
|
+ resolveUrl(url: string): string {
|
|
|
// Ignore urls that have a protocol.
|
|
|
if (utils.urlParse(url).protocol || url.indexOf('//') === 0) {
|
|
|
return url;
|
|
|
}
|
|
|
- let contextEx = this._contexts[id];
|
|
|
- let cwd = ContentsManager.dirname(contextEx.path);
|
|
|
+ let cwd = ContentsManager.dirname(this._path);
|
|
|
let path = ContentsManager.getAbsolutePath(url, cwd);
|
|
|
return this._manager.contents.getDownloadUrl(path);
|
|
|
}
|
|
@@ -640,31 +371,31 @@ class ContextManager implements IDisposable {
|
|
|
/**
|
|
|
* Add a sibling widget to the document manager.
|
|
|
*/
|
|
|
- addSibling(id: string, widget: Widget): IDisposable {
|
|
|
+ addSibling(widget: Widget): IDisposable {
|
|
|
let opener = this._opener;
|
|
|
- return opener(id, widget);
|
|
|
+ opener(widget);
|
|
|
+ return new DisposableDelegate(() => {
|
|
|
+ widget.close();
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Start a session and set up its signals.
|
|
|
*/
|
|
|
- private _startSession(id: string, options: ISession.IOptions): Promise<IKernel> {
|
|
|
- let contextEx = this._contexts[id];
|
|
|
- let context = contextEx.context;
|
|
|
+ private _startSession(options: ISession.IOptions): Promise<IKernel> {
|
|
|
return this._manager.sessions.startNew(options).then(session => {
|
|
|
- if (contextEx.session) {
|
|
|
- contextEx.session.dispose();
|
|
|
+ if (this._session) {
|
|
|
+ this._session.dispose();
|
|
|
}
|
|
|
- contextEx.session = session;
|
|
|
- context.kernelChanged.emit(session.kernel);
|
|
|
+ this._session = session;
|
|
|
+ this.kernelChanged.emit(session.kernel);
|
|
|
session.pathChanged.connect((s, path) => {
|
|
|
- if (path !== contextEx.path) {
|
|
|
- contextEx.path = path;
|
|
|
- context.pathChanged.emit(path);
|
|
|
+ if (path !== this._path) {
|
|
|
+ this.setPath(path);
|
|
|
}
|
|
|
});
|
|
|
session.kernelChanged.connect((s, kernel) => {
|
|
|
- context.kernelChanged.emit(kernel);
|
|
|
+ this.kernelChanged.emit(kernel);
|
|
|
});
|
|
|
return session.kernel;
|
|
|
});
|
|
@@ -690,36 +421,62 @@ class ContextManager implements IDisposable {
|
|
|
* Handle a change to the running sessions.
|
|
|
*/
|
|
|
private _onSessionsChanged(sender: ISession.IManager, models: ISession.IModel[]): void {
|
|
|
- for (let id in this._contexts) {
|
|
|
- let contextEx = this._contexts[id];
|
|
|
- let session = contextEx.session;
|
|
|
- if (!session) {
|
|
|
- continue;
|
|
|
- }
|
|
|
- let index = findIndex(models, model => model.id === session.id);
|
|
|
- if (index === -1) {
|
|
|
- session.dispose();
|
|
|
- contextEx.session = null;
|
|
|
- contextEx.context.kernelChanged.emit(null);
|
|
|
- }
|
|
|
+ let session = this._session;
|
|
|
+ if (!session) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ let index = findIndex(models, model => model.id === session.id);
|
|
|
+ if (index === -1) {
|
|
|
+ session.dispose();
|
|
|
+ this._session = null;
|
|
|
+ this.kernelChanged.emit(null);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Handle an initial population.
|
|
|
+ */
|
|
|
+ private _populate(): void {
|
|
|
+ this._isPopulated = true;
|
|
|
+ // Add a checkpoint if none exists.
|
|
|
+ this.listCheckpoints().then(checkpoints => {
|
|
|
+ if (!checkpoints) {
|
|
|
+ return this.createCheckpoint();
|
|
|
+ }
|
|
|
+ }).then(() => {
|
|
|
+ this.populated.emit(void 0);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
private _manager: IServiceManager = null;
|
|
|
- private _contexts: { [key: string]: Private.IContextEx } = Object.create(null);
|
|
|
- private _opener: (id: string, widget: Widget) => IDisposable = null;
|
|
|
+ private _opener: (widget: Widget) => void = null;
|
|
|
+ private _model: T = null;
|
|
|
+ private _path = '';
|
|
|
+ private _session: ISession = null;
|
|
|
+ private _factory: IModelFactory<T> = null;
|
|
|
+ private _saver: SaveHandler = null;
|
|
|
+ private _isPopulated = false;
|
|
|
+ private _contentsModel: IContents.IModel = null;
|
|
|
}
|
|
|
|
|
|
|
|
|
+// Define the signals for the `Context` class.
|
|
|
+defineSignal(Context.prototype, 'kernelChanged');
|
|
|
+defineSignal(Context.prototype, 'pathChanged');
|
|
|
+defineSignal(Context.prototype, 'contentsModelChanged');
|
|
|
+defineSignal(Context.prototype, 'populated');
|
|
|
+defineSignal(Context.prototype, 'disposed');
|
|
|
+
|
|
|
+
|
|
|
/**
|
|
|
- * A namespace for ContextManager statics.
|
|
|
+ * A namespace for `Context` statics.
|
|
|
*/
|
|
|
-export namespace ContextManager {
|
|
|
+export namespace Context {
|
|
|
/**
|
|
|
- * The options used to initialize a context manager.
|
|
|
+ * The options used to initialize a context.
|
|
|
*/
|
|
|
export
|
|
|
- interface IOptions {
|
|
|
+ interface IOptions<T extends IDocumentModel> {
|
|
|
/**
|
|
|
* A service manager instance.
|
|
|
*/
|
|
@@ -728,7 +485,17 @@ export namespace ContextManager {
|
|
|
/**
|
|
|
* A callback for opening sibling widgets.
|
|
|
*/
|
|
|
- opener: (id: string, widget: Widget) => IDisposable;
|
|
|
+ opener: (widget: Widget) => void;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The model factory used to create the model.
|
|
|
+ */
|
|
|
+ factory: IModelFactory<T>;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The initial path of the file.
|
|
|
+ */
|
|
|
+ path: string;
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -737,28 +504,11 @@ export namespace ContextManager {
|
|
|
* A namespace for private data.
|
|
|
*/
|
|
|
namespace Private {
|
|
|
- /**
|
|
|
- * An extended interface for data associated with a context.
|
|
|
- */
|
|
|
- export
|
|
|
- interface IContextEx {
|
|
|
- context: IDocumentContext<IDocumentModel>;
|
|
|
- model: IDocumentModel;
|
|
|
- session: ISession;
|
|
|
- fileType: IContents.FileType;
|
|
|
- fileFormat: IContents.FileFormat;
|
|
|
- path: string;
|
|
|
- contentsModel: IContents.IModel;
|
|
|
- modelName: string;
|
|
|
- isPopulated: boolean;
|
|
|
- saveHandler: SaveHandler;
|
|
|
- }
|
|
|
-
|
|
|
/**
|
|
|
* Get a new file path from the user.
|
|
|
*/
|
|
|
export
|
|
|
- function saveAs(path: string): Promise<string> {
|
|
|
+ function getSavePath(path: string): Promise<string> {
|
|
|
let input = document.createElement('input');
|
|
|
input.value = path;
|
|
|
return showDialog({
|