123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354 |
- // Copyright (c) Jupyter Development Team.
- // Distributed under the terms of the Modified BSD License.
- import {
- JupyterLab, JupyterLabPlugin
- } from '@jupyterlab/application';
- import {
- showDialog, showErrorMessage, Spinner, Dialog, ICommandPalette, IMainMenu
- } from '@jupyterlab/apputils';
- import {
- IChangedArgs
- } from '@jupyterlab/coreutils';
- import {
- renameDialog, DocumentManager, IDocumentManager
- } from '@jupyterlab/docmanager';
- import {
- DocumentRegistry
- } from '@jupyterlab/docregistry';
- import {
- Contents, Kernel
- } from '@jupyterlab/services';
- import {
- IDisposable
- } from '@phosphor/disposable';
- /**
- * The command IDs used by the document manager plugin.
- */
- namespace CommandIDs {
- export
- const clone = 'docmanager:clone';
- export
- const close = 'docmanager:close';
- export
- const closeAllFiles = 'docmanager:close-all-files';
- export
- const createFrom = 'docmanager:create-from';
- export
- const deleteFile = 'docmanager:delete-file';
- export
- const newUntitled = 'docmanager:new-untitled';
- export
- const open = 'docmanager:open';
- export
- const rename = 'docmanager:rename';
- export
- const restoreCheckpoint = 'docmanager:restore-checkpoint';
- export
- const save = 'docmanager:save';
- export
- const saveAs = 'docmanager:save-as';
- }
- /**
- * The default document manager provider.
- */
- const plugin: JupyterLabPlugin<IDocumentManager> = {
- id: '@jupyterlab/docmanager-extension:plugin',
- provides: IDocumentManager,
- requires: [ICommandPalette, IMainMenu],
- activate: (app: JupyterLab, palette: ICommandPalette, mainMenu: IMainMenu): IDocumentManager => {
- const manager = app.serviceManager;
- const contexts = new WeakSet<DocumentRegistry.Context>();
- const opener: DocumentManager.IWidgetOpener = {
- open: widget => {
- if (!widget.id) {
- widget.id = `document-manager-${++Private.id}`;
- }
- widget.title.dataset = {
- 'type': 'document-title',
- ...widget.title.dataset
- };
- if (!widget.isAttached) {
- app.shell.addToMainArea(widget);
- // Add a loading spinner, and remove it when the widget is ready.
- let spinner = new Spinner();
- widget.node.appendChild(spinner.node);
- widget.ready.then(() => { widget.node.removeChild(spinner.node); });
- }
- app.shell.activateById(widget.id);
- // Handle dirty state for open documents.
- let context = docManager.contextForWidget(widget);
- if (!contexts.has(context)) {
- handleContext(app, context);
- contexts.add(context);
- }
- }
- };
- const registry = app.docRegistry;
- const docManager = new DocumentManager({ registry, manager, opener });
- // Register the file operations commands.
- addCommands(app, docManager, palette, opener);
- return docManager;
- }
- };
- /**
- * Export the plugin as default.
- */
- export default plugin;
- /**
- * Add the file operations commands to the application's command registry.
- */
- function addCommands(app: JupyterLab, docManager: IDocumentManager, palette: ICommandPalette, opener: DocumentManager.IWidgetOpener): void {
- const { commands } = app;
- const category = 'File Operations';
- const isEnabled = () => {
- const { currentWidget } = app.shell;
- return !!(currentWidget && docManager.contextForWidget(currentWidget));
- };
- commands.addCommand(CommandIDs.close, {
- label: 'Close',
- execute: () => {
- if (app.shell.currentWidget) {
- app.shell.currentWidget.close();
- }
- }
- });
- commands.addCommand(CommandIDs.closeAllFiles, {
- label: 'Close All',
- execute: () => { app.shell.closeAll(); }
- });
- commands.addCommand(CommandIDs.deleteFile, {
- execute: args => {
- const path = typeof args['path'] === 'undefined' ? ''
- : args['path'] as string;
- if (!path) {
- const command = CommandIDs.deleteFile;
- throw new Error(`A non-empty path is required for ${command}.`);
- }
- return docManager.deleteFile(path);
- }
- });
- commands.addCommand(CommandIDs.newUntitled, {
- execute: args => {
- const errorTitle = args['error'] as string || 'Error';
- const path = typeof args['path'] === 'undefined' ? ''
- : args['path'] as string;
- let options: Partial<Contents.ICreateOptions> = {
- type: args['type'] as Contents.ContentType,
- path
- };
- if (args['type'] === 'file') {
- options.ext = args['ext'] as string || '.txt';
- }
- return docManager.services.contents.newUntitled(options)
- .catch(error => showErrorMessage(errorTitle, error));
- },
- label: args => args['label'] as string || `New ${args['type'] as string}`
- });
- commands.addCommand(CommandIDs.open, {
- execute: args => {
- const path = typeof args['path'] === 'undefined' ? ''
- : args['path'] as string;
- const factory = args['factory'] as string || void 0;
- const kernel = args['kernel'] as Kernel.IModel || void 0;
- return docManager.services.contents.get(path, { content: false })
- .then(() => docManager.openOrReveal(path, factory, kernel));
- },
- icon: args => args['icon'] as string || '',
- label: args => (args['label'] || args['factory']) as string,
- mnemonic: args => args['mnemonic'] as number || -1
- });
- commands.addCommand(CommandIDs.restoreCheckpoint, {
- label: 'Revert to Checkpoint',
- caption: 'Revert contents to previous checkpoint',
- isEnabled,
- execute: () => {
- if (isEnabled()) {
- let context = docManager.contextForWidget(app.shell.currentWidget);
- if (context.model.readOnly) {
- return context.revert();
- }
- return context.restoreCheckpoint().then(() => context.revert());
- }
- }
- });
- commands.addCommand(CommandIDs.save, {
- label: 'Save',
- caption: 'Save and create checkpoint',
- isEnabled,
- execute: () => {
- if (isEnabled()) {
- let context = docManager.contextForWidget(app.shell.currentWidget);
- if (context.model.readOnly) {
- return showDialog({
- title: 'Cannot Save',
- body: 'Document is read-only',
- buttons: [Dialog.okButton()]
- });
- }
- return context.save().then(() => context.createCheckpoint());
- }
- }
- });
- commands.addCommand(CommandIDs.saveAs, {
- label: 'Save As...',
- caption: 'Save with new path and create checkpoint',
- isEnabled,
- execute: () => {
- if (isEnabled()) {
- let context = docManager.contextForWidget(app.shell.currentWidget);
- return context.saveAs().then(() => context.createCheckpoint());
- }
- }
- });
- commands.addCommand(CommandIDs.rename, {
- isVisible: () => {
- const widget = app.shell.currentWidget;
- if (!widget) {
- return;
- }
- // Find the context for the widget.
- let context = docManager.contextForWidget(widget);
- return context !== null;
- },
- execute: () => {
- const widget = app.shell.currentWidget;
- if (!widget) {
- return;
- }
- // Find the context for the widget.
- let context = docManager.contextForWidget(widget);
- if (context) {
- return renameDialog(docManager, context.path);
- }
- },
- label: 'Rename'
- });
- commands.addCommand(CommandIDs.clone, {
- isVisible: () => {
- const widget = app.shell.currentWidget;
- if (!widget) {
- return;
- }
- // Find the context for the widget.
- let context = docManager.contextForWidget(widget);
- return context !== null;
- },
- execute: () => {
- const widget = app.shell.currentWidget;
- if (!widget) {
- return;
- }
- // Clone the widget.
- let child = docManager.cloneWidget(widget);
- if (child) {
- opener.open(child);
- }
- },
- label: 'New View into File'
- });
- app.contextMenu.addItem({
- command: CommandIDs.rename,
- selector: '[data-type="document-title"]',
- rank: 1
- });
- app.contextMenu.addItem({
- command: CommandIDs.clone,
- selector: '[data-type="document-title"]',
- rank: 2
- });
- [
- CommandIDs.save,
- CommandIDs.restoreCheckpoint,
- CommandIDs.saveAs,
- CommandIDs.clone,
- CommandIDs.close,
- CommandIDs.closeAllFiles
- ].forEach(command => { palette.addItem({ command, category }); });
- }
- /**
- * Handle dirty state for a context.
- */
- function handleContext(app: JupyterLab, context: DocumentRegistry.Context): void {
- let disposable: IDisposable | null = null;
- let onStateChanged = (sender: any, args: IChangedArgs<any>) => {
- if (args.name === 'dirty') {
- if (args.newValue === true) {
- if (!disposable) {
- disposable = app.setDirty();
- }
- } else if (disposable) {
- disposable.dispose();
- disposable = null;
- }
- }
- };
- context.ready.then(() => {
- context.model.stateChanged.connect(onStateChanged);
- if (context.model.dirty) {
- disposable = app.setDirty();
- }
- });
- context.disposed.connect(() => {
- if (disposable) {
- disposable.dispose();
- }
- });
- }
- /**
- * A namespace for private module data.
- */
- namespace Private {
- /**
- * A counter for unique IDs.
- */
- export
- let id = 0;
- }
|