Ver código fonte

wip add document manager and console

Steven Silvester 9 anos atrás
pai
commit
999e8acf18

+ 3 - 3
package.json

@@ -10,9 +10,9 @@
     "file-loader": "^0.8.5",
     "jquery": "^2.2.0",
     "jquery-ui": "^1.10.5",
-    "jupyter-js-notebook": "^0.19.0",
-    "jupyter-js-services": "^0.9.0",
-    "jupyter-js-ui": "^0.11.0",
+    "jupyter-js-notebook": "file:../js-notebook",
+    "jupyter-js-services": "^0.10.2",
+    "jupyter-js-ui": "file:../ui",
     "jupyter-js-utils": "^0.4.0",
     "jupyter-js-widgets": "0.0.17",
     "phosphide": "^0.9.4",

+ 20 - 0
src/clipboard/plugin.ts

@@ -0,0 +1,20 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+'use strict';
+
+import {
+  MimeData as IClipboard
+} from 'phosphor-dragdrop';
+
+
+/**
+ * The clipboard provider.
+ */
+export
+const clipboardProvider = {
+  id: 'jupyter.services.clipboard',
+  provides: IClipboard,
+  resolve: () => {
+    return new IClipboard();
+  }
+};

+ 139 - 0
src/console/plugin.ts

@@ -0,0 +1,139 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+'use strict';
+
+import {
+  ConsolePanel
+} from 'jupyter-js-notebook/lib/console';
+
+import {
+  RenderMime
+} from 'jupyter-js-ui/lib/rendermime';
+
+import {
+  Application
+} from 'phosphide/lib/core/application';
+
+import {
+  Widget
+} from 'phosphor-widget';
+
+import {
+  JupyterServices
+} from '../services/plugin';
+
+
+/**
+ * The console extension.
+ */
+export
+const consoleExtension = {
+  id: 'jupyter.extensions.console',
+  requires: [JupyterServices, RenderMime],
+  activate: activateConsole
+};
+
+
+/**
+ * Activate the console extension.
+ */
+function activateConsole(app: Application, services: JupyterServices, rendermime: RenderMime<Widget>): Promise<void> {
+
+  let manager = services.notebookSessionManager;
+
+  // Add the ability to create new consoles for each kernel.
+  let specs = services.kernelspecs;
+  let displayNameMap: { [key: string]: string } = Object.create(null);
+  for (let kernelName in specs.kernelspecs) {
+    let displayName = specs.kernelspecs[kernelName].spec.display_name;
+    displayNameMap[displayName] = kernelName;
+  }
+  let displayNames = Object.keys(displayNameMap).sort((a, b) => {
+    return a.localeCompare(b);
+  });
+  let count = 0;
+  for (let displayName of displayNames) {
+    let id = `console:create-${displayNameMap[displayName]}`;
+    app.commands.add([{
+      id,
+      handler: () => {
+        manager.startNew({
+          notebookPath: `Console-${count++}`,
+          kernelName: `${displayNameMap[displayName]}`
+        }).then(session => {
+          let console = new ConsolePanel(session, rendermime.clone());
+          app.shell.addToMainArea(console);
+          console.disposed.connect(() => {
+            let index = Private.widgets.indexOf(console);
+            Private.widgets.splice(index, 1);
+            if (Private.activeWidget === console) {
+              Private.activeWidget = null;
+            }
+          });
+        });
+      }
+    }]);
+    app.palette.add([{
+      command: id,
+      category: 'Console',
+      text: `New ${displayName}`
+    }]);
+  }
+
+  // Temporary console focus follower.
+  document.body.addEventListener('focus', event => {
+    for (let widget of Private.widgets) {
+      let target = event.target as HTMLElement;
+      if (widget.isAttached && widget.isVisible) {
+        if (widget.node.contains(target)) {
+          Private.activeWidget = widget;
+          return;
+        }
+      }
+    }
+  }, true);
+
+  app.commands.add([
+  {
+    id: 'console:clear',
+    handler: () => {
+      if (Private.activeWidget) {
+        Private.activeWidget.content.clear();
+      }
+    }
+  },
+  {
+    id: 'console:execute',
+    handler: () => {
+      if (Private.activeWidget) {
+        Private.activeWidget.content.execute();
+      }
+    }
+  }
+  ]);
+  app.palette.add([
+  {
+    command: 'console:clear',
+    category: 'Console',
+    text: 'Clear'
+  },
+  {
+    command: 'console:execute',
+    category: 'Console',
+    text: 'Execute'
+  }]);
+
+  return Promise.resolve(void 0);
+}
+
+
+/**
+ * A namespace for private data.
+ */
+namespace Private {
+  export
+  var widgets: ConsolePanel[] = [];
+
+  export
+  var activeWidget: ConsolePanel = null;
+}

+ 23 - 0
src/docregistry/plugin.ts

@@ -0,0 +1,23 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+'use strict';
+
+import {
+  DocumentRegistry, TextModelFactory, Base64ModelFactory
+} from 'jupyter-js-ui/lib/docmanager';
+
+
+/**
+ * The default document registry provider.
+ */
+export
+const docRegistryProvider = {
+  id: 'jupyter.services.document-registry',
+  provides: DocumentRegistry,
+  resolve: () => {
+    let registry = new DocumentRegistry();
+    registry.registerModelFactory(new TextModelFactory());
+    registry.registerModelFactory(new Base64ModelFactory());
+    return registry;
+  }
+};

+ 32 - 0
src/editorhandler/plugin.ts

@@ -0,0 +1,32 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+'use strict';
+
+import {
+  DocumentRegistry, EditorWidgetFactory
+} from 'jupyter-js-ui/lib/docmanager';
+
+import {
+  Application
+} from 'phosphide/lib/core/application';
+
+
+/**
+ * The editor handler extension.
+ */
+export
+const editorHandlerExtension = {
+  id: 'jupyter.extensions.editorHandler',
+  requires: [DocumentRegistry],
+  activate: (app: Application, registry: DocumentRegistry) => {
+    registry.registerWidgetFactory(new EditorWidgetFactory(),
+    {
+      fileExtensions: ['.*'],
+      displayName: 'Editor',
+      modelName: 'text',
+      defaultFor: ['.*'],
+      preferKernel: false,
+      canStartKernel: false
+    });
+  }
+};

+ 66 - 82
src/filebrowser/plugin.ts

@@ -2,26 +2,18 @@
 // Distributed under the terms of the Modified BSD License.
 'use strict';
 
-import {
-  IContentsModel
-} from 'jupyter-js-services';
-
 import {
   FileBrowserWidget, FileBrowserModel
 } from 'jupyter-js-ui/lib/filebrowser';
 
 import {
-  FileHandlerRegistry
-} from 'jupyter-js-ui/lib/filehandler';
+  DocumentManager, DocumentRegistry, DocumentWidget
+} from 'jupyter-js-ui/lib/docmanager';
 
 import {
   Application
 } from 'phosphide/lib/core/application';
 
-import {
-  IChangedArgs
-} from 'phosphor-properties';
-
 import {
   Menu, MenuItem
 } from 'phosphor-menus';
@@ -30,10 +22,6 @@ import {
   TabPanel
 } from 'phosphor-tabs';
 
-import {
-  Widget
-} from 'phosphor-widget';
-
 import {
   JupyterServices
 } from '../services/plugin';
@@ -45,7 +33,7 @@ import {
 export
 const fileBrowserExtension = {
   id: 'jupyter.extensions.fileBrowser',
-  requires: [JupyterServices, FileHandlerRegistry],
+  requires: [JupyterServices, DocumentRegistry],
   activate: activateFileBrowser
 };
 
@@ -53,11 +41,54 @@ const fileBrowserExtension = {
 /**
  * Activate the file browser.
  */
-function activateFileBrowser(app: Application, provider: JupyterServices, registry: FileHandlerRegistry): Promise<void> {
+function activateFileBrowser(app: Application, provider: JupyterServices, registry: DocumentRegistry): Promise<void> {
   let contents = provider.contentsManager;
   let sessions = provider.notebookSessionManager;
+  let widgets: DocumentWidget[] = [];
+  let activeWidget: DocumentWidget;
+  let id = 0;
+
+  let opener = {
+    open: (widget: DocumentWidget) => {
+      if (!widget.id) {
+        widget.id = `document-manager-${++id}`;
+      }
+      if (!widget.isAttached) {
+        app.shell.addToMainArea(widget);
+      }
+      // TODO: Move this logic to the shell.
+      let stack = widget.parent;
+      if (!stack) {
+        return;
+      }
+      let tabs = stack.parent;
+      if (tabs instanceof TabPanel) {
+        tabs.currentWidget = widget;
+      }
+      activeWidget = widget;
+      widget.disposed.connect((w: DocumentWidget) => {
+        let index = widgets.indexOf(w);
+        widgets.splice(index, 1);
+      });
+    }
+  };
+
+  // TODO: Move focus tracking to the shell.
+  document.addEventListener('focus', event => {
+    for (let i = 0; i < widgets.length; i++) {
+      let widget = widgets[i];
+      if (widget.node.contains(event.target as HTMLElement)) {
+        activeWidget = widget;
+        break;
+      }
+    }
+  });
+
+  let docManager = new DocumentManager(
+    registry, contents, sessions, provider.kernelspecs, opener
+  );
   let model = new FileBrowserModel(contents, sessions);
-  let widget = new FileBrowserWidget(model, registry);
+  let widget = new FileBrowserWidget(model, docManager, opener);
   let menu = createMenu(widget);
 
   // Add a context menu to the dir listing.
@@ -69,70 +100,22 @@ function activateFileBrowser(app: Application, provider: JupyterServices, regist
     menu.popup(x, y);
   });
 
-  model.fileChanged.connect((mModel, args) => (args: IChangedArgs<string>) => {
-    registry.rename(args.oldValue, args.newValue);
-  });
-
-  let id = 0;
-  registry.opened.connect((r, widget) => {
-    if (!widget.id) widget.id = `document-manager-${++id}`;
-    if (!widget.isAttached) app.shell.addToMainArea(widget);
-    let stack = widget.parent;
-    if (!stack) {
-      return;
-    }
-    let tabs = stack.parent;
-    if (tabs instanceof TabPanel) {
-      tabs.currentWidget = widget;
-    }
-  });
-
   // Add the command for a new items.
   let newTextFileId = 'file-operations:new-text-file';
-  let newNotebookId = 'file-operations:new-notebook';
 
   app.commands.add([
-    {
-      id: newNotebookId,
-      handler: () => {
-        registry.createNew('notebook', model.path, widget.node).then(contents => {
-          registry.open(contents.path);
-        });
-      }
-    },
     {
       id: newTextFileId,
       handler: () => {
-        registry.createNew('file', model.path, widget.node).then(contents => {
-          registry.open(contents.path);
+        model.newUntitled('file').then(contents => {
+          let widget = docManager.open(contents.path);
+          opener.open(widget);
         });
       }
     }
   ]);
 
 
-  // Temporary file object focus follower.
-  let activeWidget: Widget;
-  let widgets: Widget[] = [];
-  document.body.addEventListener('focus', event => {
-    for (let widget of widgets) {
-      let target = event.target as HTMLElement;
-      if (widget.isAttached && widget.isVisible) {
-        if (widget.node.contains(target)) {
-          activeWidget = widget;
-          return;
-        }
-      }
-    }
-  });
-
-  // Add opened files to the widget list temporarily.
-  registry.opened.connect((r, widget) => {
-    activeWidget = widget;
-    widgets.push(widget);
-  });
-
-
   // Add the command for saving a document.
   let saveDocumentId = 'file-operations:save';
 
@@ -140,8 +123,10 @@ function activateFileBrowser(app: Application, provider: JupyterServices, regist
     {
       id: saveDocumentId,
       handler: () => {
-        let path = registry.findPath(activeWidget);
-        if (path) registry.save(path);
+        if (!activeWidget) {
+          return;
+        }
+        activeWidget.context.save();
       }
     }
   ]);
@@ -161,8 +146,10 @@ function activateFileBrowser(app: Application, provider: JupyterServices, regist
     {
       id: revertDocumentId,
       handler: () => {
-        let path = registry.findPath(activeWidget);
-        if (path) registry.revert(path);
+        if (!activeWidget) {
+          return;
+        }
+        activeWidget.context.revert();
       }
     }
   ]);
@@ -182,8 +169,9 @@ function activateFileBrowser(app: Application, provider: JupyterServices, regist
     {
       id: closeDocumentId,
       handler: () => {
-        let path = registry.findPath(activeWidget);
-        if (path) registry.close(path);
+        if (activeWidget) {
+          activeWidget.close();
+        }
       }
     }
   ]);
@@ -203,7 +191,7 @@ function activateFileBrowser(app: Application, provider: JupyterServices, regist
     {
       id: closeAllId,
       handler: () => {
-        registry.closeAll();
+        docManager.closeAll();
       }
     }
   ]);
@@ -217,12 +205,6 @@ function activateFileBrowser(app: Application, provider: JupyterServices, regist
   ]);
 
   app.palette.add([
-    {
-      command: newNotebookId,
-      category: 'File Operations',
-      text: 'New Notebook',
-      caption: 'Create a new Jupyter Notebook'
-    },
     {
       command: newTextFileId,
       category: 'File Operations',
@@ -257,7 +239,9 @@ function activateFileBrowser(app: Application, provider: JupyterServices, regist
   }
 
   function hideBrowser(): void {
-    if (!widget.isHidden) app.shell.collapseLeft();
+    if (!widget.isHidden) {
+      app.shell.collapseLeft();
+    }
   }
 
   function toggleBrowser(): void {

+ 0 - 65
src/filehandler/plugin.ts

@@ -1,65 +0,0 @@
-// Copyright (c) Jupyter Development Team.
-// Distributed under the terms of the Modified BSD License.
-'use strict';
-
-import {
-  FileHandlerRegistry, FileHandler, FileCreator
-} from 'jupyter-js-ui/lib/filehandler';
-
-import {
-  Application
-} from 'phosphide/lib/core/application';
-
-import {
-  JupyterServices
-} from '../services/plugin';
-
-
-/**
- * The default document manager provider.
- */
-export
-const fileHandlerProvider = {
-  id: 'jupyter.services.fileHandlerRegistry',
-  provides: FileHandlerRegistry,
-  resolve: () => {
-    return new FileHandlerRegistry();
-  }
-};
-
-
-/**
- * The default file handler extension.
- */
-export
-const fileHandlerExtension = {
-  id: 'jupyter.extensions.fileHandler',
-  requires: [FileHandlerRegistry, JupyterServices],
-  activate: activateFileHandler
-};
-
-
-function activateFileHandler(app: Application, registry: FileHandlerRegistry, services: JupyterServices): Promise<void> {
-  let contents = services.contentsManager;
-  let activeId = '';
-  let id = 0;
-  let fileHandler = new FileHandler(contents);
-  let dirCreator = new FileCreator(contents, 'directory');
-  let fileCreator = new FileCreator(contents, 'file');
-
-  registry.addDefaultHandler(fileHandler);
-  registry.addCreator(
-    'New Directory', dirCreator.createNew.bind(dirCreator));
-  registry.addCreator('New File', fileCreator.createNew.bind(fileCreator));
-
-  app.commands.add([
-  {
-    id: 'text-file:create-new',
-    handler: () => {
-      fileCreator.createNew('').then(contents => {
-        registry.open(contents.path);
-      });
-    }
-  }]);
-  return Promise.resolve(void 0);
-};

+ 18 - 74
src/imagehandler/plugin.ts

@@ -3,24 +3,19 @@
 'use strict';
 
 import {
-  IContentsModel, IContentsOpts
-} from 'jupyter-js-services';
-
-import {
-  AbstractFileHandler, FileHandlerRegistry
-} from 'jupyter-js-ui/lib/filehandler';
+  DocumentRegistry, ImageWidgetFactory
+} from 'jupyter-js-ui/lib/docmanager';
 
 import {
   Application
 } from 'phosphide/lib/core/application';
 
-import {
-  Widget
-} from 'phosphor-widget';
 
-import {
-  JupyterServices
-} from '../services/plugin';
+/**
+ * The list of file extensions for images.
+ */
+const EXTENSIONS = ['.png', '.gif', '.jpeg', '.jpg', '.svg', '.bmp', '.ico',
+  '.xbm', '.tiff', '.tif'];
 
 
 /**
@@ -29,67 +24,16 @@ import {
 export
 const imageHandlerExtension = {
   id: 'jupyter.extensions.imageHandler',
-  requires: [FileHandlerRegistry, JupyterServices],
-  activate: (app: Application, registry: FileHandlerRegistry, services: JupyterServices) => {
-    let handler = new ImageHandler(services.contentsManager);
-    registry.addHandler(handler);
-    return Promise.resolve(void 0);
-  }
-};
-
-
-export
-class ImageHandler extends AbstractFileHandler<Widget> {
-  /**
-   * Get the list of file extensions explicitly supported by the handler.
-   */
-  get fileExtensions(): string[] {
-    return ['.png', '.gif', '.jpeg', '.jpg', '.svg', '.bmp', '.ico', '.xbm',
-            '.tiff', '.tif'];
-  }
-
-  /**
-   * Get the options used to save the widget content.
-   */
-  protected getFetchOptions(path: string): IContentsOpts {
-    return { type: 'file' };
-  }
-
-  /**
-   * Get the options used to save the widget content.
-   */
-  protected getSaveOptions(widget: Widget, path: string): Promise<IContentsOpts> {
-    return Promise.resolve(void 0);
-  }
-
-  /**
-   * Create the widget from a path.
-   */
-  protected createWidget(path: string): Widget {
-    var widget = new Widget();
-    widget.node.tabIndex = 0;
-    let image = document.createElement('img');
-    widget.node.appendChild(image);
-    widget.node.style.overflowX = 'auto';
-    widget.node.style.overflowY = 'auto';
-    widget.title.text = path.split('/').pop();
-    return widget;
-  }
-
- /**
-  * Populate a widget from `IContentsModel`.
-  */
-  protected populateWidget(widget: Widget, model: IContentsModel): Promise<IContentsModel> {
-    return new Promise<IContentsModel>((resolve, reject) => {
-      let img = widget.node.firstChild as HTMLImageElement;
-      img.addEventListener('load', () => {
-        resolve(void 0);
-      });
-      img.addEventListener('error', error => {
-        reject(error);
-      });
-      img.src = `data:${model.mimetype};${model.format},${model.content}`;
-      return model;
+  requires: [DocumentRegistry],
+  activate: (app: Application, registry: DocumentRegistry) => {
+    registry.registerWidgetFactory(new ImageWidgetFactory(),
+    {
+      fileExtensions: EXTENSIONS,
+      displayName: 'Image',
+      modelName: 'base64',
+      defaultFor: EXTENSIONS,
+      preferKernel: false,
+      canStartKernel: false
     });
   }
-}
+};

+ 236 - 181
src/notebook/plugin.ts

@@ -3,50 +3,34 @@
 'use strict';
 
 import {
-  NotebookWidget, NotebookModel, serialize, INotebookModel, deserialize,
-  NotebookManager, NotebookToolbar, selectKernel,
-  findKernel, NotebookFileHandler, NotebookCreator, NotebookPanel
+  NotebookPanel, NotebookModelFactory, INotebookModel,
+  NotebookWidgetFactory, NotebookActions
 } from 'jupyter-js-notebook';
 
 import {
-  IContentsModel, IContentsManager, IContentsOpts,
-  INotebookSessionManager, INotebookSession, IKernelSpecIds,
-  IKernelMessage, IComm, KernelStatus, getKernelSpecs
+  IKernelId
 } from 'jupyter-js-services';
 
 import {
-  RenderMime
-} from 'jupyter-js-ui/lib/rendermime';
-
-import {
-  HTMLRenderer, LatexRenderer, ImageRenderer, TextRenderer,
-  ConsoleTextRenderer, JavascriptRenderer, SVGRenderer
-} from 'jupyter-js-ui/lib/renderers';
+  IDocumentContext, DocumentRegistry, selectKernel
+} from 'jupyter-js-ui/lib/docmanager';
 
 import {
-  showDialog
-} from 'jupyter-js-ui/lib/dialog';
-
-import {
-  FileHandlerRegistry
-} from 'jupyter-js-ui/lib/filehandler';
+  RenderMime
+} from 'jupyter-js-ui/lib/rendermime';
 
 import {
   Application
 } from 'phosphide/lib/core/application';
 
 import {
-  Panel, PanelLayout
-} from 'phosphor-panel';
+  MimeData as IClipboard
+} from 'phosphor-dragdrop';
 
 import {
   ISignal, Signal
 } from 'phosphor-signaling';
 
-import {
-  IChangedArgs
-} from 'phosphor-properties';
-
 import {
   Widget
 } from 'phosphor-widget';
@@ -55,10 +39,6 @@ import {
   JupyterServices
 } from '../services/plugin';
 
-import {
-   WidgetManager
-} from './widgetmanager';
-
 
 /**
  * The map of command ids used by the notebook.
@@ -78,40 +58,26 @@ const cmdIds = {
   paste: 'notebook-cells:paste',
   insertAbove: 'notebook-cells:insert-above',
   insertBelow: 'notebook-cells:insert-below',
-  selectPrevious: 'notebook-cells:select-previous',
-  selectNext: 'notebook-cells:select-next',
-  toggleLinenumbers: 'notebook-cells:toggle-linenumbers',
-  toggleAllLinenumbers: 'notebook:toggle-allLinenumbers',
+  selectAbove: 'notebook-cells:select-above',
+  selectBelow: 'notebook-cells:select-below',
+  extendAbove: 'notebook-cells:extend-above',
+  extendBelow: 'notebook-cells:extend-below',
   editMode: 'notebook-cells:editMode',
   commandMode: 'notebook-cells:commandMode',
-  newNotebook: 'notebook:create-new'
+  newNotebook: 'notebook:create-new',
+  undo: 'notebook-cells:undo',
+  redo: 'notebook-cells:redo',
+  launchConsole: 'notebook:launch-console'
 };
 
 
-/**
- * The class name added to notebook container widgets.
- */
-const NB_CONTAINER = 'jp-Notebook-container';
-
-/**
- * The class name added to notebook panels.
- */
-const NB_PANE = 'jp-Notebook-panel';
-
-
-/**
- * The class name added to the widget area.
- */
-let WIDGET_CLASS = 'jp-NotebookPane-widget';
-
-
 /**
  * The notebook file handler provider.
  */
 export
 const notebookHandlerExtension = {
   id: 'jupyter.extensions.notebookHandler',
-  requires: [FileHandlerRegistry, JupyterServices, RenderMime],
+  requires: [DocumentRegistry, JupyterServices, RenderMime, IClipboard],
   activate: activateNotebookHandler
 };
 
@@ -121,6 +87,24 @@ const notebookHandlerExtension = {
  */
 export
 class ActiveNotebook {
+  /**
+   * Construct a new active notebook tracker.
+   */
+  constructor() {
+    // Temporary notebook focus follower.
+    document.body.addEventListener('focus', event => {
+      for (let widget of this._widgets) {
+        let target = event.target as HTMLElement;
+        if (widget.isAttached && widget.isVisible) {
+          if (widget.node.contains(target)) {
+            this.activeNotebook = widget;
+            return;
+          }
+        }
+      }
+    }, true);
+  }
+
   /**
    * A signal emitted when the active notebook changes.
    */
@@ -129,14 +113,35 @@ class ActiveNotebook {
   }
 
   /**
-   * Get the current active notebook.
-   *
-   * #### Notes
-   * This is a read-only property.
+   * The current active notebook.
    */
   get activeNotebook(): NotebookPanel {
-    return Private.activeWidget;
+    return this._activeWidget;
   }
+  set activeNotebook(widget: NotebookPanel) {
+    if (this._activeWidget === widget) {
+      return;
+    }
+    if (this._widgets.indexOf(widget) !== -1) {
+      this._activeWidget = widget;
+      this.activeNotebookChanged.emit(widget);
+      return;
+    }
+    if (widget === null) {
+      return;
+    }
+    this._widgets.push(widget);
+    widget.disposed.connect(() => {
+      let index = this._widgets.indexOf(widget);
+      this._widgets.splice(index, 1);
+      if (this._activeWidget === widget) {
+        this.activeNotebook = null;
+      }
+    });
+  }
+
+  private _activeWidget: NotebookPanel = null;
+  private _widgets: NotebookPanel[] = [];
 }
 
 
@@ -148,7 +153,22 @@ const activeNotebookProvider = {
   id: 'jupyter.services.activeNotebook',
   provides: ActiveNotebook,
   resolve: () => {
-    return new ActiveNotebook();
+    return Private.notebookTracker;
+  }
+};
+
+
+/**
+ * A version of the notebook widget factory that uses the notebook tracker.
+ */
+class TrackingNotebookWidgetFactory extends NotebookWidgetFactory {
+  /**
+   * Create a new widget.
+   */
+  createNew(model: INotebookModel, context: IDocumentContext, kernel?: IKernelId): NotebookPanel {
+    let widget = super.createNew(model, context, kernel);
+    Private.notebookTracker.activeNotebook = widget;
+    return widget;
   }
 }
 
@@ -156,192 +176,241 @@ const activeNotebookProvider = {
 /**
  * Activate the notebook handler extension.
  */
-function activateNotebookHandler(app: Application, registry: FileHandlerRegistry, services: JupyterServices, rendermime: RenderMime<Widget>): Promise<void> {
-  let handler = new NotebookFileHandler(
-    services.contentsManager,
-    services.notebookSessionManager,
-    rendermime
-  );
-  registry.addHandler(handler);
-
-  let creator = new NotebookCreator(handler);
-  registry.addCreator('New Notebook', creator.createNew.bind(creator));
-
-  // Temporary notebook focus follower.
-  document.body.addEventListener('focus', event => {
-    for (let widget of Private.widgets) {
-      let target = event.target as HTMLElement;
-      if (widget.isAttached && widget.isVisible) {
-        if (widget.node.contains(target)) {
-          Private.activeWidget = widget;
-          return;
-        }
-      }
-    }
-  }, true);
+function activateNotebookHandler(app: Application, registry: DocumentRegistry, services: JupyterServices, rendermime: RenderMime<Widget>, clipboard: IClipboard): Promise<void> {
+
+  let widgetFactory = new TrackingNotebookWidgetFactory(rendermime, clipboard);
+  registry.registerModelFactory(new NotebookModelFactory());
+  registry.registerWidgetFactory(widgetFactory,
+  {
+    fileExtensions: ['.ipynb'],
+    displayName: 'Notebook',
+    modelName: 'notebook',
+    defaultFor: ['.ipynb'],
+    preferKernel: true,
+    canStartKernel: true
+  });
+
 
-  // Add opened notebooks to the widget list temporarily
-  handler.opened.connect((h, widget) => {
-    Private.activeWidget = widget;
-    Private.widgets.push(widget);
+  // Add the ability to launch notebooks for each kernel type.
+  let displayNameMap: { [key: string]: string } = Object.create(null);
+  let specs = services.kernelspecs;
+  for (let kernelName in specs.kernelspecs) {
+    let displayName = specs.kernelspecs[kernelName].spec.display_name;
+    displayNameMap[displayName] = kernelName;
+  }
+  let displayNames = Object.keys(displayNameMap).sort((a, b) => {
+    return a.localeCompare(b);
   });
+  for (let displayName of displayNames) {
+    registry.registerCreator({
+      name: `${displayName} Notebook`,
+      extension: '.ipynb',
+      type: 'notebook',
+      kernelName: displayNameMap[name]
+    });
+  }
 
+  let tracker = Private.notebookTracker;
   app.commands.add([
   {
     id: cmdIds['runAndAdvance'],
     handler: () => {
-      let manager = Private.activeWidget.manager;
-      if (manager) manager.runAndAdvance();
+      if (tracker.activeNotebook) {
+        let nbWidget = tracker.activeNotebook;
+        NotebookActions.runAndAdvance(nbWidget.content, nbWidget.context.kernel);
+      }
     }
   },
   {
     id: cmdIds['run'],
     handler: () => {
-      let manager = Private.activeWidget.manager;
-      if (manager) manager.run();
+      if (tracker.activeNotebook) {
+        let nbWidget = tracker.activeNotebook;
+        NotebookActions.run(nbWidget.content, nbWidget.context.kernel);
+      }
     }
   },
   {
     id: cmdIds['runAndInsert'],
     handler: () => {
-      let manager = Private.activeWidget.manager;
-      if (manager) manager.runAndInsert();
+      if (tracker.activeNotebook) {
+        let nbWidget = tracker.activeNotebook;
+        NotebookActions.runAndInsert(nbWidget.content, nbWidget.context.kernel);
+      }
     }
   },
   {
     id: cmdIds['restart'],
     handler: () => {
-      let manager = Private.activeWidget.manager;
-      if (manager) manager.restart();
+      if (tracker.activeNotebook) {
+        let nbWidget = tracker.activeNotebook;
+        nbWidget.restart();
+      }
     }
   },
   {
     id: cmdIds['interrupt'],
     handler: () => {
-      let manager = Private.activeWidget.manager;
-      if (manager) manager.interrupt();
+      if (tracker.activeNotebook) {
+        let kernel = tracker.activeNotebook.context.kernel;
+        if (kernel) {
+          kernel.interrupt();
+        }
+      }
     }
   },
   {
     id: cmdIds['toCode'],
     handler: () => {
-      let manager = Private.activeWidget.manager;
-      if (manager) manager.changeCellType('code'); }
+      if (tracker.activeNotebook) {
+        let nbWidget = tracker.activeNotebook;
+        NotebookActions.changeCellType(nbWidget.content, 'code');
+      }
+    }
   },
   {
     id: cmdIds['toMarkdown'],
     handler: () => {
-      let manager = Private.activeWidget.manager;
-      if (manager) manager.changeCellType('markdown'); }
+      if (tracker.activeNotebook) {
+        let nbWidget = tracker.activeNotebook;
+        NotebookActions.changeCellType(nbWidget.content, 'markdown');
+      }
+    }
   },
   {
     id: cmdIds['toRaw'],
     handler: () => {
-      let manager = Private.activeWidget.manager;
-      if (manager) manager.changeCellType('raw');
+      if (tracker.activeNotebook) {
+        let nbWidget = tracker.activeNotebook;
+        NotebookActions.changeCellType(nbWidget.content, 'raw');
+      }
     }
   },
   {
     id: cmdIds['cut'],
     handler: () => {
-      let manager = Private.activeWidget.manager;
-      if (manager) manager.cut();
+      if (tracker.activeNotebook) {
+        let nbWidget = tracker.activeNotebook;
+        NotebookActions.cut(nbWidget.content, nbWidget.clipboard);
+      }
     }
   },
   {
     id: cmdIds['copy'],
     handler: () => {
-      let manager = Private.activeWidget.manager;
-      if (manager) manager.copy();
+      if (tracker.activeNotebook) {
+        let nbWidget = tracker.activeNotebook;
+        NotebookActions.copy(nbWidget.content, nbWidget.clipboard);
+      }
     }
   },
   {
     id: cmdIds['paste'],
     handler: () => {
-      let manager = Private.activeWidget.manager;
-      if (manager) manager.paste();
+      if (tracker.activeNotebook) {
+        let nbWidget = tracker.activeNotebook;
+        NotebookActions.paste(nbWidget.content, nbWidget.clipboard);
+      }
     }
   },
   {
     id: cmdIds['insertAbove'],
     handler: () => {
-      let manager = Private.activeWidget.manager;
-      if (manager) manager.insertAbove();
+      if (tracker.activeNotebook) {
+        let nbWidget = tracker.activeNotebook;
+        NotebookActions.insertAbove(nbWidget.content);
+      }
     }
   },
   {
     id: cmdIds['insertBelow'],
     handler: () => {
-      let manager = Private.activeWidget.manager;
-      if (manager) manager.insertBelow();
+      if (tracker.activeNotebook) {
+        let nbWidget = tracker.activeNotebook;
+        NotebookActions.insertBelow(nbWidget.content);
+      }
     }
   },
   {
-    id: cmdIds['selectPrevious'],
+    id: cmdIds['selectAbove'],
     handler: () => {
-      let model = Private.activeWidget.model;
-      if (model) model.activeCellIndex -= 1;
+      if (tracker.activeNotebook) {
+        let nbWidget = tracker.activeNotebook;
+        NotebookActions.selectAbove(nbWidget.content);
+      }
     }
   },
   {
-    id: cmdIds['selectNext'],
+    id: cmdIds['selectBelow'],
     handler: () => {
-      let model = Private.activeWidget.model;;
-      if (model) model.activeCellIndex += 1;
+      if (tracker.activeNotebook) {
+        let nbWidget = tracker.activeNotebook;
+        NotebookActions.selectBelow(nbWidget.content);
+      }
     }
   },
   {
-    id: cmdIds['toggleLinenumbers'],
+    id: cmdIds['extendAbove'],
     handler: () => {
-      let model = Private.activeWidget.model;
-      if (model) {
-        let cell = model.cells.get(model.activeCellIndex);
-        let lineNumbers = cell.input.textEditor.lineNumbers;
-        for (let i = 0; i < model.cells.length; i++) {
-          cell = model.cells.get(i);
-          if (model.isSelected(cell) || i === model.activeCellIndex) {
-            cell.input.textEditor.lineNumbers = !lineNumbers;
-          }
-        }
+      if (tracker.activeNotebook) {
+        let nbWidget = tracker.activeNotebook;
+        NotebookActions.extendSelectionAbove(nbWidget.content);
       }
     }
   },
   {
-    id: cmdIds['toggleAllLinenumbers'],
+    id: cmdIds['extendBelow'],
     handler: () => {
-      let model = Private.activeWidget.model;
-      if (model) {
-        let cell = model.cells.get(model.activeCellIndex);
-        let lineNumbers = cell.input.textEditor.lineNumbers;
-        for (let i = 0; i < model.cells.length; i++) {
-          cell = model.cells.get(i);
-          cell.input.textEditor.lineNumbers = !lineNumbers;
-        }
+      if (tracker.activeNotebook) {
+        let nbWidget = tracker.activeNotebook;
+        NotebookActions.extendSelectionBelow(nbWidget.content);
       }
     }
   },
   {
     id: cmdIds['commandMode'],
     handler: () => {
-      let model = Private.activeWidget.model;
-      if (model) model.mode = 'command';
+      if (tracker.activeNotebook) {
+        tracker.activeNotebook.content.mode = 'command';
+      }
     }
   },
   {
     id: cmdIds['editMode'],
     handler: () => {
-      let model = Private.activeWidget.model;
-      if (model) model.mode = 'edit';
+      if (tracker.activeNotebook) {
+        tracker.activeNotebook.content.mode = 'edit';
+      }
     }
   },
   {
-    id: cmdIds['newNotebook'],
+    id: cmdIds['undo'],
     handler: () => {
-      creator.createNew('').then(contents => {
-        registry.open(contents.path);
-      });
+      if (tracker.activeNotebook) {
+        let nbWidget = tracker.activeNotebook;
+        nbWidget.content.mode = 'command';
+        nbWidget.content.model.cells.undo();
+      }
     }
   },
+  {
+    id: cmdIds['redo'],
+    handler: () => {
+      if (tracker.activeNotebook) {
+        let nbWidget = tracker.activeNotebook;
+        nbWidget.content.mode = 'command';
+        nbWidget.content.model.cells.redo();
+      }
+    }
+  },
+  {
+    id: cmdIds['switchKernel'],
+    handler: () => {
+      if (tracker.activeNotebook) {
+        selectKernel(tracker.activeNotebook.context, tracker.activeNotebook.node);
+      }
+    }
+  }
   ]);
   app.palette.add([
   {
@@ -410,24 +479,24 @@ function activateNotebookHandler(app: Application, registry: FileHandlerRegistry
     text: 'Insert cell below'
   },
   {
-    command: cmdIds['selectPrevious'],
+    command: cmdIds['selectAbove'],
     category: 'Notebook Cell Operations',
-    text: 'Select previous cell'
+    text: 'Select cell below'
   },
   {
-    command: cmdIds['selectNext'],
+    command: cmdIds['selectBelow'],
     category: 'Notebook Cell Operations',
-    text: 'Select next cell'
+    text: 'Select cell above'
   },
   {
-    command: cmdIds['toggleLinenumbers'],
+    command: cmdIds['extendAbove'],
     category: 'Notebook Cell Operations',
-    text: 'Toggle line numbers'
+    text: 'Extend selection above'
   },
   {
-    command: cmdIds['toggleAllLinenumbers'],
-    category: 'Notebook Operations',
-    text: 'Toggle all line numbers'
+    command: cmdIds['extendBelow'],
+    category: 'Notebook Cell Operations',
+    text: 'Extend selction below'
   },
   {
     command: cmdIds['editMode'],
@@ -438,31 +507,14 @@ function activateNotebookHandler(app: Application, registry: FileHandlerRegistry
     command: cmdIds['commandMode'],
     category: 'Notebook Cell Operations',
     text: 'To Command Mode'
+  },
+  {
+    command: cmdIds['switchKernel'],
+    category: 'Notebook Operations',
+    text: 'Switch Kernel'
   }
   ]);
 
-  getKernelSpecs({}).then(specs => {
-    app.commands.add([
-    {
-      id: cmdIds['switchKernel'],
-      handler: () => {
-        let model = Private.activeWidget.model;
-        let name = model.kernelspec.name;
-        if (model) {
-          selectKernel(Private.activeWidget.parent.node, name, specs).then(newName => {
-            if (newName) model.session.changeKernel({name: newName});
-          });
-        }
-      }
-    }]);
-    app.palette.add([
-    {
-      command: cmdIds['switchKernel'],
-      category: 'Notebook Operations',
-      text: 'Switch Kernel'
-    }]);
-  });
-
   return Promise.resolve(void 0);
 }
 
@@ -471,12 +523,15 @@ function activateNotebookHandler(app: Application, registry: FileHandlerRegistry
  * A namespace for notebook plugin private data.
  */
 namespace Private {
+  /**
+   * A signal emitted when the active notebook changes.
+   */
   export
-  var activeWidget: NotebookPanel = null;
-
-  export
-  var widgets: NotebookPanel[] = [];
+  const activeNotebookChangedSignal = new Signal<ActiveNotebook, NotebookPanel>();
 
+  /**
+   * A singleton notebook tracker instance.
+   */
   export
-  const activeNotebookChangedSignal = new Signal<ActiveNotebook, NotebookPanel>();
+  const notebookTracker = new ActiveNotebook();
 }

+ 0 - 48
src/notebook/widgetmanager.ts

@@ -1,48 +0,0 @@
-// Copyright (c) Jupyter Development Team.
-// Distributed under the terms of the Modified BSD License.
-'use strict';
-
-import * as Backbone from 'backbone';
-
-import {
-    ManagerBase, shims
-} from 'jupyter-js-widgets';
-
-import {
-  Panel
-} from 'phosphor-panel';
-
-import {
-  BackboneViewWrapper
-} from '../backboneviewwrapper/plugin';
-
-import 'jquery-ui/themes/smoothness/jquery-ui.min.css';
-
-import 'jupyter-js-widgets/css/widgets.min.css';
-
-export
-class WidgetManager extends ManagerBase {
-  constructor(panel: Panel) {
-    super();
-    this._panel = panel;
-  }
-
-  display_view(msg: any, view: Backbone.View<any>, options: any): Promise<Backbone.View<any>> {
-    return Promise.resolve(view).then(view => {
-      this._panel.addChild(new BackboneViewWrapper(view));
-      return view;
-    });
-  }
-
-  /**
-   * Handle when a comm is opened.
-   */
-  handle_comm_open(comm: any, msg: any) {
-    // Convert jupyter-js-services comm to old comm
-    // so that widget models use it compatibly
-    let oldComm = new shims.services.Comm(comm);
-    return super.handle_comm_open(oldComm, msg);
-  }
-
-  private _panel: Panel;
-}

+ 0 - 118
src/readonly-notebook/plugin.ts

@@ -1,118 +0,0 @@
-// Copyright (c) Jupyter Development Team.
-// Distributed under the terms of the Modified BSD License.
-'use strict';
-
-import {
-  NotebookWidget, NotebookModel, serialize, deserialize
-} from 'jupyter-js-notebook';
-
-import {
-  IContentsModel, IContentsManager, IContentsOpts
-} from 'jupyter-js-services';
-
-import {
-  AbstractFileHandler, FileHandlerRegistry
-} from 'jupyter-js-ui/lib/filehandler';
-
-import {
-  RenderMime
-} from 'jupyter-js-ui/lib/rendermime';
-
-import {
-  HTMLRenderer, LatexRenderer, ImageRenderer, TextRenderer,
-  ConsoleTextRenderer, JavascriptRenderer, SVGRenderer
-} from 'jupyter-js-ui/lib/renderers';
-
-import {
-  Application
-} from 'phosphide/lib/core/application';
-
-import {
-  Widget
-} from 'phosphor-widget';
-
-import {
-  JupyterServices
-} from '../services/plugin';
-
-
-/**
- * The notebook file handler provider.
- */
-export
-const notebookHandlerExtension = {
-  id: 'jupyter.extensions.notebookHandler',
-  requires: [FileHandlerRegistry, JupyterServices, RenderMime],
-  activate: activateNotebookHandler
-};
-
-
-/**
- * Activate the notebook handler extension.
- */
-function activateNotebookHandler(app: Application, registry: FileHandlerRegistry, services: JupyterServices, rendermime: RenderMime<Widget>): Promise<void> {
-  let handler = new NotebookFileHandler(
-    services.contentsManager,
-    rendermime
-  );
-  registry.addHandler(handler);
-  return Promise.resolve(void 0);
-}
-
-
-/**
- * An implementation of a file handler.
- */
-class NotebookFileHandler extends AbstractFileHandler<NotebookWidget> {
-
-  constructor(contents: IContentsManager, rendermime: RenderMime<Widget>) {
-    super(contents);
-    this._rendermime = rendermime;
-  }
-
-  /**
-   * Get the list of file extensions supported by the handler.
-   */
-  get fileExtensions(): string[] {
-    return ['.ipynb'];
-  }
-
-  /**
-   * Get options use to fetch the model contents from disk.
-   */
-  protected getFetchOptions(path: string): IContentsOpts {
-    return { type: 'notebook' };
-  }
-
-  /**
-   * Get the options used to save the widget content.
-   */
-  protected getSaveOptions(widget: NotebookWidget, path: string): Promise<IContentsOpts> {
-      let content = serialize(widget.model);
-      return Promise.resolve({ type: 'notebook', content });
-  }
-
-  /**
-   * Create the widget from a path.
-   */
-  protected createWidget(path: string): NotebookWidget {
-    let model = new NotebookModel();
-    model.readOnly = true;
-    return new NotebookWidget(model, this._rendermime);
-  }
-
-  /**
-   * Populate the notebook widget with the contents of the notebook.
-   */
-  protected populateWidget(widget: NotebookWidget, model: IContentsModel): Promise<IContentsModel> {
-    deserialize(model.content, widget.model);
-    if (widget.model.cells.length === 0) {
-      let cell = widget.model.createCodeCell();
-      widget.model.cells.add(cell);
-    }
-
-    return Promise.resolve(model);
-  }
-
-  private _rendermime: RenderMime<Widget> = null;
-}

+ 7 - 8
src/rendermime/plugin.ts

@@ -3,12 +3,12 @@
 'use strict';
 
 import {
-  RenderMime
+  RenderMime, MimeMap, IRenderer
 } from 'jupyter-js-ui/lib/rendermime';
 
 import {
   HTMLRenderer, LatexRenderer, ImageRenderer, TextRenderer,
-  ConsoleTextRenderer, JavascriptRenderer, SVGRenderer, MarkdownRenderer
+  JavascriptRenderer, SVGRenderer, MarkdownRenderer
 } from 'jupyter-js-ui/lib/renderers';
 
 import {
@@ -24,7 +24,6 @@ const renderMimeProvider = {
   id: 'jupyter.services.rendermime',
   provides: RenderMime,
   resolve: () => {
-    let rendermime = new RenderMime<Widget>();
     const transformers = [
       new JavascriptRenderer(),
       new MarkdownRenderer(),
@@ -32,16 +31,16 @@ const renderMimeProvider = {
       new ImageRenderer(),
       new SVGRenderer(),
       new LatexRenderer(),
-      new ConsoleTextRenderer(),
       new TextRenderer()
     ];
-
+    let renderers: MimeMap<IRenderer<Widget>> = {};
+    let order: string[] = [];
     for (let t of transformers) {
       for (let m of t.mimetypes) {
-        rendermime.order.push(m);
-        rendermime.renderers[m] = t;
+        renderers[m] = t;
+        order.push(m);
       }
     }
-    return rendermime;
+    return new RenderMime<Widget>(renderers, order);
   }
 };

+ 18 - 5
src/services/plugin.ts

@@ -8,7 +8,8 @@ import {
 
 import {
   IKernelManager, INotebookSessionManager, IContentsManager,
-  ContentsManager, KernelManager, NotebookSessionManager
+  ContentsManager, KernelManager, NotebookSessionManager,
+  getKernelSpecs, IKernelSpecIds, IAjaxSettings
 } from 'jupyter-js-services';
 
 
@@ -21,15 +22,21 @@ class JupyterServices {
   /**
    * Construct a new services provider.
    */
-  constructor() {
-    let baseUrl = getBaseUrl();
-    let ajaxSettings = getConfigOption('ajaxSettings');
+  constructor(baseUrl: string, ajaxSettings: IAjaxSettings, specs: IKernelSpecIds) {
     let options = { baseUrl, ajaxSettings };
+    this._kernelspecs = specs;
     this._kernelManager = new KernelManager(options);
     this._sessionManager = new NotebookSessionManager(options);
     this._contentsManager = new ContentsManager(baseUrl, ajaxSettings);
   }
 
+  /**
+   * Get kernel specs.
+   */
+  get kernelspecs(): IKernelSpecIds {
+    return this._kernelspecs;
+  }
+
   /**
    * Get kernel manager instance.
    *
@@ -63,6 +70,7 @@ class JupyterServices {
   private _kernelManager: IKernelManager = null;
   private _sessionManager: INotebookSessionManager = null;
   private _contentsManager: IContentsManager = null;
+  private _kernelspecs: IKernelSpecIds = null;
 }
 
 
@@ -74,6 +82,11 @@ const servicesProvider = {
   id: 'jupyter.services.services',
   provides: JupyterServices,
   resolve: () => {
-    return new JupyterServices();
+    let baseUrl = getBaseUrl();
+    let ajaxSettings = getConfigOption('ajaxSettings');
+    let options = { baseUrl, ajaxSettings };
+    return getKernelSpecs(options).then(specs => {
+      return new JupyterServices(baseUrl, ajaxSettings, specs);
+    });
   }
 };