registry.ts 35 KB

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