UNPKG

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