123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044 |
- // Copyright (c) Jupyter Development Team.
- // Distributed under the terms of the Modified BSD License.
- 'use strict';
- import {
- IContentsModel, IKernelId, IContentsOpts, IKernel,
- IContentsManager, INotebookSessionManager,
- IKernelSpecIds, ISessionId
- } from 'jupyter-js-services';
- import {
- 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 {
- ContextManager
- } from './context';
- /**
- * The class name added to a document container widgets.
- */
- const DOCUMENT_CLASS = 'jp-DocumentWidget';
- /**
- * The interface for a document model.
- */
- export
- interface IDocumentModel extends IDisposable {
- /**
- * A signal emitted when the document content changes.
- */
- contentChanged: ISignal<IDocumentModel, any>;
- /**
- * A signal emitted when the model dirty state changes.
- */
- dirtyChanged: ISignal<IDocumentModel, boolean>;
- /**
- * The dirty state of the model.
- *
- * #### Notes
- * This should be cleared when the document is loaded from
- * or saved to disk.
- */
- dirty: boolean;
- /**
- * The read-only state of the model.
- */
- readOnly: boolean;
- /**
- * The default kernel name of the document.
- *
- * #### Notes
- * This is a read-only property.
- */
- defaultKernelName: string;
- /**
- * The default kernel language of the document.
- *
- * #### Notes
- * This is a read-only property.
- */
- defaultKernelLanguage: string;
- /**
- * Serialize the model to a string.
- */
- toString(): string;
- /**
- * Deserialize the model from a string.
- *
- * #### Notes
- * Should emit a [contentChanged] signal.
- */
- fromString(value: string): void;
- /**
- * Serialize the model to JSON.
- */
- toJSON(): any;
- /**
- * Deserialize the model from JSON.
- *
- * #### Notes
- * Should emit a [contentChanged] signal.
- */
- fromJSON(value: any): void;
- /**
- * Initialize the model state.
- */
- initialize(): void;
- }
- /**
- * The document context object.
- */
- export interface IDocumentContext extends IDisposable {
- /**
- * The unique id of the context.
- *
- * #### Notes
- * This is a read-only property.
- */
- id: string;
- /**
- * The current kernel associated with the document.
- *
- * #### Notes
- * This is a read-only propery.
- */
- kernel: IKernel;
- /**
- * The current path associated with the document.
- *
- * #### Notes
- * This is a read-only property.
- */
- path: string;
- /**
- * The current contents model associated with the document
- *
- * #### Notes
- * This is a read-only property. The model will have an
- * empty `contents` field.
- */
- contentsModel: IContentsModel;
- /**
- * Get the kernel spec information.
- *
- * #### Notes
- * This is a read-only property.
- */
- kernelSpecs: IKernelSpecIds;
- /**
- * A signal emitted when the kernel changes.
- */
- kernelChanged: ISignal<IDocumentContext, IKernel>;
- /**
- * A signal emitted when the path changes.
- */
- pathChanged: ISignal<IDocumentContext, string>;
- /**
- * Change the current kernel associated with the document.
- */
- changeKernel(options: IKernelId): Promise<IKernel>;
- /**
- * Save the document contents to disk.
- */
- save(): Promise<void>;
- /**
- * Save the document to a different path.
- */
- saveAs(path: string): Promise<void>;
- /**
- * Revert the document contents to disk contents.
- */
- revert(): Promise<void>;
- /**
- * Get the list of running sessions.
- */
- listSessions(): Promise<ISessionId[]>;
- /**
- * 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.
- */
- addSibling(widget: Widget): IDisposable;
- }
- /**
- * The options used to register a widget factory.
- */
- export
- interface IWidgetFactoryOptions {
- /**
- * The file extensions the widget can view.
- *
- * #### Notes
- * Use ".*" to denote all files.
- */
- fileExtensions: string[];
- /**
- * The name of the widget to display in dialogs.
- */
- displayName: string;
- /**
- * The registered name of the model type used to create the widgets.
- */
- modelName: string;
- /**
- * The file extensions for which the factory should be the default.
- *
- * #### Notes
- * Use ".*" to denote all files.
- */
- defaultFor?: string[];
- /**
- * Whether the widgets prefer having a kernel started.
- */
- preferKernel?: boolean;
- /**
- * Whether the widgets can start a kernel when opened.
- */
- canStartKernel?: boolean;
- }
- /**
- * The interface for a widget factory.
- */
- export
- interface IWidgetFactory<T extends Widget> extends IDisposable {
- /**
- * Create a new widget.
- */
- createNew(model: IDocumentModel, context: IDocumentContext, kernel?: IKernelId): T;
- /**
- * Take an action on a widget before closing it.
- *
- * @returns A promise that resolves to true if the document should close
- * and false otherwise.
- */
- beforeClose(model: IDocumentModel, context: IDocumentContext, widget: Widget): Promise<boolean>;
- }
- /**
- * The options used to register a model factory.
- */
- export
- interface IModelFactoryOptions {
- /**
- * The name of the model factory.
- */
- name: string;
- /**
- * The contents options used to fetch/save files.
- */
- contentsOptions: IContentsOpts;
- }
- /**
- * The interface for a model factory.
- */
- export
- interface IModelFactory extends IDisposable {
- /**
- * Create a new model for a given path.
- *
- * @param languagePreference - An optional kernel language preference.
- *
- * @returns A new document model.
- */
- createNew(languagePreference?: string): IDocumentModel;
- /**
- * Get the preferred kernel language given an extension.
- */
- preferredLanguage(ext: string): string;
- }
- /**
- * A kernel preference for a given file path and widget.
- */
- export
- interface IKernelPreference {
- /**
- * The preferred kernel language.
- */
- language: string;
- /**
- * Whether to prefer having a kernel started when opening.
- */
- preferKernel: boolean;
- /**
- * Whether a kernel when can be started when opening.
- */
- canStartKernel: boolean;
- }
- /**
- * An interface for a file type.
- */
- export
- interface IFileType {
- /**
- * The name of the file type.
- */
- name: string;
- /**
- * The extension of the file type (e.g. `".txt"`).
- */
- extension: string;
- /**
- * The optional mimetype of the file type.
- */
- mimetype?: string;
- /**
- * The optional icon class to use for the file type.
- */
- icon?: string;
- }
- /**
- * The document manager.
- *
- * #### Notes
- * The document manager is used to register model and widget creators,
- * and the file browser uses the document manager to create widgets. The
- * document manager maintains a context for each path and model type that is
- * open, and a list of widgets for each context. The document manager is in
- * control of the proper closing and disposal of the widgets and contexts.
- */
- export
- class DocumentManager implements IDisposable {
- /**
- * Construct a new document manager.
- */
- constructor(contentsManager: IContentsManager, sessionManager: INotebookSessionManager, kernelSpecs: IKernelSpecIds, opener: IWidgetOpener) {
- 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();
- });
- });
- }
- /**
- * Get the kernel spec ids for the manager.
- *
- * #### Notes
- * This is a read-only property.
- */
- get kernelSpecs(): IKernelSpecIds {
- return this._specs;
- }
- /**
- * Get whether the document manager has been disposed.
- */
- get isDisposed(): boolean {
- return this._contentsManager === null;
- }
- /**
- * Dispose of the resources held by the document manager.
- */
- dispose(): void {
- if (this.isDisposed) {
- return;
- }
- for (let modelName in this._modelFactories) {
- this._modelFactories[modelName].factory.dispose();
- }
- this._modelFactories = null;
- for (let widgetName in this._widgetFactories) {
- this._widgetFactories[widgetName].factory.dispose();
- }
- this._widgetFactories = null;
- 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;
- }
- /**
- * Register a widget factory with the document manager.
- *
- * @param factory - The factory instance.
- *
- * @param options - The options used to register the factory.
- *
- * @returns A disposable used to unregister the factory.
- *
- * #### Notes
- * If a factory with the given `displayName` is already registered,
- * an error will be thrown.
- * If `'.*'` is given as a default extension, the factory will be registered
- * as the global default.
- * If a factory is already registered as a default for a given extension or
- * as the global default, this factory will override the existing default.
- */
- registerWidgetFactory(factory: IWidgetFactory<Widget>, options: IWidgetFactoryOptions): IDisposable {
- let name = options.displayName;
- let exOpt = options as Private.IWidgetFactoryEx;
- exOpt.factory = factory;
- if (this._widgetFactories[name]) {
- throw new Error(`Duplicate registered factory ${name}`);
- }
- this._widgetFactories[name] = exOpt;
- if (options.defaultFor) {
- for (let option of options.defaultFor) {
- if (option === '.*') {
- this._defaultWidgetFactory = name;
- } else if (options.fileExtensions.indexOf(option) !== -1) {
- this._defaultWidgetFactories[option] = name;
- }
- }
- }
- return new DisposableDelegate(() => {
- delete this._widgetFactories[name];
- if (this._defaultWidgetFactory === name) {
- this._defaultWidgetFactory = '';
- }
- for (let opt of Object.keys(this._defaultWidgetFactories)) {
- let n = this._defaultWidgetFactories[opt];
- if (n === name) {
- delete this._defaultWidgetFactories[opt];
- }
- }
- });
- }
- /**
- * Register a model factory.
- *
- * @param factory - The factory instance.
- *
- * @param options - The options used to register the factory.
- *
- * @returns A disposable used to unregister the factory.
- *
- * #### Notes
- * If a factory with the given `name` is already registered, an error
- * will be thrown.
- */
- registerModelFactory(factory: IModelFactory, options: IModelFactoryOptions): IDisposable {
- let exOpt = options as Private.IModelFactoryEx;
- let name = options.name;
- exOpt.factory = factory;
- if (this._modelFactories[name]) {
- throw new Error(`Duplicate registered factory ${name}`);
- }
- this._modelFactories[name] = exOpt;
- return new DisposableDelegate(() => {
- delete this._modelFactories[name];
- });
- }
- /**
- * Register a file type with the document manager.
- *
- * #### Notes
- * These are used to populate the "Create New" dialog.
- */
- registerFileType(fileType: IFileType): IDisposable {
- this._fileTypes.push(fileType);
- this._fileTypes.sort((a, b) => {
- a.name.localCompare(b.name);
- });
- return new DisposableDelegate(() => {
- let index = this._fileTypes.indexOf(fileType);
- this._fileTypes.splice(index, 1);
- });
- }
- /**
- * Get the list of registered widget factory display names.
- *
- * @param path - An optional file path to filter the results.
- *
- * #### Notes
- * The first item in the list is considered the default.
- */
- listWidgetFactories(ext?: string): string[] {
- ext = ext || '';
- let factories: string[] = [];
- let options: Private.IWidgetFactoryEx;
- let name = '';
- // If an extension was given, filter by extension.
- // Make sure the modelFactory is registered.
- if (ext.length > 1) {
- if (ext in this._defaultWidgetFactories) {
- name = this._defaultWidgetFactories[ext];
- options = this._widgetFactories[name];
- if (options.modelName in this._modelFactories) {
- factories.push(name);
- }
- }
- }
- // Add the rest of the valid widgetFactories that can open the path.
- for (name in this._widgetFactories) {
- if (factories.indexOf(name) !== -1) {
- continue;
- }
- options = this._widgetFactories[name];
- if (!(options.modelName in this._modelFactories)) {
- continue;
- }
- let exts = options.fileExtensions;
- if ((exts.indexOf(ext) !== -1) || (exts.indexOf('.*') !== -1)) {
- factories.push(name);
- }
- }
- // Add the default widget if it was not already added.
- name = this._defaultWidgetFactory;
- if (name && factories.indexOf(name) === -1) {
- options = this._widgetFactories[name];
- if (options.modelName in this._modelFactories) {
- factories.push(name);
- }
- }
- return factories;
- }
- /**
- * Get a list of file types that have been registered.
- */
- listFileTypes(): IFileType[] {
- return this._fileTypes.slice();
- }
- /**
- * Get the kernel preference.
- */
- getKernelPreference(ext: string, widgetName: string): IKernelPreference {
- let widgetFactoryEx = this._getWidgetFactoryEx(widgetName);
- let modelFactoryEx = this._getModelFactoryEx(widgetName);
- let language = modelFactoryEx.factory.preferredLanguage(ext);
- return {
- language,
- preferKernel: widgetFactoryEx.preferKernel,
- canStartKernel: widgetFactoryEx.canStartKernel
- };
- }
- /**
- * List the running notebook sessions.
- */
- listSessions(): Promise<ISessionId[]> {
- return this._sessionManager.listRunning();
- }
- /**
- * Open a file and return the widget used to display the contents.
- *
- * @param path - The file path to open.
- *
- * @param widgetName - The name of the widget factory to use.
- *
- * @param kernel - An optional kernel name/id to override the default.
- */
- open(path: string, widgetName='default', kernel?: IKernelId): DocumentWidget {
- if (widgetName === 'default') {
- widgetName = this.listWidgetFactories(path)[0];
- }
- let mFactoryEx = this._getModelFactoryEx(widgetName);
- if (!mFactoryEx) {
- return;
- }
- let widget: DocumentWidget;
- // Use an existing context if available.
- let id = this._contextManager.findContext(path, mFactoryEx.name);
- if (id) {
- widget = this._createWidget(widgetName, id);
- this._populateWidget(widget, kernel);
- return widget;
- }
- let lang = mFactoryEx.factory.preferredLanguage(path);
- let model = mFactoryEx.factory.createNew(lang);
- id = this._contextManager.createNew(path, model, mFactoryEx);
- widget = this._createWidget(widgetName, id);
- // Load the contents from disk.
- this._contextManager.revert(id).then(() => {
- model.initialize();
- this._populateWidget(widget, kernel);
- });
- return widget;
- }
- /**
- * Create a new file of the given name.
- *
- * @param path - The file path to use.
- *
- * @param widgetName - The name of the widget factory to use.
- *
- * @param kernel - An optional kernel name/id to override the default.
- */
- createNew(path: string, widgetName='default', kernel?: IKernelId): Widget {
- if (widgetName === 'default') {
- widgetName = this.listWidgetFactories(path)[0];
- }
- let mFactoryEx = this._getModelFactoryEx(widgetName);
- if (!mFactoryEx) {
- return;
- }
- let lang = mFactoryEx.factory.preferredLanguage(path);
- let model = mFactoryEx.factory.createNew(lang);
- let id = this._contextManager.createNew(path, model, mFactoryEx);
- let widget = this._createWidget(widgetName, id);
- // Save the contents to disk to get a valid contentsModel for the
- // context.
- this._contextManager.save(id).then(() => {
- model.initialize();
- this._populateWidget(widget, kernel);
- });
- return widget;
- }
- /**
- * Handle the renaming of an open document.
- *
- * @param oldPath - The previous path.
- *
- * @param newPath - The new path.
- */
- handleRename(oldPath: string, newPath: string): void {
- this._contextManager.rename(oldPath, newPath);
- }
- /**
- * Handle a file deletion.
- */
- handleDelete(path: string): void {
- // TODO: Leave all of the widgets open and flag them as orphaned?
- }
- /**
- * See if a widget already exists for the given path and widget name.
- *
- * #### Notes
- * This can be used to use an existing widget instead of opening
- * a new widget.
- */
- findWidget(path: string, widgetName='default'): Widget {
- let ids = this._contextManager.getIdsForPath(path);
- if (widgetName === 'default') {
- widgetName = this._defaultWidgetFactory;
- }
- for (let id of ids) {
- for (let widget of this._widgets[id]) {
- if (widget.name === widgetName) {
- return widget;
- }
- }
- }
- }
- /**
- * Clone a widget.
- *
- * #### Notes
- * This will create a new widget with the same model and context
- * as this widget.
- */
- clone(widget: DocumentWidget): DocumentWidget {
- let parent = this._createWidget(widget.name, widget.context.id);
- this._populateWidget(parent);
- return parent;
- }
- /**
- * 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: DocumentWidget[] = this._widgets[id] || [];
- for (let w of widgets) {
- w.close();
- }
- }
- }
- /**
- * Close all of the open documents.
- */
- closeAll(): void {
- for (let id in this._widgets) {
- for (let w of this._widgets[id]) {
- w.close();
- }
- }
- }
- /**
- * Create a container widget and handle its lifecycle.
- */
- private _createWidget(name: string, id: string): DocumentWidget {
- let factory = this._widgetFactories[name].factory;
- let widget = new DocumentWidget(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: DocumentWidget, kernel?: IKernelId): void {
- let factory = this._widgetFactories[parent.name].factory;
- let id = parent.context.id;
- let model = this._contextManager.getModel(id);
- let context = this._contextManager.getContext(id);
- let child = factory.createNew(model, context, kernel);
- parent.setContent(child);
- }
- /**
- * Get the appropriate widget factory by name.
- */
- private _getWidgetFactoryEx(widgetName: string): Private.IWidgetFactoryEx {
- let options: Private.IWidgetFactoryEx;
- if (widgetName === 'default') {
- options = this._widgetFactories[this._defaultWidgetFactory];
- } else {
- options = this._widgetFactories[widgetName];
- }
- return options;
- }
- /**
- * Get the appropriate model factory given a widget factory.
- */
- private _getModelFactoryEx(widgetName: string): Private.IModelFactoryEx {
- let wFactoryEx = this._getWidgetFactoryEx(widgetName);
- if (!wFactoryEx) {
- return;
- }
- return this._modelFactories[wFactoryEx.modelName];
- }
- private _modelFactories: { [key: string]: Private.IModelFactoryEx } = Object.create(null);
- private _widgetFactories: { [key: string]: Private.IWidgetFactoryEx } = Object.create(null);
- private _defaultWidgetFactory = '';
- private _defaultWidgetFactories: { [key: string]: string } = Object.create(null);
- private _widgets: { [key: string]: DocumentWidget[] } = Object.create(null);
- private _contentsManager: IContentsManager = null;
- private _sessionManager: INotebookSessionManager = null;
- private _contextManager: ContextManager = null;
- private _specs: IKernelSpecIds = null;
- private _fileTypes: IFileType[] = [];
- }
- /**
- * A container widget for documents.
- */
- export
- class DocumentWidget extends Widget {
- /**
- * A signal emitted when the document widget is populated.
- */
- get populated(): ISignal<DocumentWidget, Widget> {
- return Private.populatedSignal.bind(this);
- }
- /**
- * Construct a new document widget.
- */
- constructor(name: string, id: string, manager: ContextManager, factory: IWidgetFactory<Widget>, widgets: { [key: string]: DocumentWidget[] }) {
- super();
- this.addClass(DOCUMENT_CLASS);
- this.layout = new PanelLayout();
- this._name = name;
- this._id = id;
- this._manager = manager;
- this._widgets = widgets;
- 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 {
- 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);
- }
- /**
- * Bring up a dialog to select a kernel.
- */
- selectKernel(): Promise<IKernel> {
- // TODO: the dialog should take kernel information only,
- // and return kernel information. We then change the
- // kernel in the context.
- return void 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);
- }
- /**
- * Handle `'close-request'` messages.
- */
- 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(model, this.context, child);
- }
- 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;
- });
- }
- private _manager: ContextManager = null;
- private _factory: IWidgetFactory<Widget> = null;
- private _id = '';
- private _name = '';
- private _widgets: { [key: string]: DocumentWidget[] } = null;
- }
- /**
- * A private namespace for DocumentManager data.
- */
- namespace Private {
- /**
- * A signal emitted when the document widget is populated.
- */
- export
- const populatedSignal = new Signal<DocumentWidget, Widget>();
- /**
- * An extended interface for a model factory and its options.
- */
- export
- interface IModelFactoryEx extends IModelFactoryOptions {
- factory: IModelFactory;
- }
- /**
- * An extended interface for a widget factory and its options.
- */
- export
- interface IWidgetFactoryEx extends IWidgetFactoryOptions {
- factory: IWidgetFactory<Widget>;
- }
- }
|