|
@@ -6,47 +6,29 @@ import {
|
|
|
} from 'jupyter-js-services';
|
|
|
|
|
|
import {
|
|
|
- IDisposable, DisposableDelegate, DisposableSet
|
|
|
+ IDisposable, DisposableDelegate
|
|
|
} from 'phosphor-disposable';
|
|
|
|
|
|
-import {
|
|
|
- Message
|
|
|
-} from 'phosphor-messaging';
|
|
|
-
|
|
|
-import {
|
|
|
- PanelLayout
|
|
|
-} from 'phosphor-panel';
|
|
|
-
|
|
|
-import {
|
|
|
- ISignal, Signal
|
|
|
-} from 'phosphor-signaling';
|
|
|
-
|
|
|
import {
|
|
|
Widget
|
|
|
} from 'phosphor-widget';
|
|
|
|
|
|
-import {
|
|
|
- showDialog
|
|
|
-} from '../dialog';
|
|
|
-
|
|
|
import {
|
|
|
IWidgetOpener
|
|
|
} from '../filebrowser/browser';
|
|
|
|
|
|
import {
|
|
|
- DocumentRegistry, IDocumentContext, IWidgetFactory, IWidgetFactoryOptions,
|
|
|
- IDocumentModel
|
|
|
+ DocumentRegistry, IWidgetFactory, IWidgetFactoryOptions,
|
|
|
+ IDocumentModel, IDocumentContext
|
|
|
} from '../docregistry';
|
|
|
|
|
|
import {
|
|
|
ContextManager
|
|
|
} from './context';
|
|
|
|
|
|
-
|
|
|
-/**
|
|
|
- * The class name added to a document wrapper widgets.
|
|
|
- */
|
|
|
-const DOCUMENT_CLASS = 'jp-DocumentWrapper';
|
|
|
+import {
|
|
|
+ DocumentWidgetManager
|
|
|
+} from './widgetmanager';
|
|
|
|
|
|
|
|
|
/**
|
|
@@ -64,18 +46,27 @@ class DocumentManager implements IDisposable {
|
|
|
/**
|
|
|
* Construct a new document manager.
|
|
|
*/
|
|
|
- constructor(registry: DocumentRegistry, contentsManager: IContentsManager, sessionManager: ISession.IManager, kernelspecs: IKernel.ISpecModels, opener: IWidgetOpener) {
|
|
|
- this._registry = registry;
|
|
|
- this._contentsManager = contentsManager;
|
|
|
- this._sessionManager = sessionManager;
|
|
|
- this._specs = kernelspecs;
|
|
|
- this._contextManager = new ContextManager(contentsManager, sessionManager, kernelspecs, (id: string, widget: Widget) => {
|
|
|
- let parent = this._createWidget('', id);
|
|
|
- parent.setContent(widget);
|
|
|
- opener.open(parent);
|
|
|
- return new DisposableDelegate(() => {
|
|
|
- parent.close();
|
|
|
- });
|
|
|
+ constructor(options: DocumentManager.IOptions) {
|
|
|
+ this._registry = options.registry;
|
|
|
+ this._contentsManager = options.contentsManager;
|
|
|
+ this._sessionManager = options.sessionManager;
|
|
|
+ this._specs = options.kernelspecs;
|
|
|
+ let opener = options.opener;
|
|
|
+ this._contextManager = new ContextManager({
|
|
|
+ contentsManager: this._contentsManager,
|
|
|
+ sessionManager: this._sessionManager,
|
|
|
+ kernelspecs: this._specs,
|
|
|
+ opener: (id: string, widget: Widget) => {
|
|
|
+ this._widgetManager.adoptWidget(id, widget);
|
|
|
+ opener.open(widget);
|
|
|
+ return new DisposableDelegate(() => {
|
|
|
+ widget.close();
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ this._widgetManager = new DocumentWidgetManager({
|
|
|
+ contextManager: this._contextManager,
|
|
|
+ registry: this._registry
|
|
|
});
|
|
|
}
|
|
|
|
|
@@ -113,16 +104,12 @@ class DocumentManager implements IDisposable {
|
|
|
if (this.isDisposed) {
|
|
|
return;
|
|
|
}
|
|
|
- for (let id in this._widgets) {
|
|
|
- for (let widget of this._widgets[id]) {
|
|
|
- widget.dispose();
|
|
|
- }
|
|
|
- }
|
|
|
- this._widgets = null;
|
|
|
this._contentsManager = null;
|
|
|
this._sessionManager = null;
|
|
|
this._contextManager.dispose();
|
|
|
this._contextManager = null;
|
|
|
+ this._widgetManager.dispose();
|
|
|
+ this._widgetManager = null;
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -134,7 +121,7 @@ class DocumentManager implements IDisposable {
|
|
|
*
|
|
|
* @param kernel - An optional kernel name/id to override the default.
|
|
|
*/
|
|
|
- open(path: string, widgetName='default', kernel?: IKernel.IModel): DocumentWrapper {
|
|
|
+ open(path: string, widgetName='default', kernel?: IKernel.IModel): Widget {
|
|
|
let registry = this._registry;
|
|
|
if (widgetName === 'default') {
|
|
|
let parts = path.split('.');
|
|
@@ -150,23 +137,20 @@ class DocumentManager implements IDisposable {
|
|
|
if (!mFactory) {
|
|
|
return;
|
|
|
}
|
|
|
- let widget: DocumentWrapper;
|
|
|
// Use an existing context if available.
|
|
|
let id = this._contextManager.findContext(path, mFactory.name);
|
|
|
if (id) {
|
|
|
- widget = this._createWidget(widgetName, id);
|
|
|
- this._populateWidget(widget, kernel);
|
|
|
- return widget;
|
|
|
+ return this._widgetManager.createWidget(widgetName, id, kernel);
|
|
|
}
|
|
|
let lang = mFactory.preferredLanguage(path);
|
|
|
let model = mFactory.createNew(lang);
|
|
|
id = this._contextManager.createNew(path, model, mFactory);
|
|
|
- widget = this._createWidget(widgetName, id);
|
|
|
// Load the contents from disk.
|
|
|
this._contextManager.revert(id).then(() => {
|
|
|
- this._populateWidget(widget, kernel);
|
|
|
+ model.dirty = false;
|
|
|
+ this._contextManager.finalize(id);
|
|
|
});
|
|
|
- return widget;
|
|
|
+ return this._widgetManager.createWidget(widgetName, id, kernel);
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -178,7 +162,7 @@ class DocumentManager implements IDisposable {
|
|
|
*
|
|
|
* @param kernel - An optional kernel name/id to override the default.
|
|
|
*/
|
|
|
- createNew(path: string, widgetName='default', kernel?: IKernel.IModel): DocumentWrapper {
|
|
|
+ createNew(path: string, widgetName='default', kernel?: IKernel.IModel): Widget {
|
|
|
let registry = this._registry;
|
|
|
if (widgetName === 'default') {
|
|
|
let parts = path.split('.');
|
|
@@ -197,13 +181,11 @@ class DocumentManager implements IDisposable {
|
|
|
let lang = mFactory.preferredLanguage(path);
|
|
|
let model = mFactory.createNew(lang);
|
|
|
let id = this._contextManager.createNew(path, model, mFactory);
|
|
|
- let widget = this._createWidget(widgetName, id);
|
|
|
- // Save the contents to disk to get a valid contentsModel for the
|
|
|
- // context.
|
|
|
this._contextManager.save(id).then(() => {
|
|
|
- this._populateWidget(widget, kernel);
|
|
|
+ model.dirty = false;
|
|
|
+ this._contextManager.finalize(id);
|
|
|
});
|
|
|
- return widget;
|
|
|
+ return this._widgetManager.createWidget(widgetName, id, kernel);
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -221,7 +203,7 @@ class DocumentManager implements IDisposable {
|
|
|
* @param newPath - The new path.
|
|
|
*/
|
|
|
handleRename(oldPath: string, newPath: string): void {
|
|
|
- this._contextManager.rename(oldPath, newPath);
|
|
|
+ this._contextManager.handleRename(oldPath, newPath);
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -238,18 +220,18 @@ class DocumentManager implements IDisposable {
|
|
|
* This can be used to use an existing widget instead of opening
|
|
|
* a new widget.
|
|
|
*/
|
|
|
- findWidget(path: string, widgetName='default'): DocumentWrapper {
|
|
|
- let ids = this._contextManager.getIdsForPath(path);
|
|
|
+ findWidget(path: string, widgetName='default'): Widget {
|
|
|
if (widgetName === 'default') {
|
|
|
widgetName = this._registry.defaultWidgetFactory;
|
|
|
}
|
|
|
- for (let id of ids) {
|
|
|
- for (let widget of this._widgets[id]) {
|
|
|
- if (widget.name === widgetName) {
|
|
|
- return widget;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+ return this._widgetManager.findWidget(path, widgetName);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get the document context for a widget.
|
|
|
+ */
|
|
|
+ contextForWidget(widget: Widget): IDocumentContext<IDocumentModel> {
|
|
|
+ return this._widgetManager.contextForWidget(widget);
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -259,267 +241,68 @@ class DocumentManager implements IDisposable {
|
|
|
* This will create a new widget with the same model and context
|
|
|
* as this widget.
|
|
|
*/
|
|
|
- clone(widget: DocumentWrapper): DocumentWrapper {
|
|
|
- let parent = this._createWidget(widget.name, widget.context.id);
|
|
|
- this._populateWidget(parent);
|
|
|
- return parent;
|
|
|
+ clone(widget: Widget): Widget {
|
|
|
+ return this._widgetManager.clone(widget);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Close the widgets associated with a given path.
|
|
|
*/
|
|
|
closeFile(path: string): void {
|
|
|
- let ids = this._contextManager.getIdsForPath(path);
|
|
|
- for (let id of ids) {
|
|
|
- let widgets: DocumentWrapper[] = this._widgets[id] || [];
|
|
|
- for (let w of widgets) {
|
|
|
- w.close();
|
|
|
- }
|
|
|
- }
|
|
|
+ this._widgetManager.closeFile(path);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Close all of the open documents.
|
|
|
*/
|
|
|
closeAll(): void {
|
|
|
- for (let id in this._widgets) {
|
|
|
- for (let w of this._widgets[id]) {
|
|
|
- w.close();
|
|
|
- }
|
|
|
- }
|
|
|
+ this._widgetManager.closeAll();
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * Create a container widget and handle its lifecycle.
|
|
|
- */
|
|
|
- private _createWidget(name: string, id: string): DocumentWrapper {
|
|
|
- let factory = this._registry.getWidgetFactory(name);
|
|
|
- let widget = new DocumentWrapper(name, id, this._contextManager, factory, this._widgets);
|
|
|
- if (!(id in this._widgets)) {
|
|
|
- this._widgets[id] = [];
|
|
|
- }
|
|
|
- this._widgets[id].push(widget);
|
|
|
- return widget;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Create a content widget and add it to the container widget.
|
|
|
- */
|
|
|
- private _populateWidget(parent: DocumentWrapper, kernel?: IKernel.IModel): void {
|
|
|
- let factory = this._registry.getWidgetFactory(parent.name);
|
|
|
- let id = parent.context.id;
|
|
|
- let model = this._contextManager.getModel(id);
|
|
|
- model.initialize();
|
|
|
- let context = this._contextManager.getContext(id);
|
|
|
- let child = factory.createNew(context, kernel);
|
|
|
- parent.setContent(child);
|
|
|
- // Handle widget extensions.
|
|
|
- let disposables = new DisposableSet();
|
|
|
- for (let extender of this._registry.getWidgetExtensions(parent.name)) {
|
|
|
- disposables.add(extender.createNew(child, context));
|
|
|
- }
|
|
|
- parent.disposed.connect(() => {
|
|
|
- disposables.dispose();
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- private _widgets: { [key: string]: DocumentWrapper[] } = Object.create(null);
|
|
|
private _contentsManager: IContentsManager = null;
|
|
|
private _sessionManager: ISession.IManager = null;
|
|
|
private _contextManager: ContextManager = null;
|
|
|
+ private _widgetManager: DocumentWidgetManager = null;
|
|
|
private _specs: IKernel.ISpecModels = null;
|
|
|
private _registry: DocumentRegistry = null;
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
- * A container widget for documents.
|
|
|
+ * A namespace for document manager statics.
|
|
|
*/
|
|
|
export
|
|
|
-class DocumentWrapper extends Widget {
|
|
|
- /**
|
|
|
- * A signal emitted when the document widget is populated.
|
|
|
- */
|
|
|
- get populated(): ISignal<DocumentWrapper, Widget> {
|
|
|
- return Private.populatedSignal.bind(this);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Construct a new document widget.
|
|
|
- */
|
|
|
- constructor(name: string, id: string, manager: ContextManager, factory: IWidgetFactory<Widget, IDocumentModel>, widgets: { [key: string]: DocumentWrapper[] }) {
|
|
|
- super();
|
|
|
- this.addClass(DOCUMENT_CLASS);
|
|
|
- this.layout = new PanelLayout();
|
|
|
- this._name = name;
|
|
|
- this._id = id;
|
|
|
- this._manager = manager;
|
|
|
- this._widgets = widgets;
|
|
|
- this._factory = factory;
|
|
|
- this.title.closable = true;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Get the name of the widget.
|
|
|
- *
|
|
|
- * #### Notes
|
|
|
- * This is a read-only property.
|
|
|
- */
|
|
|
- get name(): string {
|
|
|
- return this._name;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * The context for the widget.
|
|
|
- *
|
|
|
- * #### Notes
|
|
|
- * This is a read-only property.
|
|
|
- */
|
|
|
- get context(): IDocumentContext<IDocumentModel> {
|
|
|
- return this._manager.getContext(this._id);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * The content widget used by the document widget.
|
|
|
- */
|
|
|
- get content(): Widget {
|
|
|
- let layout = this.layout as PanelLayout;
|
|
|
- return layout.childAt(0);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Dispose of the resources held by the widget.
|
|
|
- */
|
|
|
- dispose(): void {
|
|
|
- if (this.isDisposed) {
|
|
|
- return;
|
|
|
- }
|
|
|
- // Remove the widget from the widget registry.
|
|
|
- let id = this._id;
|
|
|
- let index = this._widgets[id].indexOf(this);
|
|
|
- this._widgets[id].splice(index, 1);
|
|
|
- // Dispose of the context if this is the last widget using it.
|
|
|
- if (!this._widgets[id].length) {
|
|
|
- this._manager.removeContext(id);
|
|
|
- }
|
|
|
- this._manager = null;
|
|
|
- this._factory = null;
|
|
|
- this._widgets = null;
|
|
|
- super.dispose();
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Set the child the widget.
|
|
|
- *
|
|
|
- * #### Notes
|
|
|
- * This function is not intended to be called by user code.
|
|
|
- */
|
|
|
- setContent(child: Widget): void {
|
|
|
- let layout = this.layout as PanelLayout;
|
|
|
- if (layout.childAt(0)) {
|
|
|
- throw new Error('Content already set');
|
|
|
- }
|
|
|
- this.title.text = child.title.text;
|
|
|
- this.title.icon = child.title.icon;
|
|
|
- this.title.className = child.title.className;
|
|
|
- // Mirror this title based on the child.
|
|
|
- child.title.changed.connect(() => {
|
|
|
- this.title.text = child.title.text;
|
|
|
- this.title.icon = child.title.icon;
|
|
|
- this.title.className = child.title.className;
|
|
|
- });
|
|
|
- // Add the child widget to the layout.
|
|
|
- (this.layout as PanelLayout).addChild(child);
|
|
|
- this.populated.emit(child);
|
|
|
- }
|
|
|
-
|
|
|
+namespace DocumentManager {
|
|
|
/**
|
|
|
- * Handle `'close-request'` messages.
|
|
|
+ * The options used to initialize a document manager.
|
|
|
*/
|
|
|
- protected onCloseRequest(msg: Message): void {
|
|
|
- let model = this._manager.getModel(this._id);
|
|
|
- let layout = this.layout as PanelLayout;
|
|
|
- let child = layout.childAt(0);
|
|
|
- // Handle dirty state.
|
|
|
- this._maybeClose(model.dirty).then(result => {
|
|
|
- if (result) {
|
|
|
- // Let the widget factory handle closing.
|
|
|
- return this._factory.beforeClose(child, this.context);
|
|
|
- }
|
|
|
- return result;
|
|
|
- }).then(result => {
|
|
|
- if (result) {
|
|
|
- // Perform close tasks.
|
|
|
- return this._actuallyClose();
|
|
|
- }
|
|
|
- return result;
|
|
|
- }).then(result => {
|
|
|
- if (result) {
|
|
|
- // Dispose of document widgets when they are closed.
|
|
|
- this.dispose();
|
|
|
- }
|
|
|
- }).catch(() => {
|
|
|
- this.dispose();
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Ask the user whether to close an unsaved file.
|
|
|
- */
|
|
|
- private _maybeClose(dirty: boolean): Promise<boolean> {
|
|
|
- // Bail if the model is not dirty or other widgets are using the model.
|
|
|
- let widgets = this._widgets[this._id];
|
|
|
- if (!dirty || widgets.length > 1) {
|
|
|
- return Promise.resolve(true);
|
|
|
- }
|
|
|
- return showDialog({
|
|
|
- title: 'Close without saving?',
|
|
|
- body: `File "${this.title.text}" has unsaved changes, close without saving?`,
|
|
|
- host: this.node
|
|
|
- }).then(value => {
|
|
|
- if (value && value.text === 'OK') {
|
|
|
- return true;
|
|
|
- }
|
|
|
- return false;
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Perform closing tasks for the widget.
|
|
|
- */
|
|
|
- private _actuallyClose(): Promise<boolean> {
|
|
|
- // Check for a dangling kernel.
|
|
|
- let widgets = this._widgets[this._id];
|
|
|
- let kernelId = this.context.kernel ? this.context.kernel.id : '';
|
|
|
- if (!kernelId || widgets.length > 1) {
|
|
|
- return Promise.resolve(true);
|
|
|
- }
|
|
|
- for (let id in this._widgets) {
|
|
|
- for (let widget of this._widgets[id]) {
|
|
|
- let kId = widget.context.kernel || widget.context.kernel.id;
|
|
|
- if (widget !== this && kId === kernelId) {
|
|
|
- return Promise.resolve(true);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- return showDialog({
|
|
|
- title: 'Shut down kernel?',
|
|
|
- body: `Shut down ${this.context.kernel.name}?`,
|
|
|
- host: this.node
|
|
|
- }).then(value => {
|
|
|
- if (value && value.text === 'OK') {
|
|
|
- return this.context.kernel.shutdown();
|
|
|
- }
|
|
|
- }).then(() => {
|
|
|
- return true;
|
|
|
- });
|
|
|
+ export
|
|
|
+ interface IOptions {
|
|
|
+ /**
|
|
|
+ * A document registry instance.
|
|
|
+ */
|
|
|
+ registry: DocumentRegistry;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * A contents manager instance.
|
|
|
+ */
|
|
|
+ contentsManager: IContentsManager;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * A session manager instance.
|
|
|
+ */
|
|
|
+ sessionManager: ISession.IManager;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The system kernelspec information.
|
|
|
+ */
|
|
|
+ kernelspecs: IKernel.ISpecModels;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * A widget opener for sibling widgets.
|
|
|
+ */
|
|
|
+ opener: IWidgetOpener;
|
|
|
}
|
|
|
-
|
|
|
- private _manager: ContextManager = null;
|
|
|
- private _factory: IWidgetFactory<Widget, IDocumentModel> = null;
|
|
|
- private _id = '';
|
|
|
- private _name = '';
|
|
|
- private _widgets: { [key: string]: DocumentWrapper[] } = null;
|
|
|
}
|
|
|
|
|
|
|
|
@@ -527,12 +310,6 @@ class DocumentWrapper extends Widget {
|
|
|
* A private namespace for DocumentManager data.
|
|
|
*/
|
|
|
namespace Private {
|
|
|
- /**
|
|
|
- * A signal emitted when the document widget is populated.
|
|
|
- */
|
|
|
- export
|
|
|
- const populatedSignal = new Signal<DocumentWrapper, Widget>();
|
|
|
-
|
|
|
/**
|
|
|
* An extended interface for a widget factory and its options.
|
|
|
*/
|