registry.ts 27 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import {
  4. Contents, ContentsManager, Kernel
  5. } from '@jupyterlab/services';
  6. import {
  7. IIterator, each, empty, map
  8. } from 'phosphor/lib/algorithm/iteration';
  9. import {
  10. find, findIndex, indexOf
  11. } from 'phosphor/lib/algorithm/searching';
  12. import {
  13. Vector
  14. } from 'phosphor/lib/collections/vector';
  15. import {
  16. IDisposable, DisposableDelegate
  17. } from 'phosphor/lib/core/disposable';
  18. import {
  19. ISignal, clearSignalData, defineSignal
  20. } from 'phosphor/lib/core/signaling';
  21. import {
  22. Token
  23. } from 'phosphor/lib/core/token';
  24. import {
  25. Widget
  26. } from 'phosphor/lib/ui/widget';
  27. import {
  28. CodeEditor
  29. } from '../codeeditor';
  30. import {
  31. IChangedArgs as IChangedArgsGeneric
  32. } from '../common/interfaces';
  33. /* tslint:disable */
  34. /**
  35. * The document registry token.
  36. */
  37. export
  38. const IDocumentRegistry = new Token<IDocumentRegistry>('jupyter.services.document-registry');
  39. /* tslint:enable */
  40. /**
  41. * The interface for a document registry.
  42. */
  43. export
  44. interface IDocumentRegistry extends DocumentRegistry {}
  45. /**
  46. * The document registry.
  47. */
  48. export
  49. class DocumentRegistry implements IDisposable {
  50. /**
  51. * A signal emitted when the registry has changed.
  52. */
  53. readonly changed: ISignal<this, DocumentRegistry.IChangedArgs>;
  54. /**
  55. * Get whether the document registry has been disposed.
  56. */
  57. get isDisposed(): boolean {
  58. return this._widgetFactories === null;
  59. }
  60. /**
  61. * Dispose of the resources held by the document registery.
  62. */
  63. dispose(): void {
  64. if (this._widgetFactories === null) {
  65. return;
  66. }
  67. let widgetFactories = this._widgetFactories;
  68. let modelFactories = this._modelFactories;
  69. let extenders = this._extenders;
  70. this._widgetFactories = null;
  71. this._modelFactories = null;
  72. this._extenders = null;
  73. for (let modelName in modelFactories) {
  74. modelFactories[modelName].dispose();
  75. }
  76. for (let widgetName in widgetFactories) {
  77. widgetFactories[widgetName].dispose();
  78. }
  79. for (let widgetName in extenders) {
  80. extenders[widgetName].clear();
  81. }
  82. this._fileTypes.clear();
  83. this._creators.clear();
  84. clearSignalData(this);
  85. }
  86. /**
  87. * Add a widget factory to the registry.
  88. *
  89. * @param factory - The factory instance to register.
  90. *
  91. * @returns A disposable which will unregister the factory.
  92. *
  93. * #### Notes
  94. * If a factory with the given `'displayName'` is already registered,
  95. * a warning will be logged, and this will be a no-op.
  96. * If `'*'` is given as a default extension, the factory will be registered
  97. * as the global default.
  98. * If an extension or global default is already registered, this factory
  99. * will override the existing default.
  100. */
  101. addWidgetFactory(factory: DocumentRegistry.WidgetFactory): IDisposable {
  102. let name = factory.name.toLowerCase();
  103. if (this._widgetFactories[name]) {
  104. console.warn(`Duplicate registered factory ${name}`);
  105. return new DisposableDelegate(null);
  106. }
  107. this._widgetFactories[name] = factory;
  108. for (let ext of factory.defaultFor) {
  109. if (factory.fileExtensions.indexOf(ext) === -1) {
  110. continue;
  111. }
  112. if (ext === '*') {
  113. this._defaultWidgetFactory = name;
  114. } else {
  115. this._defaultWidgetFactories[ext] = name;
  116. }
  117. }
  118. // For convenience, store a mapping of ext -> name
  119. for (let ext of factory.fileExtensions) {
  120. if (!this._widgetFactoryExtensions[ext]) {
  121. this._widgetFactoryExtensions[ext] = new Vector<string>();
  122. }
  123. this._widgetFactoryExtensions[ext].pushBack(name);
  124. }
  125. this.changed.emit({
  126. type: 'widgetFactory',
  127. name,
  128. change: 'added'
  129. });
  130. return new DisposableDelegate(() => {
  131. delete this._widgetFactories[name];
  132. if (this._defaultWidgetFactory === name) {
  133. this._defaultWidgetFactory = '';
  134. }
  135. for (let ext of Object.keys(this._defaultWidgetFactories)) {
  136. if (this._defaultWidgetFactories[ext] === name) {
  137. delete this._defaultWidgetFactories[ext];
  138. }
  139. }
  140. for (let ext of Object.keys(this._widgetFactoryExtensions)) {
  141. this._widgetFactoryExtensions[ext].remove(name);
  142. if (this._widgetFactoryExtensions[ext].length === 0) {
  143. delete this._widgetFactoryExtensions[ext];
  144. }
  145. }
  146. this.changed.emit({
  147. type: 'widgetFactory',
  148. name,
  149. change: 'removed'
  150. });
  151. });
  152. }
  153. /**
  154. * Add a model factory to the registry.
  155. *
  156. * @param factory - The factory instance.
  157. *
  158. * @returns A disposable which will unregister the factory.
  159. *
  160. * #### Notes
  161. * If a factory with the given `name` is already registered, or
  162. * the given factory is already registered, a warning will be logged
  163. * and this will be a no-op.
  164. */
  165. addModelFactory(factory: DocumentRegistry.ModelFactory): IDisposable {
  166. let name = factory.name.toLowerCase();
  167. if (this._modelFactories[name]) {
  168. console.warn(`Duplicate registered factory ${name}`);
  169. return new DisposableDelegate(null);
  170. }
  171. this._modelFactories[name] = factory;
  172. this.changed.emit({
  173. type: 'modelFactory',
  174. name,
  175. change: 'added'
  176. });
  177. return new DisposableDelegate(() => {
  178. delete this._modelFactories[name];
  179. this.changed.emit({
  180. type: 'modelFactory',
  181. name,
  182. change: 'removed'
  183. });
  184. });
  185. }
  186. /**
  187. * Add a widget extension to the registry.
  188. *
  189. * @param widgetName - The name of the widget factory.
  190. *
  191. * @param extension - A widget extension.
  192. *
  193. * @returns A disposable which will unregister the extension.
  194. *
  195. * #### Notes
  196. * If the extension is already registered for the given
  197. * widget name, a warning will be logged and this will be a no-op.
  198. */
  199. addWidgetExtension(widgetName: string, extension: DocumentRegistry.WidgetExtension): IDisposable {
  200. widgetName = widgetName.toLowerCase();
  201. if (!(widgetName in this._extenders)) {
  202. this._extenders[widgetName] = new Vector<DocumentRegistry.WidgetExtension>();
  203. }
  204. let extenders = this._extenders[widgetName];
  205. let index = indexOf(extenders, extension);
  206. if (index !== -1) {
  207. console.warn(`Duplicate registered extension for ${widgetName}`);
  208. return new DisposableDelegate(null);
  209. }
  210. this._extenders[widgetName].pushBack(extension);
  211. this.changed.emit({
  212. type: 'widgetExtension',
  213. name: null,
  214. change: 'added'
  215. });
  216. return new DisposableDelegate(() => {
  217. this._extenders[widgetName].remove(extension);
  218. this.changed.emit({
  219. type: 'widgetExtension',
  220. name: null,
  221. change: 'removed'
  222. });
  223. });
  224. }
  225. /**
  226. * Add a file type to the document registry.
  227. *
  228. * @params fileType - The file type object to register.
  229. *
  230. * @returns A disposable which will unregister the command.
  231. *
  232. * #### Notes
  233. * These are used to populate the "Create New" dialog.
  234. */
  235. addFileType(fileType: DocumentRegistry.IFileType): IDisposable {
  236. this._fileTypes.pushBack(fileType);
  237. this.changed.emit({
  238. type: 'fileType',
  239. name: fileType.name,
  240. change: 'added'
  241. });
  242. return new DisposableDelegate(() => {
  243. this._fileTypes.remove(fileType);
  244. this.changed.emit({
  245. type: 'fileType',
  246. name: fileType.name,
  247. change: 'removed'
  248. });
  249. });
  250. }
  251. /**
  252. * Add a creator to the registry.
  253. *
  254. * @params creator - The file creator object to register.
  255. *
  256. * @returns A disposable which will unregister the creator.
  257. */
  258. addCreator(creator: DocumentRegistry.IFileCreator): IDisposable {
  259. let index = findIndex(this._creators, (value) => {
  260. return value.name.localeCompare(creator.name) > 0;
  261. });
  262. if (index !== -1) {
  263. this._creators.insert(index, creator);
  264. } else {
  265. this._creators.pushBack(creator);
  266. }
  267. this.changed.emit({
  268. type: 'fileCreator',
  269. name: creator.name,
  270. change: 'added'
  271. });
  272. return new DisposableDelegate(() => {
  273. this._creators.remove(creator);
  274. this.changed.emit({
  275. type: 'fileCreator',
  276. name: creator.name,
  277. change: 'removed'
  278. });
  279. });
  280. }
  281. /**
  282. * Get a list of the preferred widget factories.
  283. *
  284. * @param ext - An optional file extension to filter the results.
  285. *
  286. * @returns A new array of widget factories.
  287. *
  288. * #### Notes
  289. * Only the widget factories whose associated model factory have
  290. * been registered will be returned.
  291. * The first item is considered the default. The returned iterator
  292. * has widget factories in the following order:
  293. * - extension-specific default factory
  294. * - global default factory
  295. * - all other extension-specific factories
  296. * - all other global factories
  297. */
  298. preferredWidgetFactories(ext: string = '*'): DocumentRegistry.WidgetFactory[] {
  299. let factories = new Set<string>();
  300. ext = Private.normalizeExtension(ext);
  301. let last = '.' + ext.split('.').pop();
  302. // Start with the extension-specific default factory.
  303. if (ext.length > 1) {
  304. if (ext in this._defaultWidgetFactories) {
  305. factories.add(this._defaultWidgetFactories[ext]);
  306. }
  307. }
  308. // Handle multi-part extension default factories.
  309. if (last !== ext) {
  310. if (last in this._defaultWidgetFactories) {
  311. factories.add(this._defaultWidgetFactories[last]);
  312. }
  313. }
  314. // Add the global default factory.
  315. if (this._defaultWidgetFactory) {
  316. factories.add(this._defaultWidgetFactory);
  317. }
  318. // Add the extension-specific factories in registration order.
  319. if (ext.length > 1) {
  320. if (ext in this._widgetFactoryExtensions) {
  321. each(this._widgetFactoryExtensions[ext], n => {
  322. factories.add(n);
  323. });
  324. }
  325. }
  326. // Handle multi-part extension-specific factories.
  327. if (last !== ext) {
  328. if (last in this._widgetFactoryExtensions) {
  329. each(this._widgetFactoryExtensions[last], n => {
  330. factories.add(n);
  331. });
  332. }
  333. }
  334. // Add the rest of the global factories, in registration order.
  335. if ('*' in this._widgetFactoryExtensions) {
  336. each(this._widgetFactoryExtensions['*'], n => {
  337. factories.add(n);
  338. });
  339. }
  340. // Construct the return list, checking to make sure the corresponding
  341. // model factories are registered.
  342. let factoryList: DocumentRegistry.WidgetFactory[] = [];
  343. factories.forEach(name => {
  344. if (this._widgetFactories[name].modelName in this._modelFactories) {
  345. factoryList.push(this._widgetFactories[name]);
  346. }
  347. });
  348. return factoryList;
  349. }
  350. /**
  351. * Get the default widget factory for an extension.
  352. *
  353. * @param ext - An optional file extension to filter the results.
  354. *
  355. * @returns The default widget factory for an extension.
  356. *
  357. * #### Notes
  358. * This is equivalent to the first value in [[preferredWidgetFactories]].
  359. */
  360. defaultWidgetFactory(ext: string = '*'): DocumentRegistry.WidgetFactory {
  361. return this.preferredWidgetFactories(ext)[0];
  362. }
  363. /**
  364. * Create an iterator over the widget factories that have been registered.
  365. *
  366. * @returns A new iterator of widget factories.
  367. */
  368. widgetFactories(): IIterator<DocumentRegistry.WidgetFactory> {
  369. return map(Object.keys(this._widgetFactories), name => {
  370. return this._widgetFactories[name];
  371. });
  372. }
  373. /**
  374. * Create an iterator over the model factories that have been registered.
  375. *
  376. * @returns A new iterator of model factories.
  377. */
  378. modelFactories(): IIterator<DocumentRegistry.ModelFactory> {
  379. return map(Object.keys(this._modelFactories), name => {
  380. return this._modelFactories[name];
  381. });
  382. }
  383. /**
  384. * Create an iterator over the registered extensions for a given widget.
  385. *
  386. * @param widgetName - The name of the widget factory.
  387. *
  388. * @returns A new iterator over the widget extensions.
  389. */
  390. widgetExtensions(widgetName: string): IIterator<DocumentRegistry.WidgetExtension> {
  391. widgetName = widgetName.toLowerCase();
  392. if (!(widgetName in this._extenders)) {
  393. return empty<DocumentRegistry.WidgetExtension>();
  394. }
  395. return this._extenders[widgetName].iter();
  396. }
  397. /**
  398. * Create an iterator over the file types that have been registered.
  399. *
  400. * @returns A new iterator of file types.
  401. */
  402. fileTypes(): IIterator<DocumentRegistry.IFileType> {
  403. return this._fileTypes.iter();
  404. }
  405. /**
  406. * Create an iterator over the file creators that have been registered.
  407. *
  408. * @returns A new iterator of file creatores.
  409. */
  410. creators(): IIterator<DocumentRegistry.IFileCreator> {
  411. return this._creators.iter();
  412. }
  413. /**
  414. * Get a widget factory by name.
  415. *
  416. * @param widgetName - The name of the widget factory.
  417. *
  418. * @returns A widget factory instance.
  419. */
  420. getWidgetFactory(widgetName: string): DocumentRegistry.WidgetFactory {
  421. return this._widgetFactories[widgetName.toLowerCase()];
  422. }
  423. /**
  424. * Get a model factory by name.
  425. *
  426. * @param name - The name of the model factory.
  427. *
  428. * @returns A model factory instance.
  429. */
  430. getModelFactory(name: string): DocumentRegistry.ModelFactory {
  431. return this._modelFactories[name.toLowerCase()];
  432. }
  433. /**
  434. * Get a file type by name.
  435. */
  436. getFileType(name: string): DocumentRegistry.IFileType {
  437. name = name.toLowerCase();
  438. return find(this._fileTypes, fileType => {
  439. return fileType.name.toLowerCase() === name;
  440. });
  441. }
  442. /**
  443. * Get a creator by name.
  444. */
  445. getCreator(name: string): DocumentRegistry.IFileCreator {
  446. name = name.toLowerCase();
  447. return find(this._creators, creator => {
  448. return creator.name.toLowerCase() === name;
  449. });
  450. }
  451. /**
  452. * Get a kernel preference.
  453. *
  454. * @param ext - The file extension.
  455. *
  456. * @param widgetName - The name of the widget factory.
  457. *
  458. * @returns A kernel preference.
  459. */
  460. getKernelPreference(ext: string, widgetName: string): DocumentRegistry.IKernelPreference {
  461. ext = Private.normalizeExtension(ext);
  462. widgetName = widgetName.toLowerCase();
  463. let widgetFactory = this._widgetFactories[widgetName];
  464. if (!widgetFactory) {
  465. return void 0;
  466. }
  467. let modelFactory = this.getModelFactory(widgetFactory.modelName);
  468. if (!modelFactory) {
  469. return void 0;
  470. }
  471. let language = modelFactory.preferredLanguage(ext);
  472. return {
  473. language,
  474. preferKernel: widgetFactory.preferKernel,
  475. canStartKernel: widgetFactory.canStartKernel
  476. };
  477. }
  478. private _modelFactories: { [key: string]: DocumentRegistry.ModelFactory } = Object.create(null);
  479. private _widgetFactories: { [key: string]: DocumentRegistry.WidgetFactory } = Object.create(null);
  480. private _defaultWidgetFactory = '';
  481. private _defaultWidgetFactories: { [key: string]: string } = Object.create(null);
  482. private _widgetFactoryExtensions: {[key: string]: Vector<string> } = Object.create(null);
  483. private _fileTypes = new Vector<DocumentRegistry.IFileType>();
  484. private _creators = new Vector<DocumentRegistry.IFileCreator>();
  485. private _extenders: { [key: string] : Vector<DocumentRegistry.WidgetExtension> } = Object.create(null);
  486. }
  487. /**
  488. * The namespace for the `DocumentRegistry` class statics.
  489. */
  490. export
  491. namespace DocumentRegistry {
  492. /**
  493. * The interface for a document model.
  494. */
  495. export
  496. interface IModel extends IDisposable {
  497. /**
  498. * A signal emitted when the document content changes.
  499. */
  500. contentChanged: ISignal<this, void>;
  501. /**
  502. * A signal emitted when the model state changes.
  503. */
  504. stateChanged: ISignal<this, IChangedArgsGeneric<any>>;
  505. /**
  506. * The dirty state of the model.
  507. *
  508. * #### Notes
  509. * This should be cleared when the document is loaded from
  510. * or saved to disk.
  511. */
  512. dirty: boolean;
  513. /**
  514. * The read-only state of the model.
  515. */
  516. readOnly: boolean;
  517. /**
  518. * The default kernel name of the document.
  519. */
  520. readonly defaultKernelName: string;
  521. /**
  522. * The default kernel language of the document.
  523. */
  524. readonly defaultKernelLanguage: string;
  525. /**
  526. * Serialize the model to a string.
  527. */
  528. toString(): string;
  529. /**
  530. * Deserialize the model from a string.
  531. *
  532. * #### Notes
  533. * Should emit a [contentChanged] signal.
  534. */
  535. fromString(value: string): void;
  536. /**
  537. * Serialize the model to JSON.
  538. */
  539. toJSON(): any;
  540. /**
  541. * Deserialize the model from JSON.
  542. *
  543. * #### Notes
  544. * Should emit a [contentChanged] signal.
  545. */
  546. fromJSON(value: any): void;
  547. }
  548. /**
  549. * The interface for a document model that represents code.
  550. */
  551. export
  552. interface ICodeModel extends IModel, CodeEditor.IModel { }
  553. /**
  554. * The document context object.
  555. */
  556. export
  557. interface IContext<T extends IModel> extends IDisposable {
  558. /**
  559. * A signal emitted when the kernel changes.
  560. */
  561. kernelChanged: ISignal<this, Kernel.IKernel>;
  562. /**
  563. * A signal emitted when the path changes.
  564. */
  565. pathChanged: ISignal<this, string>;
  566. /**
  567. * A signal emitted when the contentsModel changes.
  568. */
  569. fileChanged: ISignal<this, Contents.IModel>;
  570. /**
  571. * A signal emitted when the context is disposed.
  572. */
  573. disposed: ISignal<this, void>;
  574. /**
  575. * Get the model associated with the document.
  576. */
  577. readonly model: T;
  578. /**
  579. * The current kernel associated with the document.
  580. */
  581. readonly kernel: Kernel.IKernel;
  582. /**
  583. * The current path associated with the document.
  584. */
  585. readonly path: string;
  586. /**
  587. * The current contents model associated with the document
  588. *
  589. * #### Notes
  590. * The model will have an empty `contents` field.
  591. * It will be `null` until the context is ready.
  592. */
  593. readonly contentsModel: Contents.IModel;
  594. /**
  595. * Whether the context is ready.
  596. */
  597. readonly isReady: boolean;
  598. /**
  599. * A promise that is fulfilled when the context is ready.
  600. */
  601. readonly ready: Promise<void>;
  602. /**
  603. * Start the default kernel for the context.
  604. *
  605. * @returns A promise that resolves with the new kernel.
  606. */
  607. startDefaultKernel(): Promise<Kernel.IKernel>;
  608. /**
  609. * Change the current kernel associated with the document.
  610. *
  611. * #### Notes
  612. * If no options are given, the session is shut down.
  613. */
  614. changeKernel(options?: Kernel.IModel): Promise<Kernel.IKernel>;
  615. /**
  616. * Save the document contents to disk.
  617. */
  618. save(): Promise<void>;
  619. /**
  620. * Save the document to a different path chosen by the user.
  621. */
  622. saveAs(): Promise<void>;
  623. /**
  624. * Revert the document contents to disk contents.
  625. */
  626. revert(): Promise<void>;
  627. /**
  628. * Create a checkpoint for the file.
  629. *
  630. * @returns A promise which resolves with the new checkpoint model when the
  631. * checkpoint is created.
  632. */
  633. createCheckpoint(): Promise<Contents.ICheckpointModel>;
  634. /**
  635. * Delete a checkpoint for the file.
  636. *
  637. * @param checkpointID - The id of the checkpoint to delete.
  638. *
  639. * @returns A promise which resolves when the checkpoint is deleted.
  640. */
  641. deleteCheckpoint(checkpointID: string): Promise<void>;
  642. /**
  643. * Restore the file to a known checkpoint state.
  644. *
  645. * @param checkpointID - The optional id of the checkpoint to restore,
  646. * defaults to the most recent checkpoint.
  647. *
  648. * @returns A promise which resolves when the checkpoint is restored.
  649. */
  650. restoreCheckpoint(checkpointID?: string): Promise<void>;
  651. /**
  652. * List available checkpoints for the file.
  653. *
  654. * @returns A promise which resolves with a list of checkpoint models for
  655. * the file.
  656. */
  657. listCheckpoints(): Promise<Contents.ICheckpointModel[]>;
  658. /**
  659. * Resolve a url to a correct server path.
  660. *
  661. * @param url - The source url.
  662. *
  663. * @param local - Whether to to return the local path versus
  664. * a download path.
  665. *
  666. * @returns a promise which resolves with the resolved url.
  667. */
  668. resolveUrl(url: string, local: boolean): Promise<string>;
  669. /**
  670. * Add a sibling widget to the document manager.
  671. *
  672. * @param widget - The widget to add to the document manager.
  673. *
  674. * @returns A disposable used to remove the sibling if desired.
  675. *
  676. * #### Notes
  677. * It is assumed that the widget has the same model and context
  678. * as the original widget.
  679. */
  680. addSibling(widget: Widget): IDisposable;
  681. }
  682. /**
  683. * A type alias for a context.
  684. */
  685. export
  686. type Context = IContext<IModel>;
  687. /**
  688. * A type alias for a code context.
  689. */
  690. export
  691. type CodeContext = IContext<ICodeModel>;
  692. /**
  693. * The options used to initialize a widget factory.
  694. */
  695. export
  696. interface IWidgetFactoryOptions {
  697. /**
  698. * The file extensions the widget can view.
  699. *
  700. * #### Notes
  701. * Use "*" to denote all files. Specific file extensions must be preceded
  702. * with '.', like '.png', '.txt', etc. They may themselves contain a
  703. * period (e.g. .table.json).
  704. */
  705. readonly fileExtensions: string[];
  706. /**
  707. * The name of the widget to display in dialogs.
  708. */
  709. readonly name: string;
  710. /**
  711. * The file extensions for which the factory should be the default.
  712. *
  713. * #### Notes
  714. * Use "*" to denote all files. Specific file extensions must be preceded
  715. * with '.', like '.png', '.txt', etc. Entries in this attribute must also
  716. * be included in the fileExtensions attribute.
  717. * The default is an empty array.
  718. *
  719. * **See also:** [[fileExtensions]].
  720. */
  721. readonly defaultFor?: string[];
  722. /**
  723. * The registered name of the model type used to create the widgets.
  724. */
  725. readonly modelName?: string;
  726. /**
  727. * Whether the widgets prefer having a kernel started.
  728. */
  729. readonly preferKernel?: boolean;
  730. /**
  731. * Whether the widgets can start a kernel when opened.
  732. */
  733. readonly canStartKernel?: boolean;
  734. }
  735. /**
  736. * The interface for a widget factory.
  737. */
  738. export
  739. interface IWidgetFactory<T extends Widget, U extends IModel> extends IDisposable, IWidgetFactoryOptions {
  740. /**
  741. * A signal emitted when a widget is created.
  742. */
  743. widgetCreated: ISignal<IWidgetFactory<T, U>, T>;
  744. /**
  745. * Create a new widget given a context.
  746. *
  747. * #### Notes
  748. * It should emit the [widgetCreated] signal with the new widget.
  749. */
  750. createNew(context: IContext<U>): T;
  751. }
  752. /**
  753. * A type alias for a standard widget factory.
  754. */
  755. export
  756. type WidgetFactory = IWidgetFactory<Widget, IModel>;
  757. /**
  758. * An interface for a widget extension.
  759. */
  760. export
  761. interface IWidgetExtension<T extends Widget, U extends IModel> {
  762. /**
  763. * Create a new extension for a given widget.
  764. */
  765. createNew(widget: T, context: IContext<U>): IDisposable;
  766. }
  767. /**
  768. * A type alias for a standard widget extension.
  769. */
  770. export
  771. type WidgetExtension = IWidgetExtension<Widget, IModel>;
  772. /**
  773. * The interface for a model factory.
  774. */
  775. export
  776. interface IModelFactory<T extends IModel> extends IDisposable {
  777. /**
  778. * The name of the model.
  779. */
  780. readonly name: string;
  781. /**
  782. * The content type of the file (defaults to `"file"`).
  783. */
  784. readonly contentType: Contents.ContentType;
  785. /**
  786. * The format of the file (defaults to `"text"`).
  787. */
  788. readonly fileFormat: Contents.FileFormat;
  789. /**
  790. * Create a new model for a given path.
  791. *
  792. * @param languagePreference - An optional kernel language preference.
  793. *
  794. * @returns A new document model.
  795. */
  796. createNew(languagePreference?: string): T;
  797. /**
  798. * Get the preferred kernel language given an extension.
  799. */
  800. preferredLanguage(ext: string): string;
  801. }
  802. /**
  803. * A type alias for a standard model factory.
  804. */
  805. export
  806. type ModelFactory = IModelFactory<IModel>;
  807. /**
  808. * A type alias for a code model factory.
  809. */
  810. export
  811. type CodeModelFactory = IModelFactory<ICodeModel>;
  812. /**
  813. * A kernel preference for a given file path and widget.
  814. */
  815. export
  816. interface IKernelPreference {
  817. /**
  818. * The preferred kernel language.
  819. */
  820. readonly language: string;
  821. /**
  822. * Whether to prefer having a kernel started when opening.
  823. */
  824. readonly preferKernel: boolean;
  825. /**
  826. * Whether a kernel when can be started when opening.
  827. */
  828. readonly canStartKernel: boolean;
  829. }
  830. /**
  831. * An interface for a file type.
  832. */
  833. export
  834. interface IFileType {
  835. /**
  836. * The name of the file type.
  837. */
  838. readonly name: string;
  839. /**
  840. * The extension of the file type (e.g. `".txt"`). Can be a compound
  841. * extension (e.g. `".table.json:`).
  842. */
  843. readonly extension: string;
  844. /**
  845. * The optional mimetype of the file type.
  846. */
  847. readonly mimetype?: string;
  848. /**
  849. * The optional icon class to use for the file type.
  850. */
  851. readonly icon?: string;
  852. /**
  853. * The content type of the new file (defaults to `"file"`).
  854. */
  855. readonly contentType?: Contents.ContentType;
  856. /**
  857. * The format of the new file (default to `"text"`).
  858. */
  859. readonly fileFormat?: Contents.FileFormat;
  860. }
  861. /**
  862. * An interface for a "Create New" item.
  863. */
  864. export
  865. interface IFileCreator {
  866. /**
  867. * The name of the file creator.
  868. */
  869. readonly name: string;
  870. /**
  871. * The filetype name associated with the creator.
  872. */
  873. readonly fileType: string;
  874. /**
  875. * The optional widget name.
  876. */
  877. readonly widgetName?: string;
  878. /**
  879. * The optional kernel name.
  880. */
  881. readonly kernelName?: string;
  882. }
  883. /**
  884. * An arguments object for the `changed` signal.
  885. */
  886. export
  887. interface IChangedArgs {
  888. /**
  889. * The type of the changed item.
  890. */
  891. readonly type: 'widgetFactory' | 'modelFactory' | 'widgetExtension' | 'fileCreator' | 'fileType';
  892. /**
  893. * The name of the item.
  894. */
  895. readonly name: string;
  896. /**
  897. * Whether the item was added or removed.
  898. */
  899. readonly change: 'added' | 'removed';
  900. }
  901. /**
  902. * Get the extension name of a path.
  903. *
  904. * @param file - string.
  905. *
  906. * #### Notes
  907. * Dotted filenames (e.g. `".table.json"` are allowed.
  908. */
  909. export
  910. function extname(path: string): string {
  911. let parts = ContentsManager.basename(path).split('.');
  912. parts.shift();
  913. return '.' + parts.join('.');
  914. }
  915. }
  916. // Define the signals for the `DocumentRegistry` class.
  917. defineSignal(DocumentRegistry.prototype, 'changed');
  918. /**
  919. * A private namespace for DocumentRegistry data.
  920. */
  921. namespace Private {
  922. /**
  923. * Normalize a file extension to be of the type `'.foo'`.
  924. *
  925. * Adds a leading dot if not present and converts to lower case.
  926. */
  927. export
  928. function normalizeExtension(extension: string): string {
  929. if (extension === '*') {
  930. return extension;
  931. }
  932. if (extension === '.*') {
  933. return '*';
  934. }
  935. if (extension.indexOf('.') !== 0) {
  936. extension = `.${extension}`;
  937. }
  938. return extension.toLowerCase();
  939. }
  940. }