index.ts 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. 'use strict';
  4. import {
  5. IContentsModel, IKernelId, IContentsOpts, IKernel,
  6. IContentsManager, INotebookSessionManager,
  7. IKernelSpecIds, ISessionId
  8. } from 'jupyter-js-services';
  9. import {
  10. IDisposable, DisposableDelegate
  11. } from 'phosphor-disposable';
  12. import {
  13. Message
  14. } from 'phosphor-messaging';
  15. import {
  16. PanelLayout
  17. } from 'phosphor-panel';
  18. import {
  19. ISignal, Signal
  20. } from 'phosphor-signaling';
  21. import {
  22. Widget
  23. } from 'phosphor-widget';
  24. import {
  25. showDialog
  26. } from '../dialog';
  27. import {
  28. IWidgetOpener
  29. } from '../filebrowser/browser';
  30. import {
  31. ContextManager
  32. } from './context';
  33. /**
  34. * The class name added to a document container widgets.
  35. */
  36. const DOCUMENT_CLASS = 'jp-DocumentWidget';
  37. /**
  38. * The interface for a document model.
  39. */
  40. export
  41. interface IDocumentModel extends IDisposable {
  42. /**
  43. * A signal emitted when the document content changes.
  44. */
  45. contentChanged: ISignal<IDocumentModel, any>;
  46. /**
  47. * A signal emitted when the model dirty state changes.
  48. */
  49. dirtyChanged: ISignal<IDocumentModel, boolean>;
  50. /**
  51. * The dirty state of the model.
  52. *
  53. * #### Notes
  54. * This should be cleared when the document is loaded from
  55. * or saved to disk.
  56. */
  57. dirty: boolean;
  58. /**
  59. * The read-only state of the model.
  60. */
  61. readOnly: boolean;
  62. /**
  63. * The default kernel name of the document.
  64. *
  65. * #### Notes
  66. * This is a read-only property.
  67. */
  68. defaultKernelName: string;
  69. /**
  70. * The default kernel language of the document.
  71. *
  72. * #### Notes
  73. * This is a read-only property.
  74. */
  75. defaultKernelLanguage: string;
  76. /**
  77. * Serialize the model to a string.
  78. */
  79. toString(): string;
  80. /**
  81. * Deserialize the model from a string.
  82. *
  83. * #### Notes
  84. * Should emit a [contentChanged] signal.
  85. */
  86. fromString(value: string): void;
  87. /**
  88. * Serialize the model to JSON.
  89. */
  90. toJSON(): any;
  91. /**
  92. * Deserialize the model from JSON.
  93. *
  94. * #### Notes
  95. * Should emit a [contentChanged] signal.
  96. */
  97. fromJSON(value: any): void;
  98. /**
  99. * Initialize the model state.
  100. */
  101. initialize(): void;
  102. }
  103. /**
  104. * The document context object.
  105. */
  106. export interface IDocumentContext extends IDisposable {
  107. /**
  108. * The unique id of the context.
  109. *
  110. * #### Notes
  111. * This is a read-only property.
  112. */
  113. id: string;
  114. /**
  115. * The current kernel associated with the document.
  116. *
  117. * #### Notes
  118. * This is a read-only propery.
  119. */
  120. kernel: IKernel;
  121. /**
  122. * The current path associated with the document.
  123. *
  124. * #### Notes
  125. * This is a read-only property.
  126. */
  127. path: string;
  128. /**
  129. * The current contents model associated with the document
  130. *
  131. * #### Notes
  132. * This is a read-only property. The model will have an
  133. * empty `contents` field.
  134. */
  135. contentsModel: IContentsModel;
  136. /**
  137. * Get the kernel spec information.
  138. *
  139. * #### Notes
  140. * This is a read-only property.
  141. */
  142. kernelSpecs: IKernelSpecIds;
  143. /**
  144. * A signal emitted when the kernel changes.
  145. */
  146. kernelChanged: ISignal<IDocumentContext, IKernel>;
  147. /**
  148. * A signal emitted when the path changes.
  149. */
  150. pathChanged: ISignal<IDocumentContext, string>;
  151. /**
  152. * Change the current kernel associated with the document.
  153. */
  154. changeKernel(options: IKernelId): Promise<IKernel>;
  155. /**
  156. * Save the document contents to disk.
  157. */
  158. save(): Promise<void>;
  159. /**
  160. * Save the document to a different path.
  161. */
  162. saveAs(path: string): Promise<void>;
  163. /**
  164. * Revert the document contents to disk contents.
  165. */
  166. revert(): Promise<void>;
  167. /**
  168. * Get the list of running sessions.
  169. */
  170. listSessions(): Promise<ISessionId[]>;
  171. /**
  172. * Add a sibling widget to the document manager.
  173. *
  174. * @param widget - The widget to add to the document manager.
  175. *
  176. * @returns A disposable used to remove the sibling if desired.
  177. *
  178. * #### Notes
  179. * It is assumed that the widget has the same model and context
  180. * as the original widget.
  181. */
  182. addSibling(widget: Widget): IDisposable;
  183. }
  184. /**
  185. * The options used to register a widget factory.
  186. */
  187. export
  188. interface IWidgetFactoryOptions {
  189. /**
  190. * The file extensions the widget can view.
  191. *
  192. * #### Notes
  193. * Use ".*" to denote all files.
  194. */
  195. fileExtensions: string[];
  196. /**
  197. * The name of the widget to display in dialogs.
  198. */
  199. displayName: string;
  200. /**
  201. * The registered name of the model type used to create the widgets.
  202. */
  203. modelName: string;
  204. /**
  205. * The file extensions for which the factory should be the default.
  206. *
  207. * #### Notes
  208. * Use ".*" to denote all files.
  209. */
  210. defaultFor?: string[];
  211. /**
  212. * Whether the widgets prefer having a kernel started.
  213. */
  214. preferKernel?: boolean;
  215. /**
  216. * Whether the widgets can start a kernel when opened.
  217. */
  218. canStartKernel?: boolean;
  219. }
  220. /**
  221. * The interface for a widget factory.
  222. */
  223. export
  224. interface IWidgetFactory<T extends Widget> extends IDisposable {
  225. /**
  226. * Create a new widget.
  227. */
  228. createNew(model: IDocumentModel, context: IDocumentContext, kernel?: IKernelId): T;
  229. /**
  230. * Take an action on a widget before closing it.
  231. *
  232. * @returns A promise that resolves to true if the document should close
  233. * and false otherwise.
  234. */
  235. beforeClose(model: IDocumentModel, context: IDocumentContext, widget: Widget): Promise<boolean>;
  236. }
  237. /**
  238. * The options used to register a model factory.
  239. */
  240. export
  241. interface IModelFactoryOptions {
  242. /**
  243. * The name of the model factory.
  244. */
  245. name: string;
  246. /**
  247. * The contents options used to fetch/save files.
  248. */
  249. contentsOptions: IContentsOpts;
  250. }
  251. /**
  252. * The interface for a model factory.
  253. */
  254. export
  255. interface IModelFactory extends IDisposable {
  256. /**
  257. * Create a new model for a given path.
  258. *
  259. * @param languagePreference - An optional kernel language preference.
  260. *
  261. * @returns A new document model.
  262. */
  263. createNew(languagePreference?: string): IDocumentModel;
  264. /**
  265. * Get the preferred kernel language given an extension.
  266. */
  267. preferredLanguage(ext: string): string;
  268. }
  269. /**
  270. * A kernel preference for a given file path and widget.
  271. */
  272. export
  273. interface IKernelPreference {
  274. /**
  275. * The preferred kernel language.
  276. */
  277. language: string;
  278. /**
  279. * Whether to prefer having a kernel started when opening.
  280. */
  281. preferKernel: boolean;
  282. /**
  283. * Whether a kernel when can be started when opening.
  284. */
  285. canStartKernel: boolean;
  286. }
  287. /**
  288. * An interface for a file type.
  289. */
  290. export
  291. interface IFileType {
  292. /**
  293. * The name of the file type.
  294. */
  295. name: string;
  296. /**
  297. * The extension of the file type (e.g. `".txt"`).
  298. */
  299. extension: string;
  300. /**
  301. * The optional mimetype of the file type.
  302. */
  303. mimetype?: string;
  304. /**
  305. * The optional icon class to use for the file type.
  306. */
  307. icon?: string;
  308. }
  309. /**
  310. * The document manager.
  311. *
  312. * #### Notes
  313. * The document manager is used to register model and widget creators,
  314. * and the file browser uses the document manager to create widgets. The
  315. * document manager maintains a context for each path and model type that is
  316. * open, and a list of widgets for each context. The document manager is in
  317. * control of the proper closing and disposal of the widgets and contexts.
  318. */
  319. export
  320. class DocumentManager implements IDisposable {
  321. /**
  322. * Construct a new document manager.
  323. */
  324. constructor(contentsManager: IContentsManager, sessionManager: INotebookSessionManager, kernelSpecs: IKernelSpecIds, opener: IWidgetOpener) {
  325. this._contentsManager = contentsManager;
  326. this._sessionManager = sessionManager;
  327. this._specs = kernelSpecs;
  328. this._contextManager = new ContextManager(contentsManager, sessionManager, kernelSpecs, (id: string, widget: Widget) => {
  329. let parent = this._createWidget('', id);
  330. parent.setContent(widget);
  331. opener.open(parent);
  332. return new DisposableDelegate(() => {
  333. parent.close();
  334. });
  335. });
  336. }
  337. /**
  338. * Get the kernel spec ids for the manager.
  339. *
  340. * #### Notes
  341. * This is a read-only property.
  342. */
  343. get kernelSpecs(): IKernelSpecIds {
  344. return this._specs;
  345. }
  346. /**
  347. * Get whether the document manager has been disposed.
  348. */
  349. get isDisposed(): boolean {
  350. return this._contentsManager === null;
  351. }
  352. /**
  353. * Dispose of the resources held by the document manager.
  354. */
  355. dispose(): void {
  356. if (this.isDisposed) {
  357. return;
  358. }
  359. for (let modelName in this._modelFactories) {
  360. this._modelFactories[modelName].factory.dispose();
  361. }
  362. this._modelFactories = null;
  363. for (let widgetName in this._widgetFactories) {
  364. this._widgetFactories[widgetName].factory.dispose();
  365. }
  366. this._widgetFactories = null;
  367. for (let id in this._widgets) {
  368. for (let widget of this._widgets[id]) {
  369. widget.dispose();
  370. }
  371. }
  372. this._widgets = null;
  373. this._contentsManager = null;
  374. this._sessionManager = null;
  375. this._contextManager.dispose();
  376. this._contextManager = null;
  377. }
  378. /**
  379. * Register a widget factory with the document manager.
  380. *
  381. * @param factory - The factory instance.
  382. *
  383. * @param options - The options used to register the factory.
  384. *
  385. * @returns A disposable used to unregister the factory.
  386. *
  387. * #### Notes
  388. * If a factory with the given `displayName` is already registered,
  389. * an error will be thrown.
  390. * If `'.*'` is given as a default extension, the factory will be registered
  391. * as the global default.
  392. * If a factory is already registered as a default for a given extension or
  393. * as the global default, this factory will override the existing default.
  394. */
  395. registerWidgetFactory(factory: IWidgetFactory<Widget>, options: IWidgetFactoryOptions): IDisposable {
  396. let name = options.displayName;
  397. let exOpt = options as Private.IWidgetFactoryEx;
  398. exOpt.factory = factory;
  399. if (this._widgetFactories[name]) {
  400. throw new Error(`Duplicate registered factory ${name}`);
  401. }
  402. this._widgetFactories[name] = exOpt;
  403. if (options.defaultFor) {
  404. for (let option of options.defaultFor) {
  405. if (option === '.*') {
  406. this._defaultWidgetFactory = name;
  407. } else if (options.fileExtensions.indexOf(option) !== -1) {
  408. this._defaultWidgetFactories[option] = name;
  409. }
  410. }
  411. }
  412. return new DisposableDelegate(() => {
  413. delete this._widgetFactories[name];
  414. if (this._defaultWidgetFactory === name) {
  415. this._defaultWidgetFactory = '';
  416. }
  417. for (let opt of Object.keys(this._defaultWidgetFactories)) {
  418. let n = this._defaultWidgetFactories[opt];
  419. if (n === name) {
  420. delete this._defaultWidgetFactories[opt];
  421. }
  422. }
  423. });
  424. }
  425. /**
  426. * Register a model factory.
  427. *
  428. * @param factory - The factory instance.
  429. *
  430. * @param options - The options used to register the factory.
  431. *
  432. * @returns A disposable used to unregister the factory.
  433. *
  434. * #### Notes
  435. * If a factory with the given `name` is already registered, an error
  436. * will be thrown.
  437. */
  438. registerModelFactory(factory: IModelFactory, options: IModelFactoryOptions): IDisposable {
  439. let exOpt = options as Private.IModelFactoryEx;
  440. let name = options.name;
  441. exOpt.factory = factory;
  442. if (this._modelFactories[name]) {
  443. throw new Error(`Duplicate registered factory ${name}`);
  444. }
  445. this._modelFactories[name] = exOpt;
  446. return new DisposableDelegate(() => {
  447. delete this._modelFactories[name];
  448. });
  449. }
  450. /**
  451. * Register a file type with the document manager.
  452. *
  453. * #### Notes
  454. * These are used to populate the "Create New" dialog.
  455. */
  456. registerFileType(fileType: IFileType): IDisposable {
  457. this._fileTypes.push(fileType);
  458. this._fileTypes.sort((a, b) => {
  459. a.name.localCompare(b.name);
  460. });
  461. return new DisposableDelegate(() => {
  462. let index = this._fileTypes.indexOf(fileType);
  463. this._fileTypes.splice(index, 1);
  464. });
  465. }
  466. /**
  467. * Get the list of registered widget factory display names.
  468. *
  469. * @param path - An optional file path to filter the results.
  470. *
  471. * #### Notes
  472. * The first item in the list is considered the default.
  473. */
  474. listWidgetFactories(ext?: string): string[] {
  475. ext = ext || '';
  476. let factories: string[] = [];
  477. let options: Private.IWidgetFactoryEx;
  478. let name = '';
  479. // If an extension was given, filter by extension.
  480. // Make sure the modelFactory is registered.
  481. if (ext.length > 1) {
  482. if (ext in this._defaultWidgetFactories) {
  483. name = this._defaultWidgetFactories[ext];
  484. options = this._widgetFactories[name];
  485. if (options.modelName in this._modelFactories) {
  486. factories.push(name);
  487. }
  488. }
  489. }
  490. // Add the rest of the valid widgetFactories that can open the path.
  491. for (name in this._widgetFactories) {
  492. if (factories.indexOf(name) !== -1) {
  493. continue;
  494. }
  495. options = this._widgetFactories[name];
  496. if (!(options.modelName in this._modelFactories)) {
  497. continue;
  498. }
  499. let exts = options.fileExtensions;
  500. if ((exts.indexOf(ext) !== -1) || (exts.indexOf('.*') !== -1)) {
  501. factories.push(name);
  502. }
  503. }
  504. // Add the default widget if it was not already added.
  505. name = this._defaultWidgetFactory;
  506. if (name && factories.indexOf(name) === -1) {
  507. options = this._widgetFactories[name];
  508. if (options.modelName in this._modelFactories) {
  509. factories.push(name);
  510. }
  511. }
  512. return factories;
  513. }
  514. /**
  515. * Get a list of file types that have been registered.
  516. */
  517. listFileTypes(): IFileType[] {
  518. return this._fileTypes.slice();
  519. }
  520. /**
  521. * Get the kernel preference.
  522. */
  523. getKernelPreference(ext: string, widgetName: string): IKernelPreference {
  524. let widgetFactoryEx = this._getWidgetFactoryEx(widgetName);
  525. let modelFactoryEx = this._getModelFactoryEx(widgetName);
  526. let language = modelFactoryEx.factory.preferredLanguage(ext);
  527. return {
  528. language,
  529. preferKernel: widgetFactoryEx.preferKernel,
  530. canStartKernel: widgetFactoryEx.canStartKernel
  531. };
  532. }
  533. /**
  534. * List the running notebook sessions.
  535. */
  536. listSessions(): Promise<ISessionId[]> {
  537. return this._sessionManager.listRunning();
  538. }
  539. /**
  540. * Open a file and return the widget used to display the contents.
  541. *
  542. * @param path - The file path to open.
  543. *
  544. * @param widgetName - The name of the widget factory to use.
  545. *
  546. * @param kernel - An optional kernel name/id to override the default.
  547. */
  548. open(path: string, widgetName='default', kernel?: IKernelId): DocumentWidget {
  549. if (widgetName === 'default') {
  550. widgetName = this.listWidgetFactories(path)[0];
  551. }
  552. let mFactoryEx = this._getModelFactoryEx(widgetName);
  553. if (!mFactoryEx) {
  554. return;
  555. }
  556. let widget: DocumentWidget;
  557. // Use an existing context if available.
  558. let id = this._contextManager.findContext(path, mFactoryEx.name);
  559. if (id) {
  560. widget = this._createWidget(widgetName, id);
  561. this._populateWidget(widget, kernel);
  562. return widget;
  563. }
  564. let lang = mFactoryEx.factory.preferredLanguage(path);
  565. let model = mFactoryEx.factory.createNew(lang);
  566. id = this._contextManager.createNew(path, model, mFactoryEx);
  567. widget = this._createWidget(widgetName, id);
  568. // Load the contents from disk.
  569. this._contextManager.revert(id).then(() => {
  570. model.initialize();
  571. this._populateWidget(widget, kernel);
  572. });
  573. return widget;
  574. }
  575. /**
  576. * Create a new file of the given name.
  577. *
  578. * @param path - The file path to use.
  579. *
  580. * @param widgetName - The name of the widget factory to use.
  581. *
  582. * @param kernel - An optional kernel name/id to override the default.
  583. */
  584. createNew(path: string, widgetName='default', kernel?: IKernelId): Widget {
  585. if (widgetName === 'default') {
  586. widgetName = this.listWidgetFactories(path)[0];
  587. }
  588. let mFactoryEx = this._getModelFactoryEx(widgetName);
  589. if (!mFactoryEx) {
  590. return;
  591. }
  592. let lang = mFactoryEx.factory.preferredLanguage(path);
  593. let model = mFactoryEx.factory.createNew(lang);
  594. let id = this._contextManager.createNew(path, model, mFactoryEx);
  595. let widget = this._createWidget(widgetName, id);
  596. // Save the contents to disk to get a valid contentsModel for the
  597. // context.
  598. this._contextManager.save(id).then(() => {
  599. model.initialize();
  600. this._populateWidget(widget, kernel);
  601. });
  602. return widget;
  603. }
  604. /**
  605. * Handle the renaming of an open document.
  606. *
  607. * @param oldPath - The previous path.
  608. *
  609. * @param newPath - The new path.
  610. */
  611. handleRename(oldPath: string, newPath: string): void {
  612. this._contextManager.rename(oldPath, newPath);
  613. }
  614. /**
  615. * Handle a file deletion.
  616. */
  617. handleDelete(path: string): void {
  618. // TODO: Leave all of the widgets open and flag them as orphaned?
  619. }
  620. /**
  621. * See if a widget already exists for the given path and widget name.
  622. *
  623. * #### Notes
  624. * This can be used to use an existing widget instead of opening
  625. * a new widget.
  626. */
  627. findWidget(path: string, widgetName='default'): Widget {
  628. let ids = this._contextManager.getIdsForPath(path);
  629. if (widgetName === 'default') {
  630. widgetName = this._defaultWidgetFactory;
  631. }
  632. for (let id of ids) {
  633. for (let widget of this._widgets[id]) {
  634. if (widget.name === widgetName) {
  635. return widget;
  636. }
  637. }
  638. }
  639. }
  640. /**
  641. * Clone a widget.
  642. *
  643. * #### Notes
  644. * This will create a new widget with the same model and context
  645. * as this widget.
  646. */
  647. clone(widget: DocumentWidget): DocumentWidget {
  648. let parent = this._createWidget(widget.name, widget.context.id);
  649. this._populateWidget(parent);
  650. return parent;
  651. }
  652. /**
  653. * Close the widgets associated with a given path.
  654. */
  655. closeFile(path: string): void {
  656. let ids = this._contextManager.getIdsForPath(path);
  657. for (let id of ids) {
  658. let widgets: DocumentWidget[] = this._widgets[id] || [];
  659. for (let w of widgets) {
  660. w.close();
  661. }
  662. }
  663. }
  664. /**
  665. * Close all of the open documents.
  666. */
  667. closeAll(): void {
  668. for (let id in this._widgets) {
  669. for (let w of this._widgets[id]) {
  670. w.close();
  671. }
  672. }
  673. }
  674. /**
  675. * Create a container widget and handle its lifecycle.
  676. */
  677. private _createWidget(name: string, id: string): DocumentWidget {
  678. let factory = this._widgetFactories[name].factory;
  679. let widget = new DocumentWidget(name, id, this._contextManager, factory, this._widgets);
  680. if (!(id in this._widgets)) {
  681. this._widgets[id] = [];
  682. }
  683. this._widgets[id].push(widget);
  684. return widget;
  685. }
  686. /**
  687. * Create a content widget and add it to the container widget.
  688. */
  689. private _populateWidget(parent: DocumentWidget, kernel?: IKernelId): void {
  690. let factory = this._widgetFactories[parent.name].factory;
  691. let id = parent.context.id;
  692. let model = this._contextManager.getModel(id);
  693. let context = this._contextManager.getContext(id);
  694. let child = factory.createNew(model, context, kernel);
  695. parent.setContent(child);
  696. }
  697. /**
  698. * Get the appropriate widget factory by name.
  699. */
  700. private _getWidgetFactoryEx(widgetName: string): Private.IWidgetFactoryEx {
  701. let options: Private.IWidgetFactoryEx;
  702. if (widgetName === 'default') {
  703. options = this._widgetFactories[this._defaultWidgetFactory];
  704. } else {
  705. options = this._widgetFactories[widgetName];
  706. }
  707. return options;
  708. }
  709. /**
  710. * Get the appropriate model factory given a widget factory.
  711. */
  712. private _getModelFactoryEx(widgetName: string): Private.IModelFactoryEx {
  713. let wFactoryEx = this._getWidgetFactoryEx(widgetName);
  714. if (!wFactoryEx) {
  715. return;
  716. }
  717. return this._modelFactories[wFactoryEx.modelName];
  718. }
  719. private _modelFactories: { [key: string]: Private.IModelFactoryEx } = Object.create(null);
  720. private _widgetFactories: { [key: string]: Private.IWidgetFactoryEx } = Object.create(null);
  721. private _defaultWidgetFactory = '';
  722. private _defaultWidgetFactories: { [key: string]: string } = Object.create(null);
  723. private _widgets: { [key: string]: DocumentWidget[] } = Object.create(null);
  724. private _contentsManager: IContentsManager = null;
  725. private _sessionManager: INotebookSessionManager = null;
  726. private _contextManager: ContextManager = null;
  727. private _specs: IKernelSpecIds = null;
  728. private _fileTypes: IFileType[] = [];
  729. }
  730. /**
  731. * A container widget for documents.
  732. */
  733. export
  734. class DocumentWidget extends Widget {
  735. /**
  736. * A signal emitted when the document widget is populated.
  737. */
  738. get populated(): ISignal<DocumentWidget, Widget> {
  739. return Private.populatedSignal.bind(this);
  740. }
  741. /**
  742. * Construct a new document widget.
  743. */
  744. constructor(name: string, id: string, manager: ContextManager, factory: IWidgetFactory<Widget>, widgets: { [key: string]: DocumentWidget[] }) {
  745. super();
  746. this.addClass(DOCUMENT_CLASS);
  747. this.layout = new PanelLayout();
  748. this._name = name;
  749. this._id = id;
  750. this._manager = manager;
  751. this._widgets = widgets;
  752. this.title.closable = true;
  753. }
  754. /**
  755. * Get the name of the widget.
  756. *
  757. * #### Notes
  758. * This is a read-only property.
  759. */
  760. get name(): string {
  761. return this._name;
  762. }
  763. /**
  764. * The context for the widget.
  765. *
  766. * #### Notes
  767. * This is a read-only property.
  768. */
  769. get context(): IDocumentContext {
  770. return this._manager.getContext(this._id);
  771. }
  772. /**
  773. * The content widget used by the document widget.
  774. */
  775. get content(): Widget {
  776. let layout = this.layout as PanelLayout;
  777. return layout.childAt(0);
  778. }
  779. /**
  780. * Bring up a dialog to select a kernel.
  781. */
  782. selectKernel(): Promise<IKernel> {
  783. // TODO: the dialog should take kernel information only,
  784. // and return kernel information. We then change the
  785. // kernel in the context.
  786. return void 0;
  787. }
  788. /**
  789. * Dispose of the resources held by the widget.
  790. */
  791. dispose(): void {
  792. if (this.isDisposed) {
  793. return;
  794. }
  795. // Remove the widget from the widget registry.
  796. let id = this._id;
  797. let index = this._widgets[id].indexOf(this);
  798. this._widgets[id].splice(index, 1);
  799. // Dispose of the context if this is the last widget using it.
  800. if (!this._widgets[id].length) {
  801. this._manager.removeContext(id);
  802. }
  803. this._manager = null;
  804. this._factory = null;
  805. this._widgets = null;
  806. super.dispose();
  807. }
  808. /**
  809. * Set the child the widget.
  810. *
  811. * #### Notes
  812. * This function is not intended to be called by user code.
  813. */
  814. setContent(child: Widget): void {
  815. let layout = this.layout as PanelLayout;
  816. if (layout.childAt(0)) {
  817. throw new Error('Content already set');
  818. }
  819. this.title.text = child.title.text;
  820. this.title.icon = child.title.icon;
  821. this.title.className = child.title.className;
  822. // Mirror this title based on the child.
  823. child.title.changed.connect(() => {
  824. this.title.text = child.title.text;
  825. this.title.icon = child.title.icon;
  826. this.title.className = child.title.className;
  827. });
  828. // Add the child widget to the layout.
  829. (this.layout as PanelLayout).addChild(child);
  830. this.populated.emit(child);
  831. }
  832. /**
  833. * Handle `'close-request'` messages.
  834. */
  835. protected onCloseRequest(msg: Message): void {
  836. let model = this._manager.getModel(this._id);
  837. let layout = this.layout as PanelLayout;
  838. let child = layout.childAt(0);
  839. // Handle dirty state.
  840. this._maybeClose(model.dirty).then(result => {
  841. if (result) {
  842. // Let the widget factory handle closing.
  843. return this._factory.beforeClose(model, this.context, child);
  844. }
  845. return result;
  846. }).then(result => {
  847. if (result) {
  848. // Perform close tasks.
  849. return this._actuallyClose();
  850. }
  851. return result;
  852. }).then(result => {
  853. if (result) {
  854. // Dispose of document widgets when they are closed.
  855. this.dispose();
  856. }
  857. }).catch(() => {
  858. this.dispose();
  859. });
  860. }
  861. /**
  862. * Ask the user whether to close an unsaved file.
  863. */
  864. private _maybeClose(dirty: boolean): Promise<boolean> {
  865. // Bail if the model is not dirty or other widgets are using the model.
  866. let widgets = this._widgets[this._id];
  867. if (!dirty || widgets.length > 1) {
  868. return Promise.resolve(true);
  869. }
  870. return showDialog({
  871. title: 'Close without saving?',
  872. body: `File "${this.title.text}" has unsaved changes, close without saving?`,
  873. host: this.node
  874. }).then(value => {
  875. if (value && value.text === 'OK') {
  876. return true;
  877. }
  878. return false;
  879. });
  880. }
  881. /**
  882. * Perform closing tasks for the widget.
  883. */
  884. private _actuallyClose(): Promise<boolean> {
  885. // Check for a dangling kernel.
  886. let widgets = this._widgets[this._id];
  887. let kernelId = this.context.kernel ? this.context.kernel.id : '';
  888. if (!kernelId || widgets.length > 1) {
  889. return Promise.resolve(true);
  890. }
  891. for (let id in this._widgets) {
  892. for (let widget of this._widgets[id]) {
  893. let kId = widget.context.kernel || widget.context.kernel.id;
  894. if (widget !== this && kId === kernelId) {
  895. return Promise.resolve(true);
  896. }
  897. }
  898. }
  899. return showDialog({
  900. title: 'Shut down kernel?',
  901. body: `Shut down ${this.context.kernel.name}?`,
  902. host: this.node
  903. }).then(value => {
  904. if (value && value.text === 'OK') {
  905. return this.context.kernel.shutdown();
  906. }
  907. }).then(() => {
  908. return true;
  909. });
  910. }
  911. private _manager: ContextManager = null;
  912. private _factory: IWidgetFactory<Widget> = null;
  913. private _id = '';
  914. private _name = '';
  915. private _widgets: { [key: string]: DocumentWidget[] } = null;
  916. }
  917. /**
  918. * A private namespace for DocumentManager data.
  919. */
  920. namespace Private {
  921. /**
  922. * A signal emitted when the document widget is populated.
  923. */
  924. export
  925. const populatedSignal = new Signal<DocumentWidget, Widget>();
  926. /**
  927. * An extended interface for a model factory and its options.
  928. */
  929. export
  930. interface IModelFactoryEx extends IModelFactoryOptions {
  931. factory: IModelFactory;
  932. }
  933. /**
  934. * An extended interface for a widget factory and its options.
  935. */
  936. export
  937. interface IWidgetFactoryEx extends IWidgetFactoryOptions {
  938. factory: IWidgetFactory<Widget>;
  939. }
  940. }