registry.ts 26 KB

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