registry.ts 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import {
  4. ArrayExt,
  5. ArrayIterator,
  6. IIterator,
  7. each,
  8. empty,
  9. find,
  10. map
  11. } from '@lumino/algorithm';
  12. import { PartialJSONValue, ReadonlyPartialJSONValue } from '@lumino/coreutils';
  13. import { IDisposable, DisposableDelegate } from '@lumino/disposable';
  14. import { ISignal, Signal } from '@lumino/signaling';
  15. import { DockLayout, Widget } from '@lumino/widgets';
  16. import { ISessionContext, Toolbar } from '@jupyterlab/apputils';
  17. import { CodeEditor } from '@jupyterlab/codeeditor';
  18. import {
  19. IChangedArgs as IChangedArgsGeneric,
  20. PathExt
  21. } from '@jupyterlab/coreutils';
  22. import { IModelDB } from '@jupyterlab/observables';
  23. import { IRenderMime } from '@jupyterlab/rendermime-interfaces';
  24. import { Contents, Kernel } from '@jupyterlab/services';
  25. import {
  26. fileIcon,
  27. folderIcon,
  28. imageIcon,
  29. JLIcon,
  30. jsonIcon,
  31. markdownIcon,
  32. notebookIcon,
  33. pythonIcon,
  34. rKernelIcon,
  35. spreadsheetIcon,
  36. yamlIcon
  37. } from '@jupyterlab/ui-components';
  38. import { TextModelFactory } from './default';
  39. /**
  40. * The document registry.
  41. */
  42. export class DocumentRegistry implements IDisposable {
  43. /**
  44. * Construct a new document registry.
  45. */
  46. constructor(options: DocumentRegistry.IOptions = {}) {
  47. let factory = options.textModelFactory;
  48. if (factory && factory.name !== 'text') {
  49. throw new Error('Text model factory must have the name `text`');
  50. }
  51. this._modelFactories['text'] = factory || new TextModelFactory();
  52. let fts =
  53. options.initialFileTypes?.map(ft =>
  54. DocumentRegistry.FileType.resolve(ft)
  55. ) || DocumentRegistry.defaultFileTypes;
  56. fts.forEach(ft => this._fileTypes.push(ft));
  57. }
  58. /**
  59. * A signal emitted when the registry has changed.
  60. */
  61. get changed(): ISignal<this, DocumentRegistry.IChangedArgs> {
  62. return this._changed;
  63. }
  64. /**
  65. * Get whether the document registry has been disposed.
  66. */
  67. get isDisposed(): boolean {
  68. return this._isDisposed;
  69. }
  70. /**
  71. * Dispose of the resources held by the document registery.
  72. */
  73. dispose(): void {
  74. if (this.isDisposed) {
  75. return;
  76. }
  77. this._isDisposed = true;
  78. for (let modelName in this._modelFactories) {
  79. this._modelFactories[modelName].dispose();
  80. }
  81. for (let widgetName in this._widgetFactories) {
  82. this._widgetFactories[widgetName].dispose();
  83. }
  84. for (let widgetName in this._extenders) {
  85. this._extenders[widgetName].length = 0;
  86. }
  87. this._fileTypes.length = 0;
  88. Signal.clearData(this);
  89. }
  90. /**
  91. * Add a widget factory to the registry.
  92. *
  93. * @param factory - The factory instance to register.
  94. *
  95. * @returns A disposable which will unregister the factory.
  96. *
  97. * #### Notes
  98. * If a factory with the given `'name'` is already registered,
  99. * a warning will be logged, and this will be a no-op.
  100. * If `'*'` is given as a default extension, the factory will be registered
  101. * as the global default.
  102. * If an extension or global default is already registered, this factory
  103. * will override the existing default.
  104. * The factory cannot be named an empty string or the string `'default'`.
  105. */
  106. addWidgetFactory(factory: DocumentRegistry.WidgetFactory): IDisposable {
  107. let name = factory.name.toLowerCase();
  108. if (!name || name === 'default') {
  109. throw Error('Invalid factory name');
  110. }
  111. if (this._widgetFactories[name]) {
  112. console.warn(`Duplicate registered factory ${name}`);
  113. return new DisposableDelegate(Private.noOp);
  114. }
  115. this._widgetFactories[name] = factory;
  116. for (let ft of factory.defaultFor || []) {
  117. if (factory.fileTypes.indexOf(ft) === -1) {
  118. continue;
  119. }
  120. if (ft === '*') {
  121. this._defaultWidgetFactory = name;
  122. } else {
  123. this._defaultWidgetFactories[ft] = name;
  124. }
  125. }
  126. for (let ft of factory.defaultRendered || []) {
  127. if (factory.fileTypes.indexOf(ft) === -1) {
  128. continue;
  129. }
  130. this._defaultRenderedWidgetFactories[ft] = name;
  131. }
  132. // For convenience, store a mapping of file type name -> name
  133. for (let ft of factory.fileTypes) {
  134. if (!this._widgetFactoriesForFileType[ft]) {
  135. this._widgetFactoriesForFileType[ft] = [];
  136. }
  137. this._widgetFactoriesForFileType[ft].push(name);
  138. }
  139. this._changed.emit({
  140. type: 'widgetFactory',
  141. name,
  142. change: 'added'
  143. });
  144. return new DisposableDelegate(() => {
  145. delete this._widgetFactories[name];
  146. if (this._defaultWidgetFactory === name) {
  147. this._defaultWidgetFactory = '';
  148. }
  149. for (let ext of Object.keys(this._defaultWidgetFactories)) {
  150. if (this._defaultWidgetFactories[ext] === name) {
  151. delete this._defaultWidgetFactories[ext];
  152. }
  153. }
  154. for (let ext of Object.keys(this._defaultRenderedWidgetFactories)) {
  155. if (this._defaultRenderedWidgetFactories[ext] === name) {
  156. delete this._defaultRenderedWidgetFactories[ext];
  157. }
  158. }
  159. for (let ext of Object.keys(this._widgetFactoriesForFileType)) {
  160. ArrayExt.removeFirstOf(this._widgetFactoriesForFileType[ext], name);
  161. if (this._widgetFactoriesForFileType[ext].length === 0) {
  162. delete this._widgetFactoriesForFileType[ext];
  163. }
  164. }
  165. for (let ext of Object.keys(this._defaultWidgetFactoryOverrides)) {
  166. if (this._defaultWidgetFactoryOverrides[ext] === name) {
  167. delete this._defaultWidgetFactoryOverrides[ext];
  168. }
  169. }
  170. this._changed.emit({
  171. type: 'widgetFactory',
  172. name,
  173. change: 'removed'
  174. });
  175. });
  176. }
  177. /**
  178. * Add a model factory to the registry.
  179. *
  180. * @param factory - The factory instance.
  181. *
  182. * @returns A disposable which will unregister the factory.
  183. *
  184. * #### Notes
  185. * If a factory with the given `name` is already registered, or
  186. * the given factory is already registered, a warning will be logged
  187. * and this will be a no-op.
  188. */
  189. addModelFactory(factory: DocumentRegistry.ModelFactory): IDisposable {
  190. let name = factory.name.toLowerCase();
  191. if (this._modelFactories[name]) {
  192. console.warn(`Duplicate registered factory ${name}`);
  193. return new DisposableDelegate(Private.noOp);
  194. }
  195. this._modelFactories[name] = factory;
  196. this._changed.emit({
  197. type: 'modelFactory',
  198. name,
  199. change: 'added'
  200. });
  201. return new DisposableDelegate(() => {
  202. delete this._modelFactories[name];
  203. this._changed.emit({
  204. type: 'modelFactory',
  205. name,
  206. change: 'removed'
  207. });
  208. });
  209. }
  210. /**
  211. * Add a widget extension to the registry.
  212. *
  213. * @param widgetName - The name of the widget factory.
  214. *
  215. * @param extension - A widget extension.
  216. *
  217. * @returns A disposable which will unregister the extension.
  218. *
  219. * #### Notes
  220. * If the extension is already registered for the given
  221. * widget name, a warning will be logged and this will be a no-op.
  222. */
  223. addWidgetExtension(
  224. widgetName: string,
  225. extension: DocumentRegistry.WidgetExtension
  226. ): IDisposable {
  227. widgetName = widgetName.toLowerCase();
  228. if (!(widgetName in this._extenders)) {
  229. this._extenders[widgetName] = [];
  230. }
  231. let extenders = this._extenders[widgetName];
  232. let index = ArrayExt.firstIndexOf(extenders, extension);
  233. if (index !== -1) {
  234. console.warn(`Duplicate registered extension for ${widgetName}`);
  235. return new DisposableDelegate(Private.noOp);
  236. }
  237. this._extenders[widgetName].push(extension);
  238. this._changed.emit({
  239. type: 'widgetExtension',
  240. name: widgetName,
  241. change: 'added'
  242. });
  243. return new DisposableDelegate(() => {
  244. ArrayExt.removeFirstOf(this._extenders[widgetName], extension);
  245. this._changed.emit({
  246. type: 'widgetExtension',
  247. name: widgetName,
  248. change: 'removed'
  249. });
  250. });
  251. }
  252. /**
  253. * Add a file type to the document registry.
  254. *
  255. * @params fileType - The file type object to register.
  256. *
  257. * @returns A disposable which will unregister the command.
  258. *
  259. * #### Notes
  260. * These are used to populate the "Create New" dialog.
  261. */
  262. addFileType(fileType: Partial<DocumentRegistry.IFileType>): IDisposable {
  263. let value: DocumentRegistry.IFileType = {
  264. ...DocumentRegistry.fileTypeDefaults,
  265. ...fileType
  266. };
  267. this._fileTypes.push(DocumentRegistry.FileType.resolve(value));
  268. this._changed.emit({
  269. type: 'fileType',
  270. name: value.name,
  271. change: 'added'
  272. });
  273. return new DisposableDelegate(() => {
  274. ArrayExt.removeFirstOf(this._fileTypes, value);
  275. this._changed.emit({
  276. type: 'fileType',
  277. name: fileType.name,
  278. change: 'removed'
  279. });
  280. });
  281. }
  282. /**
  283. * Get a list of the preferred widget factories.
  284. *
  285. * @param path - The file path to filter the results.
  286. *
  287. * @returns A new array of widget factories.
  288. *
  289. * #### Notes
  290. * Only the widget factories whose associated model factory have
  291. * been registered will be returned.
  292. * The first item is considered the default. The returned array
  293. * has widget factories in the following order:
  294. * - path-specific default factory
  295. * - path-specific default rendered factory
  296. * - global default factory
  297. * - all other path-specific factories
  298. * - all other global factories
  299. */
  300. preferredWidgetFactories(path: string): DocumentRegistry.WidgetFactory[] {
  301. let factories = new Set<string>();
  302. // Get the ordered matching file types.
  303. let fts = this.getFileTypesForPath(PathExt.basename(path));
  304. // Start with any user overrides for the defaults.
  305. fts.forEach(ft => {
  306. if (ft.name in this._defaultWidgetFactoryOverrides) {
  307. factories.add(this._defaultWidgetFactoryOverrides[ft.name]);
  308. }
  309. });
  310. // Next add the file type default factories.
  311. fts.forEach(ft => {
  312. if (ft.name in this._defaultWidgetFactories) {
  313. factories.add(this._defaultWidgetFactories[ft.name]);
  314. }
  315. });
  316. // Add the file type default rendered factories.
  317. fts.forEach(ft => {
  318. if (ft.name in this._defaultRenderedWidgetFactories) {
  319. factories.add(this._defaultRenderedWidgetFactories[ft.name]);
  320. }
  321. });
  322. // Add the global default factory.
  323. if (this._defaultWidgetFactory) {
  324. factories.add(this._defaultWidgetFactory);
  325. }
  326. // Add the file type factories in registration order.
  327. fts.forEach(ft => {
  328. if (ft.name in this._widgetFactoriesForFileType) {
  329. each(this._widgetFactoriesForFileType[ft.name], n => {
  330. factories.add(n);
  331. });
  332. }
  333. });
  334. // Add the rest of the global factories, in registration order.
  335. if ('*' in this._widgetFactoriesForFileType) {
  336. each(this._widgetFactoriesForFileType['*'], 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. let factory = this._widgetFactories[name];
  345. if (!factory) {
  346. return;
  347. }
  348. let modelName = factory.modelName || 'text';
  349. if (modelName in this._modelFactories) {
  350. factoryList.push(factory);
  351. }
  352. });
  353. return factoryList;
  354. }
  355. /**
  356. * Get the default rendered widget factory for a path.
  357. *
  358. * @param path - The path to for which to find a widget factory.
  359. *
  360. * @returns The default rendered widget factory for the path.
  361. *
  362. * ### Notes
  363. * If the widget factory has registered a separate set of `defaultRendered`
  364. * file types and there is a match in that set, this returns that.
  365. * Otherwise, this returns the same widget factory as
  366. * [[defaultWidgetFactory]].
  367. */
  368. defaultRenderedWidgetFactory(path: string): DocumentRegistry.WidgetFactory {
  369. // Get the matching file types.
  370. let fts = this.getFileTypesForPath(PathExt.basename(path));
  371. let factory: DocumentRegistry.WidgetFactory | undefined = undefined;
  372. // Find if a there is a default rendered factory for this type.
  373. for (let ft of fts) {
  374. if (ft.name in this._defaultRenderedWidgetFactories) {
  375. factory = this._widgetFactories[
  376. this._defaultRenderedWidgetFactories[ft.name]
  377. ];
  378. break;
  379. }
  380. }
  381. return factory || this.defaultWidgetFactory(path);
  382. }
  383. /**
  384. * Get the default widget factory for a path.
  385. *
  386. * @param path - An optional file path to filter the results.
  387. *
  388. * @returns The default widget factory for an path.
  389. *
  390. * #### Notes
  391. * This is equivalent to the first value in [[preferredWidgetFactories]].
  392. */
  393. defaultWidgetFactory(path?: string): DocumentRegistry.WidgetFactory {
  394. if (!path) {
  395. return this._widgetFactories[this._defaultWidgetFactory];
  396. }
  397. return this.preferredWidgetFactories(path)[0];
  398. }
  399. /**
  400. * Set overrides for the default widget factory for a file type.
  401. *
  402. * Normally, a widget factory informs the document registry which file types
  403. * it should be the default for using the `defaultFor` option in the
  404. * IWidgetFactoryOptions. This function can be used to override that after
  405. * the fact.
  406. *
  407. * @param fileType: The name of the file type.
  408. *
  409. * @param factory: The name of the factory.
  410. *
  411. * #### Notes
  412. * If `factory` is undefined, then any override will be unset, and the
  413. * default factory will revert to the original value.
  414. *
  415. * If `factory` or `fileType` are not known to the docregistry, or
  416. * if `factory` cannot open files of type `fileType`, this will throw
  417. * an error.
  418. */
  419. setDefaultWidgetFactory(fileType: string, factory: string | undefined): void {
  420. fileType = fileType.toLowerCase();
  421. if (!this.getFileType(fileType)) {
  422. throw Error(`Cannot find file type ${fileType}`);
  423. }
  424. if (!factory) {
  425. if (this._defaultWidgetFactoryOverrides[fileType]) {
  426. delete this._defaultWidgetFactoryOverrides[fileType];
  427. }
  428. return;
  429. }
  430. if (!this.getWidgetFactory(factory)) {
  431. throw Error(`Cannot find widget factory ${factory}`);
  432. }
  433. factory = factory.toLowerCase();
  434. const factories = this._widgetFactoriesForFileType[fileType];
  435. if (
  436. factory !== this._defaultWidgetFactory &&
  437. !(factories && factories.includes(factory))
  438. ) {
  439. throw Error(`Factory ${factory} cannot view file type ${fileType}`);
  440. }
  441. this._defaultWidgetFactoryOverrides[fileType] = factory;
  442. }
  443. /**
  444. * Create an iterator over the widget factories that have been registered.
  445. *
  446. * @returns A new iterator of widget factories.
  447. */
  448. widgetFactories(): IIterator<DocumentRegistry.WidgetFactory> {
  449. return map(Object.keys(this._widgetFactories), name => {
  450. return this._widgetFactories[name];
  451. });
  452. }
  453. /**
  454. * Create an iterator over the model factories that have been registered.
  455. *
  456. * @returns A new iterator of model factories.
  457. */
  458. modelFactories(): IIterator<DocumentRegistry.ModelFactory> {
  459. return map(Object.keys(this._modelFactories), name => {
  460. return this._modelFactories[name];
  461. });
  462. }
  463. /**
  464. * Create an iterator over the registered extensions for a given widget.
  465. *
  466. * @param widgetName - The name of the widget factory.
  467. *
  468. * @returns A new iterator over the widget extensions.
  469. */
  470. widgetExtensions(
  471. widgetName: string
  472. ): IIterator<DocumentRegistry.WidgetExtension> {
  473. widgetName = widgetName.toLowerCase();
  474. if (!(widgetName in this._extenders)) {
  475. return empty<DocumentRegistry.WidgetExtension>();
  476. }
  477. return new ArrayIterator(this._extenders[widgetName]);
  478. }
  479. /**
  480. * Create an iterator over the file types that have been registered.
  481. *
  482. * @returns A new iterator of file types.
  483. */
  484. fileTypes(): IIterator<DocumentRegistry.IFileType> {
  485. return new ArrayIterator(this._fileTypes);
  486. }
  487. /**
  488. * Get a widget factory by name.
  489. *
  490. * @param widgetName - The name of the widget factory.
  491. *
  492. * @returns A widget factory instance.
  493. */
  494. getWidgetFactory(
  495. widgetName: string
  496. ): DocumentRegistry.WidgetFactory | undefined {
  497. return this._widgetFactories[widgetName.toLowerCase()];
  498. }
  499. /**
  500. * Get a model factory by name.
  501. *
  502. * @param name - The name of the model factory.
  503. *
  504. * @returns A model factory instance.
  505. */
  506. getModelFactory(name: string): DocumentRegistry.ModelFactory | undefined {
  507. return this._modelFactories[name.toLowerCase()];
  508. }
  509. /**
  510. * Get a file type by name.
  511. */
  512. getFileType(name: string): DocumentRegistry.FileType | undefined {
  513. name = name.toLowerCase();
  514. return find(this._fileTypes, fileType => {
  515. return fileType.name.toLowerCase() === name;
  516. });
  517. }
  518. /**
  519. * Get a kernel preference.
  520. *
  521. * @param path - The file path.
  522. *
  523. * @param widgetName - The name of the widget factory.
  524. *
  525. * @param kernel - An optional existing kernel model.
  526. *
  527. * @returns A kernel preference.
  528. */
  529. getKernelPreference(
  530. path: string,
  531. widgetName: string,
  532. kernel?: Partial<Kernel.IModel>
  533. ): ISessionContext.IKernelPreference | undefined {
  534. widgetName = widgetName.toLowerCase();
  535. let widgetFactory = this._widgetFactories[widgetName];
  536. if (!widgetFactory) {
  537. return void 0;
  538. }
  539. let modelFactory = this.getModelFactory(widgetFactory.modelName || 'text');
  540. if (!modelFactory) {
  541. return void 0;
  542. }
  543. let language = modelFactory.preferredLanguage(PathExt.basename(path));
  544. let name = kernel && kernel.name;
  545. let id = kernel && kernel.id;
  546. return {
  547. id,
  548. name,
  549. language,
  550. shouldStart: widgetFactory.preferKernel,
  551. canStart: widgetFactory.canStartKernel,
  552. shutdownOnDispose: widgetFactory.shutdownOnClose
  553. };
  554. }
  555. /**
  556. * Get the best file type given a contents model.
  557. *
  558. * @param model - The contents model of interest.
  559. *
  560. * @returns The best matching file type.
  561. */
  562. getFileTypeForModel(
  563. model: Partial<Contents.IModel>
  564. ): DocumentRegistry.FileType {
  565. switch (model.type) {
  566. case 'directory':
  567. return (
  568. find(this._fileTypes, ft => ft.contentType === 'directory') ||
  569. DocumentRegistry.defaultDirectoryFileType
  570. );
  571. case 'notebook':
  572. return (
  573. find(this._fileTypes, ft => ft.contentType === 'notebook') ||
  574. DocumentRegistry.defaultNotebookFileType
  575. );
  576. default:
  577. // Find the best matching extension.
  578. if (model.name || model.path) {
  579. let name = model.name || PathExt.basename(model.path!);
  580. let fts = this.getFileTypesForPath(name);
  581. if (fts.length > 0) {
  582. return fts[0];
  583. }
  584. }
  585. return this.getFileType('text') || DocumentRegistry.defaultTextFileType;
  586. }
  587. }
  588. /**
  589. * Get the file types that match a file name.
  590. *
  591. * @param path - The path of the file.
  592. *
  593. * @returns An ordered list of matching file types.
  594. */
  595. getFileTypesForPath(path: string): DocumentRegistry.FileType[] {
  596. let fts: DocumentRegistry.FileType[] = [];
  597. let name = PathExt.basename(path);
  598. // Look for a pattern match first.
  599. let ft = find(this._fileTypes, ft => {
  600. return !!(ft.pattern && ft.pattern.match(name) !== null);
  601. });
  602. if (ft) {
  603. fts.push(ft);
  604. }
  605. // Then look by extension name, starting with the longest
  606. let ext = Private.extname(name);
  607. while (ext.length > 1) {
  608. ft = find(this._fileTypes, ft => ft.extensions.indexOf(ext) !== -1);
  609. if (ft) {
  610. fts.push(ft);
  611. }
  612. ext =
  613. '.' +
  614. ext
  615. .split('.')
  616. .slice(2)
  617. .join('.');
  618. }
  619. return fts;
  620. }
  621. private _modelFactories: {
  622. [key: string]: DocumentRegistry.ModelFactory;
  623. } = Object.create(null);
  624. private _widgetFactories: {
  625. [key: string]: DocumentRegistry.WidgetFactory;
  626. } = Object.create(null);
  627. private _defaultWidgetFactory = '';
  628. private _defaultWidgetFactoryOverrides: {
  629. [key: string]: string;
  630. } = Object.create(null);
  631. private _defaultWidgetFactories: { [key: string]: string } = Object.create(
  632. null
  633. );
  634. private _defaultRenderedWidgetFactories: {
  635. [key: string]: string;
  636. } = Object.create(null);
  637. private _widgetFactoriesForFileType: {
  638. [key: string]: string[];
  639. } = Object.create(null);
  640. private _fileTypes: DocumentRegistry.FileType[] = [];
  641. private _extenders: {
  642. [key: string]: DocumentRegistry.WidgetExtension[];
  643. } = Object.create(null);
  644. private _changed = new Signal<this, DocumentRegistry.IChangedArgs>(this);
  645. private _isDisposed = false;
  646. }
  647. /**
  648. * The namespace for the `DocumentRegistry` class statics.
  649. */
  650. export namespace DocumentRegistry {
  651. /**
  652. * The item to be added to document toolbar.
  653. */
  654. export interface IToolbarItem {
  655. name: string;
  656. widget: Widget;
  657. }
  658. /**
  659. * The options used to create a document registry.
  660. */
  661. export interface IOptions {
  662. /**
  663. * The text model factory for the registry. A default instance will
  664. * be used if not given.
  665. */
  666. textModelFactory?: ModelFactory;
  667. /**
  668. * The initial file types for the registry.
  669. * The [[DocumentRegistry.defaultFileTypes]] will be used if not given.
  670. */
  671. initialFileTypes?: DocumentRegistry.IFileType[];
  672. }
  673. /**
  674. * The interface for a document model.
  675. */
  676. export interface IModel extends IDisposable {
  677. /**
  678. * A signal emitted when the document content changes.
  679. */
  680. contentChanged: ISignal<this, void>;
  681. /**
  682. * A signal emitted when the model state changes.
  683. */
  684. stateChanged: ISignal<this, IChangedArgsGeneric<any>>;
  685. /**
  686. * The dirty state of the model.
  687. *
  688. * #### Notes
  689. * This should be cleared when the document is loaded from
  690. * or saved to disk.
  691. */
  692. dirty: boolean;
  693. /**
  694. * The read-only state of the model.
  695. */
  696. readOnly: boolean;
  697. /**
  698. * The default kernel name of the document.
  699. */
  700. readonly defaultKernelName: string;
  701. /**
  702. * The default kernel language of the document.
  703. */
  704. readonly defaultKernelLanguage: string;
  705. /**
  706. * The underlying `IModelDB` instance in which model
  707. * data is stored.
  708. *
  709. * ### Notes
  710. * Making direct edits to the values stored in the`IModelDB`
  711. * is not recommended, and may produce unpredictable results.
  712. */
  713. readonly modelDB: IModelDB;
  714. /**
  715. * Serialize the model to a string.
  716. */
  717. toString(): string;
  718. /**
  719. * Deserialize the model from a string.
  720. *
  721. * #### Notes
  722. * Should emit a [contentChanged] signal.
  723. */
  724. fromString(value: string): void;
  725. /**
  726. * Serialize the model to JSON.
  727. */
  728. toJSON(): PartialJSONValue;
  729. /**
  730. * Deserialize the model from JSON.
  731. *
  732. * #### Notes
  733. * Should emit a [contentChanged] signal.
  734. */
  735. fromJSON(value: ReadonlyPartialJSONValue): void;
  736. /**
  737. * Initialize model state after initial data load.
  738. *
  739. * #### Notes
  740. * This function must be called after the initial data is loaded to set up
  741. * initial model state, such as an initial undo stack, etc.
  742. */
  743. initialize(): void;
  744. }
  745. /**
  746. * The interface for a document model that represents code.
  747. */
  748. export interface ICodeModel extends IModel, CodeEditor.IModel {}
  749. /**
  750. * The document context object.
  751. */
  752. export interface IContext<T extends IModel> extends IDisposable {
  753. /**
  754. * A signal emitted when the path changes.
  755. */
  756. pathChanged: ISignal<this, string>;
  757. /**
  758. * A signal emitted when the contentsModel changes.
  759. */
  760. fileChanged: ISignal<this, Contents.IModel>;
  761. /**
  762. * A signal emitted on the start and end of a saving operation.
  763. */
  764. saveState: ISignal<this, SaveState>;
  765. /**
  766. * A signal emitted when the context is disposed.
  767. */
  768. disposed: ISignal<this, void>;
  769. /**
  770. * The data model for the document.
  771. */
  772. readonly model: T;
  773. /**
  774. * The session context object associated with the context.
  775. */
  776. readonly sessionContext: ISessionContext;
  777. /**
  778. * The current path associated with the document.
  779. */
  780. readonly path: string;
  781. /**
  782. * The current local path associated with the document.
  783. * If the document is in the default notebook file browser,
  784. * this is the same as the path.
  785. */
  786. readonly localPath: string;
  787. /**
  788. * The document metadata, stored as a services contents model.
  789. *
  790. * #### Notes
  791. * This will be null until the context is 'ready'. Since we only store
  792. * metadata here, the `.contents` attribute will always be empty.
  793. */
  794. readonly contentsModel: Contents.IModel | null;
  795. /**
  796. * The url resolver for the context.
  797. */
  798. readonly urlResolver: IRenderMime.IResolver;
  799. /**
  800. * Whether the context is ready.
  801. */
  802. readonly isReady: boolean;
  803. /**
  804. * A promise that is fulfilled when the context is ready.
  805. */
  806. readonly ready: Promise<void>;
  807. /**
  808. * Save the document contents to disk.
  809. */
  810. save(): Promise<void>;
  811. /**
  812. * Save the document to a different path chosen by the user.
  813. */
  814. saveAs(): Promise<void>;
  815. /**
  816. * Save the document to a different path chosen by the user.
  817. */
  818. download(): Promise<void>;
  819. /**
  820. * Revert the document contents to disk contents.
  821. */
  822. revert(): Promise<void>;
  823. /**
  824. * Create a checkpoint for the file.
  825. *
  826. * @returns A promise which resolves with the new checkpoint model when the
  827. * checkpoint is created.
  828. */
  829. createCheckpoint(): Promise<Contents.ICheckpointModel>;
  830. /**
  831. * Delete a checkpoint for the file.
  832. *
  833. * @param checkpointID - The id of the checkpoint to delete.
  834. *
  835. * @returns A promise which resolves when the checkpoint is deleted.
  836. */
  837. deleteCheckpoint(checkpointID: string): Promise<void>;
  838. /**
  839. * Restore the file to a known checkpoint state.
  840. *
  841. * @param checkpointID - The optional id of the checkpoint to restore,
  842. * defaults to the most recent checkpoint.
  843. *
  844. * @returns A promise which resolves when the checkpoint is restored.
  845. */
  846. restoreCheckpoint(checkpointID?: string): Promise<void>;
  847. /**
  848. * List available checkpoints for the file.
  849. *
  850. * @returns A promise which resolves with a list of checkpoint models for
  851. * the file.
  852. */
  853. listCheckpoints(): Promise<Contents.ICheckpointModel[]>;
  854. /**
  855. * Add a sibling widget to the document manager.
  856. *
  857. * @param widget - The widget to add to the document manager.
  858. *
  859. * @param options - The desired options for adding the sibling.
  860. *
  861. * @returns A disposable used to remove the sibling if desired.
  862. *
  863. * #### Notes
  864. * It is assumed that the widget has the same model and context
  865. * as the original widget.
  866. */
  867. addSibling(widget: Widget, options?: IOpenOptions): IDisposable;
  868. }
  869. export type SaveState = 'started' | 'completed' | 'failed';
  870. /**
  871. * A type alias for a context.
  872. */
  873. export type Context = IContext<IModel>;
  874. /**
  875. * A type alias for a code context.
  876. */
  877. export type CodeContext = IContext<ICodeModel>;
  878. /**
  879. * The options used to initialize a widget factory.
  880. */
  881. export interface IWidgetFactoryOptions<T extends Widget = Widget> {
  882. /**
  883. * The name of the widget to display in dialogs.
  884. */
  885. readonly name: string;
  886. /**
  887. * The file types the widget can view.
  888. */
  889. readonly fileTypes: ReadonlyArray<string>;
  890. /**
  891. * The file types for which the factory should be the default.
  892. */
  893. readonly defaultFor?: ReadonlyArray<string>;
  894. /**
  895. * The file types for which the factory should be the default for rendering,
  896. * if that is different than the default factory (which may be for editing).
  897. * If undefined, then it will fall back on the default file type.
  898. */
  899. readonly defaultRendered?: ReadonlyArray<string>;
  900. /**
  901. * Whether the widget factory is read only.
  902. */
  903. readonly readOnly?: boolean;
  904. /**
  905. * The registered name of the model type used to create the widgets.
  906. */
  907. readonly modelName?: string;
  908. /**
  909. * Whether the widgets prefer having a kernel started.
  910. */
  911. readonly preferKernel?: boolean;
  912. /**
  913. * Whether the widgets can start a kernel when opened.
  914. */
  915. readonly canStartKernel?: boolean;
  916. /**
  917. * Whether the kernel should be shutdown when the widget is closed.
  918. */
  919. readonly shutdownOnClose?: boolean;
  920. /**
  921. * A function producing toolbar widgets, overriding the default toolbar widgets.
  922. */
  923. readonly toolbarFactory?: (widget: T) => DocumentRegistry.IToolbarItem[];
  924. }
  925. /**
  926. * The options used to open a widget.
  927. */
  928. export interface IOpenOptions {
  929. /**
  930. * The reference widget id for the insert location.
  931. *
  932. * The default is `null`.
  933. */
  934. ref?: string | null;
  935. /**
  936. * The supported insertion modes.
  937. *
  938. * An insert mode is used to specify how a widget should be added
  939. * to the main area relative to a reference widget.
  940. */
  941. mode?: DockLayout.InsertMode;
  942. /**
  943. * Whether to activate the widget. Defaults to `true`.
  944. */
  945. activate?: boolean;
  946. /**
  947. * The rank order of the widget among its siblings.
  948. *
  949. * #### Notes
  950. * This field may be used or ignored depending on shell implementation.
  951. */
  952. rank?: number;
  953. }
  954. /**
  955. * The interface for a widget factory.
  956. */
  957. export interface IWidgetFactory<T extends IDocumentWidget, U extends IModel>
  958. extends IDisposable,
  959. IWidgetFactoryOptions {
  960. /**
  961. * A signal emitted when a new widget is created.
  962. */
  963. widgetCreated: ISignal<IWidgetFactory<T, U>, T>;
  964. /**
  965. * Create a new widget given a context.
  966. *
  967. * @param source - A widget to clone
  968. *
  969. * #### Notes
  970. * It should emit the [widgetCreated] signal with the new widget.
  971. */
  972. createNew(context: IContext<U>, source?: T): T;
  973. }
  974. /**
  975. * A type alias for a standard widget factory.
  976. */
  977. export type WidgetFactory = IWidgetFactory<IDocumentWidget, IModel>;
  978. /**
  979. * An interface for a widget extension.
  980. */
  981. export interface IWidgetExtension<T extends Widget, U extends IModel> {
  982. /**
  983. * Create a new extension for a given widget.
  984. */
  985. createNew(widget: T, context: IContext<U>): IDisposable;
  986. }
  987. /**
  988. * A type alias for a standard widget extension.
  989. */
  990. export type WidgetExtension = IWidgetExtension<Widget, IModel>;
  991. /**
  992. * The interface for a model factory.
  993. */
  994. export interface IModelFactory<T extends IModel> extends IDisposable {
  995. /**
  996. * The name of the model.
  997. */
  998. readonly name: string;
  999. /**
  1000. * The content type of the file (defaults to `"file"`).
  1001. */
  1002. readonly contentType: Contents.ContentType;
  1003. /**
  1004. * The format of the file (defaults to `"text"`).
  1005. */
  1006. readonly fileFormat: Contents.FileFormat;
  1007. /**
  1008. * Create a new model for a given path.
  1009. *
  1010. * @param languagePreference - An optional kernel language preference.
  1011. *
  1012. * @returns A new document model.
  1013. */
  1014. createNew(languagePreference?: string, modelDB?: IModelDB): T;
  1015. /**
  1016. * Get the preferred kernel language given a file path.
  1017. */
  1018. preferredLanguage(path: string): string;
  1019. }
  1020. /**
  1021. * A type alias for a standard model factory.
  1022. */
  1023. export type ModelFactory = IModelFactory<IModel>;
  1024. /**
  1025. * A type alias for a code model factory.
  1026. */
  1027. export type CodeModelFactory = IModelFactory<ICodeModel>;
  1028. /**
  1029. * An interface for a file type.
  1030. */
  1031. export interface IFileType {
  1032. /**
  1033. * The name of the file type.
  1034. */
  1035. readonly name: string;
  1036. /**
  1037. * The mime types associated the file type.
  1038. */
  1039. readonly mimeTypes: ReadonlyArray<string>;
  1040. /**
  1041. * The extensions of the file type (e.g. `".txt"`). Can be a compound
  1042. * extension (e.g. `".table.json`).
  1043. */
  1044. readonly extensions: ReadonlyArray<string>;
  1045. /**
  1046. * An optional display name for the file type.
  1047. */
  1048. readonly displayName?: string;
  1049. /**
  1050. * An optional pattern for a file name (e.g. `^Dockerfile$`).
  1051. */
  1052. readonly pattern?: string;
  1053. /**
  1054. * The icon for the file type. Can either be a string containing the name
  1055. * of an existing icon, or an object with {name, svgstr} fields, where
  1056. * svgstr is a string containing the raw contents of an svg file.
  1057. */
  1058. readonly icon?: string | { name: string; svgstr: string } | null;
  1059. /**
  1060. * The icon class name for the file type.
  1061. */
  1062. readonly iconClass?: string;
  1063. /**
  1064. * The icon label for the file type.
  1065. */
  1066. readonly iconLabel?: string;
  1067. /**
  1068. * The content type of the new file.
  1069. */
  1070. readonly contentType: Contents.ContentType;
  1071. /**
  1072. * The format of the new file.
  1073. */
  1074. readonly fileFormat: Contents.FileFormat;
  1075. }
  1076. /**
  1077. * The defaults used for a file type.
  1078. */
  1079. export const fileTypeDefaults: IFileType = {
  1080. name: 'default',
  1081. extensions: [],
  1082. mimeTypes: [],
  1083. contentType: 'file',
  1084. fileFormat: 'text'
  1085. };
  1086. /**
  1087. * A wrapper for IFileTypes
  1088. */
  1089. export class FileType implements IFileType {
  1090. static resolve(ft: Partial<IFileType>): FileType {
  1091. if (ft instanceof FileType) {
  1092. return ft;
  1093. }
  1094. return new FileType(ft);
  1095. }
  1096. constructor(options: Partial<IFileType>) {
  1097. Object.assign(this, fileTypeDefaults, options);
  1098. if (this.icon) {
  1099. // ensure that the icon is resolved to a JLIcon
  1100. this.icon = JLIcon.resolve(this.icon);
  1101. }
  1102. }
  1103. readonly name: string;
  1104. readonly mimeTypes: ReadonlyArray<string>;
  1105. readonly extensions: ReadonlyArray<string>;
  1106. readonly displayName: string;
  1107. readonly pattern: string;
  1108. readonly icon: JLIcon | null;
  1109. readonly iconClass: string;
  1110. readonly iconLabel: string;
  1111. readonly contentType: Contents.ContentType;
  1112. readonly fileFormat: Contents.FileFormat;
  1113. }
  1114. /**
  1115. * An arguments object for the `changed` signal.
  1116. */
  1117. export interface IChangedArgs {
  1118. /**
  1119. * The type of the changed item.
  1120. */
  1121. readonly type:
  1122. | 'widgetFactory'
  1123. | 'modelFactory'
  1124. | 'widgetExtension'
  1125. | 'fileType';
  1126. /**
  1127. * The name of the item or the widget factory being extended.
  1128. */
  1129. readonly name?: string;
  1130. /**
  1131. * Whether the item was added or removed.
  1132. */
  1133. readonly change: 'added' | 'removed';
  1134. }
  1135. /**
  1136. * The default text file type used by the document registry.
  1137. */
  1138. export const defaultTextFileType = new FileType({
  1139. ...fileTypeDefaults,
  1140. name: 'text',
  1141. mimeTypes: ['text/plain'],
  1142. extensions: ['.txt'],
  1143. icon: fileIcon
  1144. });
  1145. /**
  1146. * The default notebook file type used by the document registry.
  1147. */
  1148. export const defaultNotebookFileType = new FileType({
  1149. ...fileTypeDefaults,
  1150. name: 'notebook',
  1151. displayName: 'Notebook',
  1152. mimeTypes: ['application/x-ipynb+json'],
  1153. extensions: ['.ipynb'],
  1154. contentType: 'notebook',
  1155. fileFormat: 'json',
  1156. icon: notebookIcon
  1157. });
  1158. /**
  1159. * The default directory file type used by the document registry.
  1160. */
  1161. export const defaultDirectoryFileType = new FileType({
  1162. ...fileTypeDefaults,
  1163. name: 'directory',
  1164. extensions: [],
  1165. mimeTypes: ['text/directory'],
  1166. contentType: 'directory',
  1167. icon: folderIcon
  1168. });
  1169. /**
  1170. * The default file types used by the document registry.
  1171. */
  1172. export const defaultFileTypes: ReadonlyArray<FileType> = [
  1173. defaultTextFileType,
  1174. defaultNotebookFileType,
  1175. defaultDirectoryFileType,
  1176. ...[
  1177. {
  1178. name: 'markdown',
  1179. displayName: 'Markdown File',
  1180. extensions: ['.md'],
  1181. mimeTypes: ['text/markdown'],
  1182. icon: markdownIcon
  1183. },
  1184. {
  1185. name: 'python',
  1186. displayName: 'Python File',
  1187. extensions: ['.py'],
  1188. mimeTypes: ['text/x-python'],
  1189. icon: pythonIcon
  1190. },
  1191. {
  1192. name: 'json',
  1193. displayName: 'JSON File',
  1194. extensions: ['.json'],
  1195. mimeTypes: ['application/json'],
  1196. icon: jsonIcon
  1197. },
  1198. {
  1199. name: 'csv',
  1200. displayName: 'CSV File',
  1201. extensions: ['.csv'],
  1202. mimeTypes: ['text/csv'],
  1203. icon: spreadsheetIcon
  1204. },
  1205. {
  1206. name: 'tsv',
  1207. displayName: 'TSV File',
  1208. extensions: ['.tsv'],
  1209. mimeTypes: ['text/csv'],
  1210. icon: spreadsheetIcon
  1211. },
  1212. {
  1213. name: 'r',
  1214. displayName: 'R File',
  1215. mimeTypes: ['text/x-rsrc'],
  1216. extensions: ['.r'],
  1217. icon: rKernelIcon
  1218. },
  1219. {
  1220. name: 'yaml',
  1221. displayName: 'YAML File',
  1222. mimeTypes: ['text/x-yaml', 'text/yaml'],
  1223. extensions: ['.yaml', '.yml'],
  1224. icon: yamlIcon
  1225. },
  1226. {
  1227. name: 'svg',
  1228. displayName: 'Image',
  1229. mimeTypes: ['image/svg+xml'],
  1230. extensions: ['.svg'],
  1231. icon: imageIcon,
  1232. fileFormat: 'base64'
  1233. },
  1234. {
  1235. name: 'tiff',
  1236. displayName: 'Image',
  1237. mimeTypes: ['image/tiff'],
  1238. extensions: ['.tif', '.tiff'],
  1239. icon: imageIcon,
  1240. fileFormat: 'base64'
  1241. },
  1242. {
  1243. name: 'jpeg',
  1244. displayName: 'Image',
  1245. mimeTypes: ['image/jpeg'],
  1246. extensions: ['.jpg', '.jpeg'],
  1247. icon: imageIcon,
  1248. fileFormat: 'base64'
  1249. },
  1250. {
  1251. name: 'gif',
  1252. displayName: 'Image',
  1253. mimeTypes: ['image/gif'],
  1254. extensions: ['.gif'],
  1255. icon: imageIcon,
  1256. fileFormat: 'base64'
  1257. },
  1258. {
  1259. name: 'png',
  1260. displayName: 'Image',
  1261. mimeTypes: ['image/png'],
  1262. extensions: ['.png'],
  1263. icon: imageIcon,
  1264. fileFormat: 'base64'
  1265. },
  1266. {
  1267. name: 'bmp',
  1268. displayName: 'Image',
  1269. mimeTypes: ['image/bmp'],
  1270. extensions: ['.bmp'],
  1271. icon: imageIcon,
  1272. fileFormat: 'base64'
  1273. }
  1274. ].map(ft => new FileType(ft as Partial<IFileType>))
  1275. ];
  1276. }
  1277. /**
  1278. * An interface for a document widget.
  1279. */
  1280. export interface IDocumentWidget<
  1281. T extends Widget = Widget,
  1282. U extends DocumentRegistry.IModel = DocumentRegistry.IModel
  1283. > extends Widget {
  1284. /**
  1285. * The content widget.
  1286. */
  1287. readonly content: T;
  1288. /**
  1289. * A promise resolving after the content widget is revealed.
  1290. */
  1291. readonly revealed: Promise<void>;
  1292. /**
  1293. * The context associated with the document.
  1294. */
  1295. readonly context: DocumentRegistry.IContext<U>;
  1296. /**
  1297. * The toolbar for the widget.
  1298. */
  1299. readonly toolbar: Toolbar<Widget>;
  1300. /**
  1301. * Set URI fragment identifier.
  1302. */
  1303. setFragment(fragment: string): void;
  1304. }
  1305. /**
  1306. * A private namespace for DocumentRegistry data.
  1307. */
  1308. namespace Private {
  1309. /**
  1310. * Get the extension name of a path.
  1311. *
  1312. * @param file - string.
  1313. *
  1314. * #### Notes
  1315. * Dotted filenames (e.g. `".table.json"` are allowed).
  1316. */
  1317. export function extname(path: string): string {
  1318. let parts = PathExt.basename(path).split('.');
  1319. parts.shift();
  1320. let ext = '.' + parts.join('.');
  1321. return ext.toLowerCase();
  1322. }
  1323. /**
  1324. * A no-op function.
  1325. */
  1326. export function noOp() {
  1327. /* no-op */
  1328. }
  1329. }