浏览代码

Implement widget tracking on a per-plugin basis

Steven Silvester 8 年之前
父节点
当前提交
1f88a961df

+ 1 - 1
jupyterlab/index.js

@@ -28,9 +28,9 @@ var app = new phosphide.Application({
   providers: [
     require('jupyterlab/lib/clipboard/plugin').clipboardProvider,
     require('jupyterlab/lib/docregistry/plugin').docRegistryProvider,
+    require('jupyterlab/lib/notebook/plugin').notebookTrackerProvider,
     require('jupyterlab/lib/rendermime/plugin').renderMimeProvider,
     require('jupyterlab/lib/services/plugin').servicesProvider,
-    require('jupyterlab/lib/widgettracker/plugin').widgetTrackerProvider,
   ]
 });
 

+ 18 - 2
src/docregistry/default.ts

@@ -257,7 +257,14 @@ class Base64ModelFactory extends TextModelFactory {
  * The default implemetation of a widget factory.
  */
 export
-abstract class ABCWidgetFactory implements IWidgetFactory<Widget, IDocumentModel> {
+abstract class ABCWidgetFactory<T extends Widget, U extends IDocumentModel> implements IWidgetFactory<T, U> {
+  /**
+   * A signal emitted when a widget is created.
+   */
+  get widgetCreated(): ISignal<IWidgetFactory<T, U>, T> {
+    return Private.widgetCreatedSignal.bind(this);
+  }
+
   /**
    * Get whether the model factory has been disposed.
    */
@@ -274,8 +281,11 @@ abstract class ABCWidgetFactory implements IWidgetFactory<Widget, IDocumentModel
 
   /**
    * Create a new widget given a document model and a context.
+   *
+   * #### Notes
+   * It should emit the [widgetCreated] signal with the new widget.
    */
-  abstract createNew(context: IDocumentContext<IDocumentModel>, kernel?: IKernel.IModel): Widget;
+  abstract createNew(context: IDocumentContext<U>, kernel?: IKernel.IModel): T;
 
   private _isDisposed = false;
 }
@@ -291,6 +301,12 @@ namespace Private {
   export
   const contentChangedSignal = new Signal<IDocumentModel, void>();
 
+  /**
+   * A signal emitted when a widget is created.
+   */
+  export
+  const widgetCreatedSignal = new Signal<IWidgetFactory<Widget, IDocumentModel>, Widget>();
+
   /**
    * A signal emitted when a document dirty state changes.
    */

+ 8 - 0
src/docregistry/interfaces.ts

@@ -268,8 +268,16 @@ interface IWidgetFactoryOptions {
  */
 export
 interface IWidgetFactory<T extends Widget, U extends IDocumentModel> extends IDisposable {
+  /**
+   * A signal emitted when a widget is created.
+   */
+  widgetCreated: ISignal<IWidgetFactory<T, U>, T>;
+
   /**
    * Create a new widget.
+   *
+   * #### Notes
+   * It should emit the [widgetCreated] signal with the new widget.
    */
   createNew(context: IDocumentContext<U>, kernel?: IKernel.IModel): T;
 }

+ 4 - 2
src/editorwidget/widget.ts

@@ -85,7 +85,7 @@ class EditorWidget extends CodeMirrorWidget {
  * A widget factory for editors.
  */
 export
-class EditorWidgetFactory extends ABCWidgetFactory implements IWidgetFactory<EditorWidget, IDocumentModel> {
+class EditorWidgetFactory extends ABCWidgetFactory<EditorWidget, IDocumentModel> {
   /**
    * Create a new widget given a context.
    */
@@ -93,6 +93,8 @@ class EditorWidgetFactory extends ABCWidgetFactory implements IWidgetFactory<Edi
     if (kernel) {
       context.changeKernel(kernel);
     }
-    return new EditorWidget(context);
+    let widget = new EditorWidget(context);
+    this.widgetCreated.emit(widget);
+    return widget;
   }
 }

+ 14 - 20
src/filebrowser/plugin.ts

@@ -44,7 +44,7 @@ import {
 export
 const fileBrowserExtension = {
   id: 'jupyter.extensions.fileBrowser',
-  requires: [JupyterServices, DocumentRegistry, WidgetTracker],
+  requires: [JupyterServices, DocumentRegistry],
   activate: activateFileBrowser
 };
 
@@ -52,11 +52,17 @@ const fileBrowserExtension = {
 /**
  * Activate the file browser.
  */
-function activateFileBrowser(app: Application, provider: JupyterServices, registry: DocumentRegistry, tracker: WidgetTracker): Promise<void> {
+function activateFileBrowser(app: Application, provider: JupyterServices, registry: DocumentRegistry): Promise<void> {
   let contents = provider.contentsManager;
   let sessions = provider.sessionManager;
   let id = 0;
 
+  let tracker = new WidgetTracker<Widget>();
+  let activeWidget: Widget;
+  tracker.activeWidgetChanged.connect((sender, widget) => {
+    activeWidget = widget;
+  });
+
   let opener: IWidgetOpener = {
     open: (widget) => {
       if (!widget.id) {
@@ -97,16 +103,6 @@ function activateFileBrowser(app: Application, provider: JupyterServices, regist
     menu.popup(x, y);
   });
 
-  // Track the current active document.
-  let activeWidget: Widget;
-  tracker.activeWidgetChanged.connect((sender, widget) => {
-    if (widget && widget.hasClass('jp-Document')) {
-      activeWidget = widget;
-    } else {
-      activeWidget = null;
-    }
-  });
-
   // Add the command for a new items.
   let newTextFileId = 'file-operations:new-text-file';
 
@@ -133,11 +129,10 @@ function activateFileBrowser(app: Application, provider: JupyterServices, regist
     {
       id: saveDocumentId,
       handler: () => {
-        if (!activeWidget) {
-          return;
+        if (activeWidget) {
+          let context = docManager.contextForWidget(activeWidget);
+          context.save();
         }
-        let context = docManager.contextForWidget(activeWidget);
-        context.save();
       }
     }
   ]);
@@ -157,11 +152,10 @@ function activateFileBrowser(app: Application, provider: JupyterServices, regist
     {
       id: revertDocumentId,
       handler: () => {
-        if (!activeWidget) {
-          return;
+        if (activeWidget) {
+          let context = docManager.contextForWidget(activeWidget);
+          context.revert();
         }
-        let context = docManager.contextForWidget(activeWidget);
-        context.revert();
       }
     }
   ]);

+ 4 - 2
src/imagewidget/widget.ts

@@ -86,11 +86,13 @@ class ImageWidget extends Widget {
  * A widget factory for images.
  */
 export
-class ImageWidgetFactory extends ABCWidgetFactory implements IWidgetFactory<ImageWidget, IDocumentModel> {
+class ImageWidgetFactory extends ABCWidgetFactory<ImageWidget, IDocumentModel> {
   /**
    * Create a new widget given a context.
    */
   createNew(context: IDocumentContext<IDocumentModel>, kernel?: IKernel.IModel): ImageWidget {
-    return new ImageWidget(context);
+    let widget = new ImageWidget(context);
+    this.widgetCreated.emit(widget);
+    return widget;
   }
 }

+ 8 - 25
src/notebook/notebook/widgetfactory.ts

@@ -6,7 +6,7 @@ import {
 } from 'jupyter-js-services';
 
 import {
-  IWidgetFactory, IDocumentContext, findKernel
+  ABCWidgetFactory, IDocumentContext, findKernel
 } from '../../docregistry';
 
 import {
@@ -38,7 +38,7 @@ import {
  * A widget factory for notebook panels.
  */
 export
-class NotebookWidgetFactory implements IWidgetFactory<NotebookPanel, INotebookModel> {
+class NotebookWidgetFactory extends ABCWidgetFactory<NotebookPanel, INotebookModel> {
   /**
    * Construct a new notebook widget factory.
    *
@@ -47,26 +47,21 @@ class NotebookWidgetFactory implements IWidgetFactory<NotebookPanel, INotebookMo
    * @param clipboard - The application clipboard.
    */
   constructor(rendermime: RenderMime<Widget>, clipboard: IClipboard) {
+    super();
     this._rendermime = rendermime;
     this._clipboard = clipboard;
   }
 
-  /**
-   * Get whether the factory has been disposed.
-   *
-   * #### Notes
-   * This is a read-only property.
-   */
-  get isDisposed(): boolean {
-    return this._rendermime === null;
-  }
-
   /**
    * Dispose of the resources used by the factory.
    */
   dispose(): void {
+    if (this.isDisposed) {
+      return;
+    }
     this._rendermime = null;
     this._clipboard = null;
+    super.dispose();
   }
 
   /**
@@ -88,22 +83,10 @@ class NotebookWidgetFactory implements IWidgetFactory<NotebookPanel, INotebookMo
     let panel = new NotebookPanel({ rendermime, clipboard: this._clipboard });
     panel.context = context;
     ToolbarItems.populateDefaults(panel);
+    this.widgetCreated.emit(panel);
     return panel;
   }
 
-  /**
-   * Take an action on a widget before closing it.
-   *
-   * @returns A promise that resolves to true if the document should close
-   *   and false otherwise.
-   *
-   * ### The default implementation is a no-op.
-   */
-  beforeClose(widget: NotebookPanel, context: IDocumentContext<INotebookModel>): Promise<boolean> {
-    // No special action required.
-    return Promise.resolve(true);
-  }
-
   private _rendermime: RenderMime<Widget> = null;
   private _clipboard: IClipboard = null;
 }

+ 42 - 9
src/notebook/plugin.ts

@@ -14,7 +14,8 @@ import {
 } from 'phosphor-widget';
 
 import {
-  DocumentRegistry, restartKernel, selectKernelForContext
+  DocumentRegistry, restartKernel, selectKernelForContext,
+  IWidgetExtension
 } from '../docregistry';
 
 import {
@@ -75,20 +76,40 @@ const cmdIds = {
 
 
 /**
- * The notebook file handler provider.
+ * The notebook file handler extension.
  */
 export
 const notebookHandlerExtension = {
   id: 'jupyter.extensions.notebookHandler',
-  requires: [DocumentRegistry, JupyterServices, RenderMime, IClipboard, WidgetTracker],
+  requires: [DocumentRegistry, JupyterServices, RenderMime, IClipboard],
   activate: activateNotebookHandler
 };
 
 
+/**
+ * The notebook widget tracker provider.
+ */
+export
+const notebookTrackerProvider = {
+  id: 'jupyter.plugins.notebookTracker',
+  provides: NotebookTracker,
+  resolve: () => {
+    return Private.notebookTracker;
+  }
+};
+
+
+/**
+ * A class that tracks notebook widgets.
+ */
+export
+class NotebookTracker extends WidgetTracker<NotebookPanel> { }
+
+
 /**
  * Activate the notebook handler extension.
  */
-function activateNotebookHandler(app: Application, registry: DocumentRegistry, services: JupyterServices, rendermime: RenderMime<Widget>, clipboard: IClipboard, tracker: WidgetTracker): void {
+function activateNotebookHandler(app: Application, registry: DocumentRegistry, services: JupyterServices, rendermime: RenderMime<Widget>, clipboard: IClipboard): void {
 
   let widgetFactory = new NotebookWidgetFactory(rendermime, clipboard);
   registry.addModelFactory(new NotebookModelFactory());
@@ -124,12 +145,12 @@ function activateNotebookHandler(app: Application, registry: DocumentRegistry, s
 
   // Track the current active notebook.
   let activeNotebook: NotebookPanel;
+  let tracker = Private.notebookTracker;
+  widgetFactory.widgetCreated.connect((sender, widget) => {
+    tracker.addWidget(widget);
+  });
   tracker.activeWidgetChanged.connect((sender, widget) => {
-    if (widget instanceof NotebookPanel) {
-      activeNotebook = widget as NotebookPanel;
-    } else {
-      activeNotebook = null;
-    }
+    activeNotebook = widget;
   });
 
   app.commands.add([
@@ -590,3 +611,15 @@ function activateNotebookHandler(app: Application, registry: DocumentRegistry, s
   }
   ]);
 }
+
+
+/**
+ * A namespace for private data.
+ */
+namespace Private {
+  /**
+   * A singleton instance of a notebook tracker.
+   */
+  export
+  const notebookTracker = new NotebookTracker();
+}

+ 3 - 7
src/terminal/plugin.ts

@@ -20,23 +20,19 @@ import {
 export
 const terminalExtension = {
   id: 'jupyter.extensions.terminal',
-  requires: [WidgetTracker],
   activate: activateTerminal
 };
 
 
-function activateTerminal(app: Application, tracker: WidgetTracker): void {
+function activateTerminal(app: Application): void {
 
   let newTerminalId = 'terminal:create-new';
 
   // Track the current active terminal.
+  let tracker = new WidgetTracker<TerminalWidget>();
   let activeTerm: TerminalWidget;
   tracker.activeWidgetChanged.connect((sender, widget) => {
-    if (widget instanceof TerminalWidget) {
-      activeTerm = widget as TerminalWidget;
-    } else {
-      activeTerm = null;
-    }
+    activeTerm = widget;
   });
 
   app.commands.add([{