浏览代码

Merge pull request #1201 from blink1073/services-27

Update Services and Improve handling of Kernel specs
Afshin Darian 8 年之前
父节点
当前提交
efef3a8d14
共有 44 个文件被更改,包括 562 次插入747 次删除
  1. 1 1
      examples/console/package.json
  2. 1 1
      examples/filebrowser/package.json
  3. 2 1
      examples/filebrowser/src/index.ts
  4. 1 1
      examples/notebook/package.json
  5. 3 2
      examples/notebook/src/index.ts
  6. 1 1
      examples/terminal/package.json
  7. 2 2
      examples/terminal/src/index.ts
  8. 1 1
      package.json
  9. 1 1
      src/completer/handler.ts
  10. 5 7
      src/console/content.ts
  11. 3 1
      src/console/history.ts
  12. 24 28
      src/console/plugin.ts
  13. 16 39
      src/docmanager/manager.ts
  14. 1 2
      src/docmanager/widgetmanager.ts
  15. 70 48
      src/docregistry/context.ts
  16. 33 32
      src/docregistry/kernelselector.ts
  17. 13 16
      src/docregistry/registry.ts
  18. 0 9
      src/filebrowser/browser.ts
  19. 3 16
      src/filebrowser/buttons.ts
  20. 30 34
      src/filebrowser/dialogs.ts
  21. 7 9
      src/filebrowser/listing.ts
  22. 37 55
      src/filebrowser/model.ts
  23. 1 1
      src/inspector/handler.ts
  24. 24 41
      src/notebook/notebook/panel.ts
  25. 1 1
      src/notebook/output-area/model.ts
  26. 3 3
      src/notebook/plugin.ts
  27. 7 15
      src/running/index.ts
  28. 2 2
      src/services/plugin.ts
  29. 11 4
      src/terminal/index.ts
  30. 12 6
      src/terminal/plugin.ts
  31. 5 6
      src/toolbar/kernel.ts
  32. 5 5
      test/src/console/foreign.spec.ts
  33. 2 5
      test/src/docmanager/savehandler.spec.ts
  34. 2 5
      test/src/docmanager/widgetmanager.spec.ts
  35. 42 80
      test/src/docregistry/context.spec.ts
  36. 2 5
      test/src/docregistry/default.spec.ts
  37. 29 42
      test/src/filebrowser/model.spec.ts
  38. 6 8
      test/src/markdownwidget/widget.spec.ts
  39. 59 115
      test/src/notebook/notebook/default-toolbar.spec.ts
  40. 56 47
      test/src/notebook/notebook/panel.spec.ts
  41. 6 6
      test/src/notebook/notebook/widgetfactory.spec.ts
  42. 1 1
      test/src/notebook/output-area/model.spec.ts
  43. 20 29
      test/src/notebook/tracker.spec.ts
  44. 11 13
      test/src/utils.ts

+ 1 - 1
examples/console/package.json

@@ -9,7 +9,7 @@
     "watch": "watch \"npm run update && npm run build\" ../../src src --wait 10"
   },
   "dependencies": {
-    "@jupyterlab/services": "^0.25.0",
+    "@jupyterlab/services": "^0.30.2",
     "jupyterlab": "file:../..",
     "phosphor": "^0.7.0"
   },

+ 1 - 1
examples/filebrowser/package.json

@@ -9,7 +9,7 @@
     "watch": "watch \"npm run update && npm run build\" ../../src src --wait 10"
   },
   "dependencies": {
-    "@jupyterlab/services": "^0.25.0",
+    "@jupyterlab/services": "^0.30.2",
     "jupyterlab": "file:../..",
     "phosphor": "^0.7.0"
   },

+ 2 - 1
examples/filebrowser/src/index.ts

@@ -54,7 +54,8 @@ import '../index.css';
 
 
 function main(): void {
-  ServiceManager.create().then(manager => {
+  let manager = new ServiceManager();
+  manager.ready().then(() => {
     createApp(manager);
   });
 }

+ 1 - 1
examples/notebook/package.json

@@ -9,7 +9,7 @@
     "watch": "watch \"npm run update && npm run build\" ../../src src --wait 10"
   },
   "dependencies": {
-    "@jupyterlab/services": "^0.25.0",
+    "@jupyterlab/services": "^0.30.2",
     "jupyterlab": "file:../..",
     "phosphor": "^0.7.0"
   },

+ 3 - 2
examples/notebook/src/index.ts

@@ -90,7 +90,8 @@ const cmdIds = {
 
 
 function main(): void {
-  ServiceManager.create().then(manager => {
+  let manager = new ServiceManager();
+  manager.ready().then(() => {
     createApp(manager);
   });
 }
@@ -187,7 +188,7 @@ function createApp(manager: ServiceManager.IManager): void {
   });
   commands.addCommand(cmdIds.switchKernel, {
     label: 'Switch Kernel',
-    execute: () => selectKernelForContext(nbWidget.context, nbWidget.node)
+    execute: () => selectKernelForContext(nbWidget.context, manager.sessions, nbWidget.node)
   });
   commands.addCommand(cmdIds.runAndAdvance, {
     label: 'Run and Advance',

+ 1 - 1
examples/terminal/package.json

@@ -9,7 +9,7 @@
     "watch": "watch \"npm run update && npm run build\" ../../src src --wait 10"
   },
   "dependencies": {
-    "@jupyterlab/services": "^0.25.0",
+    "@jupyterlab/services": "^0.30.2",
     "jupyterlab": "file:../..",
     "phosphor": "^0.7.0",
     "xterm": "^1.1.3"

+ 2 - 2
examples/terminal/src/index.ts

@@ -31,8 +31,8 @@ function main(): void {
     color: 'black'
   });
 
-  TerminalSession.open().then(session => term1.session = session);
-  TerminalSession.open().then(session => term2.session = session);
+  TerminalSession.startNew().then(session => { term1.session = session; });
+  TerminalSession.startNew().then(session => { term2.session = session; });
 
   term1.title.closable = true;
   term2.title.closable = true;

+ 1 - 1
package.json

@@ -7,7 +7,7 @@
   },
   "typings": "lib/index.d.ts",
   "dependencies": {
-    "@jupyterlab/services": "^0.25.0",
+    "@jupyterlab/services": "^0.30.2",
     "ansi_up": "^1.3.0",
     "backbone": "^1.2.0",
     "codemirror": "^5.20.2",

+ 1 - 1
src/completer/handler.ts

@@ -107,7 +107,7 @@ class CellCompleterHandler implements IDisposable {
     };
     let pending = ++this._pending;
 
-    return this._kernel.complete(content).then(msg => {
+    return this._kernel.requestComplete(content).then(msg => {
       this.onReply(pending, request, msg);
     });
   }

+ 5 - 7
src/console/content.ts

@@ -497,12 +497,10 @@ class ConsoleContent extends Widget {
    * Initialize the banner and mimetype.
    */
   private _initialize(): void {
-    let session = this._session;
-    if (session.kernel.info) {
-      this._handleInfo(this._session.kernel.info);
-      return;
-    }
-    session.kernel.kernelInfo().then(msg => this._handleInfo(msg.content));
+    let kernel = this._session.kernel;
+    kernel.ready().then(() => {
+      this._handleInfo(kernel.info);
+    });
   }
 
   /**
@@ -606,7 +604,7 @@ class ConsoleContent extends Widget {
     let code = prompt.model.source + '\n';
     return new Promise<boolean>((resolve, reject) => {
       let timer = setTimeout(() => { resolve(true); }, timeout);
-      this._session.kernel.isComplete({ code }).then(isComplete => {
+      this._session.kernel.requestIsComplete({ code }).then(isComplete => {
         clearTimeout(timer);
         if (isComplete.content.status !== 'incomplete') {
           resolve(true);

+ 3 - 1
src/console/history.ts

@@ -114,7 +114,9 @@ class ConsoleHistory implements IConsoleHistory {
       return;
     }
 
-    this._kernel.history(Private.initialRequest).then(v => this.onHistory(v));
+    this._kernel.requestHistory(Private.initialRequest).then(v => {
+      this.onHistory(v);
+    });
   }
 
   /**

+ 24 - 28
src/console/plugin.ts

@@ -105,7 +105,7 @@ const tracker = new InstanceTracker<ConsolePanel>();
 
 
 /**
- * The interface for a start console.
+ * The arguments used to create a console.
  */
 interface ICreateConsoleArgs extends JSONObject {
   id?: string;
@@ -120,7 +120,6 @@ interface ICreateConsoleArgs extends JSONObject {
  */
 function activateConsole(app: JupyterLab, services: IServiceManager, rendermime: IRenderMime, mainMenu: IMainMenu, inspector: IInspector, palette: ICommandPalette, pathTracker: IPathTracker, renderer: ConsoleContent.IRenderer): IConsoleTracker {
   let manager = services.sessions;
-  let specs = services.kernelspecs;
 
   let { commands, keymap } = app;
   let category = 'Console';
@@ -231,11 +230,12 @@ function activateConsole(app: JupyterLab, services: IServiceManager, rendermime:
 
       // If we get a session, use it.
       if (args.id) {
-        return manager.connectTo(args.id).then(session => {
+        return manager.ready().then(() => {
+          return manager.connectTo(args.id);
+        }).then(session => {
           name = session.path.split('/').pop();
           name = `Console ${name.match(CONSOLE_REGEX)[1]}`;
           createConsole(session, name);
-          manager.listRunning();  // Trigger a refresh.
           return session.id;
         });
       }
@@ -249,7 +249,9 @@ function activateConsole(app: JupyterLab, services: IServiceManager, rendermime:
       path = `${path}/console-${count}-${utils.uuid()}`;
 
       // Get the kernel model.
-      return getKernel(args, name).then(kernel => {
+      return manager.ready().then(() => {
+        return getKernel(args, name);
+      }).then(kernel => {
         if (!kernel) {
           return;
         }
@@ -261,7 +263,6 @@ function activateConsole(app: JupyterLab, services: IServiceManager, rendermime:
         };
         return manager.startNew(options).then(session => {
           createConsole(session, name);
-          manager.listRunning();  // Trigger a refresh.
           return session.id;
         });
       });
@@ -305,11 +306,11 @@ function activateConsole(app: JupyterLab, services: IServiceManager, rendermime:
     if (args.kernel) {
       return Promise.resolve(args.kernel);
     }
-    return manager.listRunning().then((sessions: Session.IModel[]) => {
+    return manager.ready().then(() => {
       let options = {
         name,
-        specs,
-        sessions,
+        specs: manager.specs,
+        sessions: manager.running(),
         preferredLanguage: args.preferredLanguage || '',
         host: document.body
       };
@@ -317,16 +318,13 @@ function activateConsole(app: JupyterLab, services: IServiceManager, rendermime:
     });
   }
 
-  let displayNameMap: { [key: string]: string } = Object.create(null);
-  for (let kernelName in specs.kernelspecs) {
-    let displayName = specs.kernelspecs[kernelName].display_name;
-    displayNameMap[kernelName] = displayName;
-  }
-
   let id = 0; // The ID counter for notebook panels.
 
   /**
    * Create a console for a given session.
+   *
+   * #### Notes
+   * The manager must be ready before calling this function.
    */
   function createConsole(session: Session.ISession, name: string): void {
     let panel = new ConsolePanel({
@@ -334,7 +332,8 @@ function activateConsole(app: JupyterLab, services: IServiceManager, rendermime:
       rendermime: rendermime.clone(),
       renderer: renderer
     });
-    let displayName = displayNameMap[session.kernel.name];
+    let specs = manager.specs;
+    let displayName = specs.kernelspecs[session.kernel.name].display_name;
     let captionOptions: Private.ICaptionOptions = {
       label: name,
       displayName,
@@ -355,9 +354,9 @@ function activateConsole(app: JupyterLab, services: IServiceManager, rendermime:
     });
     // Update the caption of the tab when the kernel changes.
     panel.content.session.kernelChanged.connect(() => {
-      let name = panel.content.session.kernel.name;
+      let newName = panel.content.session.kernel.name;
       name = specs.kernelspecs[name].display_name;
-      captionOptions.displayName = name;
+      captionOptions.displayName = newName;
       captionOptions.connected = new Date();
       captionOptions.executed = null;
       panel.title.caption = Private.caption(captionOptions);
@@ -379,14 +378,15 @@ function activateConsole(app: JupyterLab, services: IServiceManager, rendermime:
       let widget = current.content;
       let session = widget.session;
       let lang = '';
-      if (session.kernel) {
-        lang = specs.kernelspecs[session.kernel.name].language;
-      }
-      manager.listRunning().then((sessions: Session.IModel[]) => {
+      manager.ready().then(() => {
+        let specs = manager.specs;
+        if (session.kernel) {
+          lang = specs.kernelspecs[session.kernel.name].language;
+        }
         let options = {
           name: widget.parent.title.label,
           specs,
-          sessions,
+          sessions: manager.running(),
           preferredLanguage: lang,
           kernel: session.kernel.model,
           host: widget.parent.node
@@ -395,7 +395,7 @@ function activateConsole(app: JupyterLab, services: IServiceManager, rendermime:
       }).then((kernelId: Kernel.IModel) => {
         // If the user cancels, kernelId will be void and should be ignored.
         if (kernelId) {
-          session.changeKernel(kernelId);
+          return session.changeKernel(kernelId);
         }
       });
     }
@@ -465,7 +465,3 @@ namespace Private {
     return caption;
   }
 }
-
-
-
-

+ 16 - 39
src/docmanager/manager.ts

@@ -6,7 +6,7 @@ import {
 } from '@jupyterlab/services';
 
 import {
-  each
+  IIterable, each
 } from 'phosphor/lib/algorithm/iteration';
 
 import {
@@ -67,17 +67,17 @@ class DocumentManager implements IDisposable {
   }
 
   /**
-   * Get the kernel spec models for the manager.
+   * Get the registry used by the manager.
    */
-  get kernelspecs(): Kernel.ISpecModels {
-    return this._serviceManager.kernelspecs;
+  get registry(): DocumentRegistry {
+    return this._registry;
   }
 
   /**
-   * Get the registry used by the manager.
+   * Get the service manager used by the manager.
    */
-  get registry(): DocumentRegistry {
-    return this._registry;
+  get services(): ServiceManager.IManager {
+    return this._serviceManager;
   }
 
   /**
@@ -127,8 +127,11 @@ class DocumentManager implements IDisposable {
       // Load the contents from disk.
       context.revert();
     }
-    if (kernel) {
+    // Handle the kernel for the context.
+    if (kernel && widgetFactory.canStartKernel) {
       context.changeKernel(kernel);
+    } else if (widgetFactory.preferKernel && !context.kernel) {
+      context.startDefaultKernel();
     }
     let widget = this._widgetManager.createWidget(widgetFactory.name, context);
     this._opener.open(widget);
@@ -156,43 +159,17 @@ class DocumentManager implements IDisposable {
     let context = this._createContext(path, factory);
     // Immediately save the contents to disk.
     context.save();
-    if (kernel) {
+    // Handle the kernel for the context.
+    if (kernel && widgetFactory.canStartKernel) {
       context.changeKernel(kernel);
+    } else if (widgetFactory.preferKernel && !context.kernel) {
+      context.startDefaultKernel();
     }
     let widget = this._widgetManager.createWidget(widgetFactory.name, context);
     this._opener.open(widget);
     return widget;
   }
 
-  /**
-   * List the running notebook sessions.
-   */
-  listSessions(): Promise<Session.IModel[]> {
-    return this._serviceManager.sessions.listRunning();
-  }
-
-  /**
-   * Handle the renaming of an open document.
-   *
-   * @param oldPath - The previous path.
-   *
-   * @param newPath - The new path.
-   */
-  handleRename(oldPath: string, newPath: string): void {
-    each(this._contexts, context => {
-      if (context.path === oldPath) {
-        context.setPath(newPath);
-      }
-    });
-  }
-
-  /**
-   * Handle a file deletion.
-   */
-  handleDelete(path: string): void {
-    // TODO: Leave all of the widgets open and flag them as orphaned?
-  }
-
   /**
    * See if a widget already exists for the given path and widget name.
    *
@@ -287,7 +264,7 @@ class DocumentManager implements IDisposable {
       manager: this._serviceManager
     });
     Private.saveHandlerProperty.set(context, handler);
-    context.populated.connect(() => {
+    context.ready().then(() => {
       handler.start();
     });
     context.disposed.connect(() => {

+ 1 - 2
src/docmanager/widgetmanager.ts

@@ -110,11 +110,10 @@ class DocumentWidgetManager implements IDisposable {
       disposables.dispose();
     });
     this.adoptWidget(context, widget);
-    this.setCaption(widget);
     context.fileChanged.connect(() => {
       this.setCaption(widget);
     });
-    context.populated.connect(() => {
+    context.ready().then(() => {
       this.setCaption(widget);
     });
     return widget;

+ 70 - 48
src/docregistry/context.ts

@@ -25,6 +25,10 @@ import {
   Widget
 } from 'phosphor/lib/ui/widget';
 
+import {
+  findKernel
+} from '../docregistry';
+
 import {
   showDialog, okButton
 } from '../dialog';
@@ -53,32 +57,31 @@ class Context<T extends DocumentRegistry.IModel> implements DocumentRegistry.ICo
     let lang = this._factory.preferredLanguage(ext);
     this._model = this._factory.createNew(lang);
     manager.sessions.runningChanged.connect(this._onSessionsChanged, this);
+    manager.contents.fileChanged.connect(this._onFileChanged, this);
+    this._readyPromise = manager.ready().then(() => {
+      return this._populatedPromise.promise;
+    });
   }
 
   /**
    * A signal emitted when the kernel changes.
    */
-  kernelChanged: ISignal<DocumentRegistry.IContext<T>, Kernel.IKernel>;
+  kernelChanged: ISignal<this, Kernel.IKernel>;
 
   /**
    * A signal emitted when the path changes.
    */
-  pathChanged: ISignal<DocumentRegistry.IContext<T>, string>;
+  pathChanged: ISignal<this, string>;
 
   /**
    * A signal emitted when the model is saved or reverted.
    */
-  fileChanged: ISignal<DocumentRegistry.IContext<T>, Contents.IModel>;
-
-  /**
-   * A signal emitted when the context is fully populated for the first time.
-   */
-  populated: ISignal<DocumentRegistry.IContext<T>, void>;
+  fileChanged: ISignal<this, Contents.IModel>;
 
   /**
    * A signal emitted when the context is disposed.
    */
-  disposed: ISignal<DocumentRegistry.IContext<T>, void>;
+  disposed: ISignal<this, void>;
 
   /**
    * Get the model associated with the document.
@@ -111,20 +114,6 @@ class Context<T extends DocumentRegistry.IModel> implements DocumentRegistry.ICo
     return this._contentsModel;
   }
 
-  /**
-   * Get the kernel spec information.
-   */
-  get kernelspecs(): Kernel.ISpecModels {
-    return this._manager.kernelspecs;
-  }
-
-  /**
-   * Test whether the context is fully populated.
-   */
-  get isPopulated(): boolean {
-    return this._isPopulated;
-  }
-
   /**
    * Get the model factory name.
    *
@@ -162,6 +151,44 @@ class Context<T extends DocumentRegistry.IModel> implements DocumentRegistry.ICo
     }
   }
 
+  /**
+   * The kernel spec models
+   */
+  get specs(): Kernel.ISpecModels {
+    return this._manager.specs;
+  }
+
+  /**
+   * Whether the context is ready.
+   */
+  get isReady(): boolean {
+    return this._isReady;
+  }
+
+ /**
+  * A promise that is fulfilled when the context is ready.
+  */
+  ready(): Promise<void> {
+    return this._readyPromise;
+  }
+
+  /**
+   * Start the default kernel for the context.
+   *
+   * @returns A promise that resolves with the new kernel.
+   */
+  startDefaultKernel(): Promise<Kernel.IKernel> {
+    return this.ready().then(() => {
+      let model = this.model;
+      let name = findKernel(
+        model.defaultKernelName,
+        model.defaultKernelLanguage,
+        this._manager.specs
+      );
+      return this.changeKernel({ name });
+    });
+  }
+
   /**
    * Change the current kernel associated with the document.
    */
@@ -240,7 +267,7 @@ class Context<T extends DocumentRegistry.IModel> implements DocumentRegistry.ICo
       if (!newPath) {
         return;
       }
-      this.setPath(newPath);
+      this._path = newPath;
       let session = this._session;
       if (session) {
         let options: Session.IOptions = {
@@ -326,13 +353,6 @@ class Context<T extends DocumentRegistry.IModel> implements DocumentRegistry.ICo
     return this._manager.contents.listCheckpoints(this._path);
   }
 
-  /**
-   * Get the list of running sessions.
-   */
-  listSessions(): Promise<Session.IModel[]> {
-    return this._manager.sessions.listRunning();
-  }
-
   /**
    * Resolve a relative url to a correct server path.
    */
@@ -360,21 +380,19 @@ class Context<T extends DocumentRegistry.IModel> implements DocumentRegistry.ICo
   }
 
   /**
-   * Set the path of the context.
-   *
-   * #### Notes
-   * This is not part of the `IContext` API and
-   * is not intended to be called by the user.
-   * It is assumed that the file has been renamed on the
-   * contents manager prior to this operation.
+   * Handle a change on the contents manager.
    */
-  setPath(value: string): void {
-    this._path = value;
-    let session = this._session;
-    if (session) {
-      session.rename(value);
+  private _onFileChanged(sender: Contents.IManager, change: Contents.IChangedArgs): void {
+    if (change.type !== 'rename') {
+      return;
+    }
+    if (change.oldValue.path === this._path) {
+      let path = this._path = change.newValue.path;
+      if (this._session) {
+        this._session.rename(path);
+      }
+      this.pathChanged.emit(path);
     }
-    this.pathChanged.emit(value);
   }
 
   /**
@@ -389,7 +407,8 @@ class Context<T extends DocumentRegistry.IModel> implements DocumentRegistry.ICo
       this.kernelChanged.emit(session.kernel);
       session.pathChanged.connect((s, path) => {
         if (path !== this._path) {
-          this.setPath(path);
+          this._path = path;
+          this.pathChanged.emit(path);
         }
       });
       session.kernelChanged.connect((s, kernel) => {
@@ -447,7 +466,8 @@ class Context<T extends DocumentRegistry.IModel> implements DocumentRegistry.ICo
         return this.createCheckpoint();
       }
     }).then(() => {
-      this.populated.emit(void 0);
+      this._isReady = true;
+      this._populatedPromise.resolve(void 0);
     });
   }
 
@@ -457,8 +477,11 @@ class Context<T extends DocumentRegistry.IModel> implements DocumentRegistry.ICo
   private _path = '';
   private _session: Session.ISession = null;
   private _factory: DocumentRegistry.IModelFactory<T> = null;
-  private _isPopulated = false;
   private _contentsModel: Contents.IModel = null;
+  private _readyPromise: Promise<void>;
+  private _populatedPromise = new utils.PromiseDelegate<void>();
+  private _isPopulated = false;
+  private _isReady = false;
 }
 
 
@@ -466,7 +489,6 @@ class Context<T extends DocumentRegistry.IModel> implements DocumentRegistry.ICo
 defineSignal(Context.prototype, 'kernelChanged');
 defineSignal(Context.prototype, 'pathChanged');
 defineSignal(Context.prototype, 'fileChanged');
-defineSignal(Context.prototype, 'populated');
 defineSignal(Context.prototype, 'disposed');
 
 

+ 33 - 32
src/docregistry/kernelselector.ts

@@ -5,6 +5,10 @@ import {
   Kernel, Session
 } from '@jupyterlab/services';
 
+import {
+  IterableOrArrayLike, each
+} from 'phosphor/lib/algorithm/iteration';
+
 import {
   showDialog
 } from '../dialog';
@@ -32,7 +36,7 @@ interface IKernelSelection {
   /**
    * The current running sessions.
    */
-  sessions: Session.IModel[];
+  sessions: IterableOrArrayLike<Session.IModel>;
 
   /**
    * The desired kernel language.
@@ -64,7 +68,7 @@ interface IPopulateOptions {
   /**
    * The current running sessions.
    */
-  sessions: Session.IModel[];
+  sessions: IterableOrArrayLike<Session.IModel>;
 
   /**
    * The optional existing kernel model.
@@ -124,14 +128,14 @@ function selectKernel(options: IKernelSelection): Promise<Kernel.IModel> {
  * Change the kernel on a context.
  */
 export
-function selectKernelForContext(context: DocumentRegistry.IContext<DocumentRegistry.IModel>, host?: HTMLElement): Promise<void> {
-  return context.listSessions().then(sessions => {
+function selectKernelForContext(context: DocumentRegistry.IContext<DocumentRegistry.IModel>, manager: Session.IManager, host?: HTMLElement): Promise<void> {
+  return manager.ready().then(() => {
     let options: IKernelSelection = {
       name: context.path.split('/').pop(),
-      specs: context.kernelspecs,
-      sessions,
+      specs: manager.specs,
+      sessions: manager.running(),
       preferredLanguage: context.model.defaultKernelLanguage,
-      kernel: context.kernel.model,
+      kernel: context.kernel ? context.kernel.model : null,
       host
     };
     return selectKernel(options);
@@ -227,7 +231,7 @@ function populateKernels(node: HTMLSelectElement, options: IPopulateOptions): vo
   }
 
   // Handle a preferred kernel language in order of display name.
-  if (preferredLanguage) {
+  if (preferredLanguage && specs) {
     for (let name in specs.kernelspecs) {
       if (languages[name] === preferredLanguage) {
         names.push(name);
@@ -264,40 +268,37 @@ function populateKernels(node: HTMLSelectElement, options: IPopulateOptions): vo
 
   // Add the sessions using the preferred language first.
   let matchingSessions: Session.IModel[] = [];
-  if (preferredLanguage) {
-    for (let session of sessions) {
-      if (languages[session.kernel.name] === preferredLanguage &&
-          session.kernel.id !== existing) {
-        matchingSessions.push(session);
-      }
-    }
-    if (matchingSessions) {
-      matchingSessions.sort((a, b) => {
-        return a.notebook.path.localeCompare(b.notebook.path);
-      });
-      for (let session of matchingSessions) {
-        let name = displayNames[session.kernel.name];
-        node.appendChild(optionForSession(session, name, maxLength));
-      }
-      node.appendChild(createSeparatorOption(maxLength));
-    }
-  }
-  // Add the other remaining sessions.
   let otherSessions: Session.IModel[] = [];
-  for (let session of sessions) {
-    if (matchingSessions.indexOf(session) === -1 &&
+
+  each(sessions, session => {
+    if (preferredLanguage &&
+        languages[session.kernel.name] === preferredLanguage &&
         session.kernel.id !== existing) {
+      matchingSessions.push(session);
+    } else if (session.kernel.id !== existing) {
       otherSessions.push(session);
     }
+  });
+
+  if (matchingSessions) {
+    matchingSessions.sort((a, b) => {
+      return a.notebook.path.localeCompare(b.notebook.path);
+    });
+    each(matchingSessions, session => {
+      let name = displayNames[session.kernel.name];
+      node.appendChild(optionForSession(session, name, maxLength));
+    });
+    node.appendChild(createSeparatorOption(maxLength));
   }
+
   if (otherSessions) {
     otherSessions.sort((a, b) => {
       return a.notebook.path.localeCompare(b.notebook.path);
     });
-    for (let session of otherSessions) {
-      let name = displayNames[session.kernel.name];
+    each(otherSessions, session => {
+      let name = displayNames[session.kernel.name] || session.kernel.name;
       node.appendChild(optionForSession(session, name, maxLength));
-    }
+    });
   }
   node.selectedIndex = 0;
 }

+ 13 - 16
src/docregistry/registry.ts

@@ -2,7 +2,7 @@
 // Distributed under the terms of the Modified BSD License.
 
 import {
-  Contents, Kernel, Session
+  Contents, Kernel
 } from '@jupyterlab/services';
 
 import {
@@ -595,11 +595,6 @@ namespace DocumentRegistry {
      */
     fileChanged: ISignal<this, Contents.IModel>;
 
-    /**
-     * A signal emitted when the context is fully populated for the first time.
-     */
-    populated: ISignal<this, void>;
-
     /**
      * A signal emitted when the context is disposed.
      */
@@ -625,19 +620,26 @@ namespace DocumentRegistry {
      *
      * #### Notes
      * The model will have an empty `contents` field.
-     * It will be `null` until the context is populated.
+     * It will be `null` until the context is ready.
      */
     readonly contentsModel: Contents.IModel;
 
     /**
-     * Get the kernel spec information.
+     * Whether the context is ready.
+     */
+    readonly isReady: boolean;
+
+    /**
+     * A promise that is fulfilled when the context is ready.
      */
-    readonly kernelspecs: Kernel.ISpecModels;
+    ready(): Promise<void>;
 
     /**
-     * Test whether the context is fully populated.
+     * Start the default kernel for the context.
+     *
+     * @returns A promise that resolves with the new kernel.
      */
-    readonly isPopulated: boolean;
+    startDefaultKernel(): Promise<Kernel.IKernel>;
 
     /**
      * Change the current kernel associated with the document.
@@ -697,11 +699,6 @@ namespace DocumentRegistry {
      */
     listCheckpoints(): Promise<Contents.ICheckpointModel[]>;
 
-    /**
-     * Get the list of running sessions.
-     */
-    listSessions(): Promise<Session.IModel[]>;
-
     /**
      * Resolve a url to a correct server path.
      */

+ 0 - 9
src/filebrowser/browser.ts

@@ -105,15 +105,6 @@ class FileBrowser extends Widget {
     });
     this._listing = new DirListing({ manager, model, renderer });
 
-    model.fileChanged.connect((fbModel, args) => {
-      let oldPath = args.oldValue && args.oldValue.path || null;
-      if (args.newValue) {
-        manager.handleRename(oldPath, args.newValue.path);
-      } else {
-        manager.handleDelete(oldPath);
-      }
-    });
-
     this._crumbs.addClass(CRUMBS_CLASS);
     this._buttons.addClass(BUTTON_CLASS);
     this._listing.addClass(LISTING_CLASS);

+ 3 - 16
src/filebrowser/buttons.ts

@@ -158,9 +158,7 @@ class FileButtons extends Widget {
    * @returns A promise that resolves with the created widget.
    */
   createFrom(creatorName: string): Promise<Widget> {
-    return createFromDialog(this.model, this.manager, creatorName).then(widget => {
-      return widget ? this._open(widget) : null;
-    });
+    return createFromDialog(this.model, this.manager, creatorName);
   }
 
   /**
@@ -179,7 +177,7 @@ class FileButtons extends Widget {
     if (!widget) {
       widget = this._manager.open(path, widgetName, kernel);
     }
-    return this._open(widget);
+    return widget;
   }
 
   /**
@@ -194,18 +192,7 @@ class FileButtons extends Widget {
    * @return The widget for the path.
    */
   createNew(path: string, widgetName='default', kernel?: Kernel.IModel): Widget {
-    let widget = this._manager.createNew(path, widgetName, kernel);
-    return this._open(widget);
-  }
-
-  /**
-   * Open a widget and attach listeners.
-   */
-  private _open(widget: Widget): Widget {
-    let context = this._manager.contextForWidget(widget);
-    context.populated.connect(() => this.model.refresh() );
-    context.kernelChanged.connect(() => this.model.refresh() );
-    return widget;
+    return this._manager.createNew(path, widgetName, kernel);
   }
 
   /**

+ 30 - 34
src/filebrowser/dialogs.ts

@@ -6,7 +6,7 @@ import {
 } from '@jupyterlab/services';
 
 import {
-  each
+  IterableOrArrayLike, each
 } from 'phosphor/lib/algorithm/iteration';
 
 import {
@@ -47,7 +47,9 @@ const FILE_CONFLICT_CLASS = 'jp-mod-conflict';
 export
 function createFromDialog(model: FileBrowserModel, manager: DocumentManager, creatorName: string): Promise<Widget> {
   let handler = new CreateFromHandler(model, manager, creatorName);
-  return handler.populate().then(() => {
+  return manager.services.ready().then(() => {
+    return handler.populate();
+  }).then(() => {
     return handler.showDialog();
   });
 }
@@ -59,8 +61,8 @@ function createFromDialog(model: FileBrowserModel, manager: DocumentManager, cre
 export
 function openWithDialog(path: string, manager: DocumentManager, host?: HTMLElement): Promise<Widget> {
   let handler: OpenWithHandler;
-  return manager.listSessions().then(sessions => {
-    handler = new OpenWithHandler(path, manager, sessions);
+  return manager.services.ready().then(() => {
+    handler = new OpenWithHandler(path, manager);
     return showDialog({
       title: 'Open File',
       body: handler.node,
@@ -80,8 +82,8 @@ function openWithDialog(path: string, manager: DocumentManager, host?: HTMLEleme
 export
 function createNewDialog(model: FileBrowserModel, manager: DocumentManager, host?: HTMLElement): Promise<Widget> {
   let handler: CreateNewHandler;
-  return manager.listSessions().then(sessions => {
-    handler = new CreateNewHandler(model, manager, sessions);
+  return manager.services.ready().then(() => {
+    handler = new CreateNewHandler(model, manager);
     return showDialog({
       title: 'Create New File',
       host,
@@ -132,11 +134,9 @@ class OpenWithHandler extends Widget {
   /**
    * Construct a new "open with" dialog.
    */
-  constructor(path: string, manager: DocumentManager, sessions: Session.IModel[], host?: HTMLElement) {
+  constructor(path: string, manager: DocumentManager) {
     super({ node: Private.createOpenWithNode() });
     this._manager = manager;
-    this._host = host;
-    this._sessions = sessions;
 
     this.inputNode.textContent = path;
     this._ext = path.split('.').pop();
@@ -149,9 +149,7 @@ class OpenWithHandler extends Widget {
    * Dispose of the resources used by the widget.
    */
   dispose(): void {
-    this._host = null;
     this._manager = null;
-    this._sessions = null;
     super.dispose();
   }
 
@@ -212,8 +210,8 @@ class OpenWithHandler extends Widget {
     let preference = this._manager.registry.getKernelPreference(
       this._ext, widgetName
     );
-    let specs = this._manager.kernelspecs;
-    let sessions = this._sessions;
+    let specs = this._manager.services.specs;
+    let sessions = this._manager.services.sessions.running();
     Private.updateKernels(this.kernelDropdownNode,
       { preference, specs, sessions }
     );
@@ -221,8 +219,6 @@ class OpenWithHandler extends Widget {
 
   private _ext = '';
   private _manager: DocumentManager = null;
-  private _host: HTMLElement = null;
-  private _sessions: Session.IModel[] = null;
 }
 
 
@@ -323,8 +319,8 @@ class CreateFromHandler extends Widget {
     // Handle the kernel preferences.
     let preference = registry.getKernelPreference(ext, widgetName);
     if (preference.canStartKernel) {
-      let specs = this._manager.kernelspecs;
-      let sessions = this._sessions;
+      let specs = this._manager.services.specs;
+      let sessions = this._manager.services.sessions.running();
       let preferredKernel = kernelName;
       Private.updateKernels(this.kernelDropdownNode,
         { specs, sessions, preferredKernel, preference }
@@ -333,10 +329,7 @@ class CreateFromHandler extends Widget {
       this.node.removeChild(this.kernelDropdownNode);
     }
 
-    return manager.listSessions().then(sessions => {
-      this._sessions = sessions;
-      return model.newUntitled({ ext, type });
-    }).then((contents: Contents.IModel) => {
+    return model.newUntitled({ ext, type }).then(contents => {
       this.inputNode.value = contents.name;
       this._orig = contents;
     });
@@ -372,7 +365,6 @@ class CreateFromHandler extends Widget {
   private _widgetName: string;
   private _orig: Contents.IModel = null;
   private _manager: DocumentManager;
-  private _sessions: Session.IModel[] = [];
 }
 
 
@@ -383,11 +375,10 @@ class CreateNewHandler extends Widget {
   /**
    * Construct a new "create new" dialog.
    */
-  constructor(model: FileBrowserModel, manager: DocumentManager, sessions: Session.IModel[]) {
+  constructor(model: FileBrowserModel, manager: DocumentManager) {
     super({ node: Private.createCreateNewNode() });
     this._model = model;
     this._manager = manager;
-    this._sessions = sessions;
 
     // Create a file name based on the current time.
     let time = new Date();
@@ -418,7 +409,6 @@ class CreateNewHandler extends Widget {
    */
   dispose(): void {
     this._model = null;
-    this._sessions = null;
     this._manager = null;
     super.dispose();
   }
@@ -489,7 +479,7 @@ class CreateNewHandler extends Widget {
     }
     // Update the file type dropdown and the factories.
     if (this._extensions.indexOf(ext) === -1) {
-      this.fileTypeDropdown.value = this._sentinal;
+      this.fileTypeDropdown.value = this._sentinel;
     } else {
       this.fileTypeDropdown.value = ext;
     }
@@ -503,7 +493,7 @@ class CreateNewHandler extends Widget {
     let dropdown = this.fileTypeDropdown;
     let option = document.createElement('option');
     option.text = 'File';
-    option.value = this._sentinal;
+    option.value = this._sentinel;
 
     each(this._manager.registry.fileTypes(), ft => {
       option = document.createElement('option');
@@ -515,7 +505,7 @@ class CreateNewHandler extends Widget {
     if (this.ext in this._extensions) {
       dropdown.value = this.ext;
     } else {
-      dropdown.value = this._sentinal;
+      dropdown.value = this._sentinel;
     }
   }
 
@@ -556,9 +546,10 @@ class CreateNewHandler extends Widget {
   protected widgetDropdownChanged(): void {
     let ext = this.ext;
     let widgetName = this.widgetDropdown.value;
-    let preference = this._manager.registry.getKernelPreference(ext, widgetName);
-    let specs = this._manager.kernelspecs;
-    let sessions = this._sessions;
+    let manager = this._manager;
+    let preference = manager.registry.getKernelPreference(ext, widgetName);
+    let specs = manager.services.specs;
+    let sessions = manager.services.sessions.running();
     Private.updateKernels(this.kernelDropdownNode,
       { preference, sessions, specs }
     );
@@ -566,8 +557,7 @@ class CreateNewHandler extends Widget {
 
   private _model: FileBrowserModel = null;
   private _manager: DocumentManager = null;
-  private _sessions: Session.IModel[] = null;
-  private _sentinal = 'UNKNOWN_EXTENSION';
+  private _sentinel = 'UNKNOWN_EXTENSION';
   private _prevExt = '';
   private _extensions: string[] = [];
 }
@@ -635,11 +625,17 @@ namespace Private {
       node.disabled = true;
       return;
     }
+    // Bail if there are no kernel specs.
+    if (!specs) {
+      return;
+    }
     let preferredLanguage = preference.language;
     node.disabled = false;
+
     populateKernels(node,
       { specs, sessions, preferredLanguage, preferredKernel }
     );
+
     // Select the "null" valued kernel if we do not prefer a kernel.
     if (!preference.preferKernel) {
       node.value = 'null';
@@ -664,7 +660,7 @@ namespace Private {
     /**
      * The running sessions.
      */
-    sessions: Session.IModel[];
+    sessions: IterableOrArrayLike<Session.IModel>;
 
     /**
      * The preferred kernel name.

+ 7 - 9
src/filebrowser/listing.ts

@@ -2,7 +2,7 @@
 // Distributed under the terms of the Modified BSD License.
 
 import {
-  Contents
+  Contents, Kernel
 } from '@jupyterlab/services';
 
 import {
@@ -669,12 +669,16 @@ class DirListing extends Widget {
 
     // Handle notebook session statuses.
     let paths = toArray(map(items, item => item.path));
-    let specs = this._model.kernelspecs;
     each(this._model.sessions(), session => {
       let index = indexOf(paths, session.notebook.path);
       let node = nodes.at(index);
       node.classList.add(RUNNING_CLASS);
-      node.title = specs.kernelspecs[session.kernel.name].display_name;
+      let name = session.kernel.name;
+      let specs = this._model.specs;
+      if (specs) {
+        name = specs.kernelspecs[name].display_name;
+      }
+      node.title = name;
     });
 
     this._prevPath = this._model.path;
@@ -868,9 +872,6 @@ class DirListing extends Widget {
       let widget = this._manager.findWidget(path);
       if (!widget) {
         widget = this._manager.open(item.path);
-        let context = this._manager.contextForWidget(widget);
-        context.populated.connect(() => model.refresh() );
-        context.kernelChanged.connect(() => model.refresh() );
       }
     }
   }
@@ -1002,9 +1003,6 @@ class DirListing extends Widget {
         let widget = this._manager.findWidget(path);
         if (!widget) {
           widget = this._manager.open(item.path);
-          let context = this._manager.contextForWidget(widget);
-          context.populated.connect(() => model.refresh() );
-          context.kernelChanged.connect(() => model.refresh() );
         }
         return widget;
       });

+ 37 - 55
src/filebrowser/model.ts

@@ -6,7 +6,7 @@ import {
 } from '@jupyterlab/services';
 
 import {
-  IIterator, each
+  IterableOrArrayLike, IIterator, each
 } from 'phosphor/lib/algorithm/iteration';
 
 import {
@@ -46,6 +46,7 @@ class FileBrowserModel implements IDisposable, IPathTracker {
     this._manager = options.manager;
     this._model = { path: '', name: '/', type: 'directory' };
     this.cd();
+    this._manager.contents.fileChanged.connect(this._onFileChanged, this);
     this._manager.sessions.runningChanged.connect(this._onRunningChanged, this);
   }
 
@@ -55,14 +56,19 @@ class FileBrowserModel implements IDisposable, IPathTracker {
   pathChanged: ISignal<this, IChangedArgs<string>>;
 
   /**
-   * Get the refreshed signal.
+   * A signal emitted when the directory listing is refreshed.
    */
   refreshed: ISignal<this, void>;
 
+  /**
+   * A signal emitted when the running sessions in the directory changes.
+   */
+  sessionsChanged: ISignal<this, void>;
+
   /**
    * Get the file path changed signal.
    */
-  fileChanged: ISignal<this, IChangedArgs<Contents.IModel>>;
+  fileChanged: ISignal<this, Contents.IChangedArgs>;
 
   /**
    * Get the current path.
@@ -79,10 +85,10 @@ class FileBrowserModel implements IDisposable, IPathTracker {
   }
 
   /**
-   * Get the kernel specs.
+   * Get the kernel spec models.
    */
-  get kernelspecs(): Kernel.ISpecModels {
-    return this._manager.kernelspecs;
+  get specs(): Kernel.ISpecModels | null {
+    return this._manager.sessions.specs;
   }
 
   /**
@@ -145,12 +151,6 @@ class FileBrowserModel implements IDisposable, IPathTracker {
     this._pending = manager.contents.get(newValue, options).then(contents => {
       this._handleContents(contents);
       this._pendingPath = null;
-      return manager.sessions.listRunning();
-    }).then(models => {
-      if (this.isDisposed) {
-        return;
-      }
-      this._onRunningChanged(manager.sessions, models);
       if (oldValue !== newValue) {
         this.pathChanged.emit({
           name: 'path',
@@ -158,6 +158,7 @@ class FileBrowserModel implements IDisposable, IPathTracker {
           newValue
         });
       }
+      this._onRunningChanged(manager.sessions, manager.sessions.running());
       this.refreshed.emit(void 0);
     });
     return this._pending;
@@ -191,14 +192,7 @@ class FileBrowserModel implements IDisposable, IPathTracker {
     let normalizePath = Private.normalizePath;
     fromFile = normalizePath(this._model.path, fromFile);
     toDir = normalizePath(this._model.path, toDir);
-    return this._manager.contents.copy(fromFile, toDir).then(contents => {
-      this.fileChanged.emit({
-        name: 'file',
-        oldValue: void 0,
-        newValue: contents
-      });
-      return contents;
-    });
+    return this._manager.contents.copy(fromFile, toDir);
   }
 
   /**
@@ -211,13 +205,7 @@ class FileBrowserModel implements IDisposable, IPathTracker {
   deleteFile(path: string): Promise<void> {
     let normalizePath = Private.normalizePath;
     path = normalizePath(this._model.path, path);
-    return this._manager.contents.delete(path).then(() => {
-      this.fileChanged.emit({
-        name: 'file',
-        oldValue: { path: path },
-        newValue: void 0
-      });
-    });
+    return this._manager.contents.delete(path);
   }
 
   /**
@@ -248,16 +236,7 @@ class FileBrowserModel implements IDisposable, IPathTracker {
       options.ext = options.ext || '.txt';
     }
     options.path = options.path || this._model.path;
-
-    let promise = this._manager.contents.newUntitled(options);
-    return promise.then((contents: Contents.IModel) => {
-      this.fileChanged.emit({
-        name: 'file',
-        oldValue: void 0,
-        newValue: contents
-      });
-      return contents;
-    });
+    return this._manager.contents.newUntitled(options);
   }
 
   /**
@@ -274,15 +253,7 @@ class FileBrowserModel implements IDisposable, IPathTracker {
     path = Private.normalizePath(this._model.path, path);
     newPath = Private.normalizePath(this._model.path, newPath);
 
-    let promise = this._manager.contents.rename(path, newPath);
-    return promise.then((contents: Contents.IModel) => {
-      this.fileChanged.emit({
-        name: 'file',
-        oldValue: {type: contents.type , path: path},
-        newValue: contents
-      });
-      return contents;
-    });
+    return this._manager.contents.rename(path, newPath);
   }
 
   /**
@@ -361,15 +332,9 @@ class FileBrowserModel implements IDisposable, IPathTracker {
           content: Private.getContent(reader)
         };
 
-        let promise = this._manager.contents.save(path, model);
-        promise.then((contents: Contents.IModel) => {
-          this.fileChanged.emit({
-            name: 'file',
-            oldValue: void 0,
-            newValue: contents
-          });
+        this._manager.contents.save(path, model).then(contents => {
           resolve(contents);
-        });
+        }).catch(reject);
       };
 
       reader.onerror = (event: Event) => {
@@ -405,7 +370,7 @@ class FileBrowserModel implements IDisposable, IPathTracker {
   /**
    * Handle a change to the running sessions.
    */
-  private _onRunningChanged(sender: Session.IManager, models: Session.IModel[]): void {
+  private _onRunningChanged(sender: Session.IManager, models: IterableOrArrayLike<Session.IModel>): void {
     this._sessions.clear();
     each(models, model => {
       if (this._paths.has(model.notebook.path)) {
@@ -415,6 +380,23 @@ class FileBrowserModel implements IDisposable, IPathTracker {
     this.refreshed.emit(void 0);
   }
 
+  /**
+   * Handle a change on the contents manager.
+   */
+  private _onFileChanged(sender: Contents.IManager, change: Contents.IChangedArgs): void {
+    let path = this._model.path || '.';
+    let value = change.oldValue;
+    if (value && value.path && ContentsManager.dirname(value.path) === path) {
+      this.fileChanged.emit(change);
+      return;
+    }
+    value = change.newValue;
+    if (value && value.path && ContentsManager.dirname(value.path) === path) {
+      this.fileChanged.emit(change);
+      return;
+    }
+  }
+
   private _maxUploadSizeMb = 15;
   private _manager: ServiceManager.IManager = null;
   private _sessions = new Vector<Session.IModel>();

+ 1 - 1
src/inspector/handler.ts

@@ -140,7 +140,7 @@ class InspectionHandler implements IDisposable, Inspector.IInspectable {
     };
     let pending = ++this._pending;
 
-    this._kernel.inspect(contents).then(msg => {
+    this._kernel.requestInspect(contents).then(msg => {
       let value = msg.content;
 
       // If handler has been disposed, bail.

+ 24 - 41
src/notebook/notebook/panel.ts

@@ -34,7 +34,7 @@ import {
 } from '../../common/interfaces';
 
 import {
-  DocumentRegistry, findKernel
+  DocumentRegistry
 } from '../../docregistry';
 
 import {
@@ -256,25 +256,6 @@ class NotebookPanel extends Widget {
     this.title.label = path.split('/').pop();
   }
 
-  /**
-   * Handle a context population.
-   */
-  protected onPopulated(sender: DocumentRegistry.IContext<INotebookModel>, args: void): void {
-    let model = sender.model;
-    // Clear the undo state of the cells.
-    if (model) {
-      model.cells.clearUndo();
-    }
-    if (!sender.kernel && model) {
-      let name = findKernel(
-        model.defaultKernelName,
-        model.defaultKernelLanguage,
-        sender.kernelspecs
-      );
-      sender.changeKernel({ name });
-    }
-  }
-
   /**
    * Handle a change in the context.
    */
@@ -282,7 +263,6 @@ class NotebookPanel extends Widget {
     if (oldValue) {
       oldValue.kernelChanged.disconnect(this._onKernelChanged, this);
       oldValue.pathChanged.disconnect(this.onPathChanged, this);
-      oldValue.populated.disconnect(this.onPopulated, this);
       if (oldValue.model) {
         oldValue.model.stateChanged.disconnect(this.onModelStateChanged, this);
       }
@@ -300,11 +280,18 @@ class NotebookPanel extends Widget {
     this._content.model = newValue.model;
     this._handleDirtyState();
     newValue.model.stateChanged.connect(this.onModelStateChanged, this);
-    if (newValue.isPopulated) {
-      this.onPopulated(newValue, void 0);
-    } else {
-      newValue.populated.connect(this.onPopulated, this);
+
+    // Clear the cells when the context is initially populated.
+    if (!newValue.isReady) {
+      newValue.ready().then(() => {
+        let model = newValue.model;
+        // Clear the undo state of the cells.
+        if (model) {
+          model.cells.clearUndo();
+        }
+      });
     }
+
     // Handle the document title.
     this.onPathChanged(context, context.path);
     context.pathChanged.connect(this.onPathChanged, this);
@@ -320,15 +307,11 @@ class NotebookPanel extends Widget {
     if (!this.model || !kernel) {
       return;
     }
-    if (kernel.info) {
-      this._updateLanguage(kernel.info.language_info);
-    } else {
-      kernel.kernelInfo().then(msg => {
-        if (this.model) {
-          this._updateLanguage(msg.content.language_info);
-        }
-      });
-    }
+    kernel.ready().then(() => {
+      if (this.model) {
+        this._updateLanguage(kernel.info.language_info);
+      }
+    });
     this._updateSpec(kernel);
   }
 
@@ -344,13 +327,13 @@ class NotebookPanel extends Widget {
    * Update the kernel spec.
    */
   private _updateSpec(kernel: Kernel.IKernel): void {
-    let specs = this.context.kernelspecs;
-    let spec = specs.kernelspecs[kernel.name];
-    let specCursor = this.model.getMetadata('kernelspec');
-    specCursor.setValue({
-      name: kernel.name,
-      display_name: spec.display_name,
-      language: spec.language
+    kernel.ready().then(() => {
+      let specCursor = this.model.getMetadata('kernelspec');
+      specCursor.setValue({
+        name: kernel.name,
+        display_name: kernel.spec.display_name,
+        language: kernel.spec.language
+      });
     });
   }
 

+ 1 - 1
src/notebook/output-area/model.ts

@@ -191,7 +191,7 @@ class OutputAreaModel implements IDisposable {
     };
     this.clear();
     return new Promise<KernelMessage.IExecuteReplyMsg>((resolve, reject) => {
-      let future = kernel.execute(content);
+      let future = kernel.requestExecute(content);
       // Handle published messages.
       future.onIOPub = (msg: KernelMessage.IIOPubMessage) => {
         let msgType = msg.header.msg_type;

+ 3 - 3
src/notebook/plugin.ts

@@ -167,7 +167,7 @@ function activateNotebookHandler(app: JupyterLab, registry: IDocumentRegistry, s
     widgetName: 'Notebook'
   });
 
-  addCommands(app);
+  addCommands(app, services);
   populatePalette(palette);
 
   let id = 0; // The ID counter for notebook panels.
@@ -191,7 +191,7 @@ function activateNotebookHandler(app: JupyterLab, registry: IDocumentRegistry, s
 /**
  * Add the notebook commands to the application's command registry.
  */
-function addCommands(app: JupyterLab): void {
+function addCommands(app: JupyterLab, services: IServiceManager): void {
   let commands = app.commands;
 
   commands.addCommand(cmdIds.runAndAdvance, {
@@ -509,7 +509,7 @@ function addCommands(app: JupyterLab): void {
       let current = tracker.currentWidget;
       if (current) {
         let { context, node } = current;
-        selectKernelForContext(context, node);
+        selectKernelForContext(context, services.sessions, node);
       }
     }
   });

+ 7 - 15
src/running/index.ts

@@ -111,11 +111,6 @@ const FILE_ICON_CLASS = 'jp-mod-file';
  */
 const TERMINAL_ICON_CLASS = 'jp-mod-terminal';
 
-/**
- * The duration of auto-refresh in ms.
- */
-const REFRESH_DURATION = 10000;
-
 /**
  * A regex for console names.
  */
@@ -217,14 +212,8 @@ class RunningSessions extends Widget {
     let terminals = this._manager.terminals;
     let sessions = this._manager.sessions;
     clearTimeout(this._refreshId);
-    return terminals.listRunning().then(running => {
-      this._onTerminalsChanged(terminals, running);
-      return sessions.listRunning();
-    }).then(running => {
-      this._onSessionsChanged(sessions, running);
-      this._refreshId = setTimeout(() => {
-        this.refresh();
-      }, REFRESH_DURATION);
+    return terminals.refreshRunning().then(() => {
+      return sessions.refreshRunning();
     });
   }
 
@@ -269,7 +258,7 @@ class RunningSessions extends Widget {
     let sessionSection = findElement(this.node, SESSIONS_CLASS);
     let sessionList = findElement(sessionSection, LIST_CLASS);
     let renderer = this._renderer;
-    let kernelspecs = this._manager.kernelspecs.kernelspecs;
+    let specs = this._manager.specs;
 
     // Remove any excess item nodes.
     while (termList.children.length > this._runningTerminals.length) {
@@ -299,7 +288,10 @@ class RunningSessions extends Widget {
     for (let i = 0; i < this._runningSessions.length; i++) {
       let node = sessionList.children[i] as HTMLLIElement;
       let model = this._runningSessions[i];
-      let kernelName = kernelspecs[model.kernel.name].display_name;
+      let kernelName = model.kernel.name;
+      if (specs) {
+        kernelName = specs.kernelspecs[kernelName].display_name;
+      }
       renderer.updateSessionNode(node, model, kernelName);
     }
   }

+ 2 - 2
src/services/plugin.ts

@@ -21,7 +21,7 @@ export
 const servicesProvider: JupyterLabPlugin<IServiceManager> = {
   id: 'jupyter.services.services',
   provides: IServiceManager,
-  activate: (): Promise<IServiceManager> => {
-    return ServiceManager.create() as Promise<IServiceManager>;
+  activate: (): IServiceManager => {
+    return new ServiceManager();
   }
 };

+ 11 - 4
src/terminal/index.ts

@@ -80,10 +80,17 @@ class TerminalWidget extends Widget {
     if (this._session && !this._session.isDisposed) {
       this._session.messageReceived.disconnect(this._onMessage, this);
     }
-    this._session = value;
-    this._session.messageReceived.connect(this._onMessage, this);
-    this.title.label = `Terminal ${this._session.name}`;
-    this._resizeTerminal(-1, -1);
+    this._session = null;
+    if (!value) {
+      return;
+    }
+
+    value.ready().then(() => {
+      this._session = value;
+      this._session.messageReceived.connect(this._onMessage, this);
+      this.title.label = `Terminal ${this._session.name}`;
+      this._resizeTerminal(-1, -1);
+    });
   }
 
   /**

+ 12 - 6
src/terminal/plugin.ts

@@ -1,6 +1,10 @@
 // Copyright (c) Jupyter Development Team.
 // Distributed under the terms of the Modified BSD License.
 
+import {
+  TerminalSession
+} from '@jupyterlab/services';
+
 import {
   InstanceTracker
 } from '../common/instancetracker';
@@ -86,11 +90,13 @@ function activateTerminal(app: JupyterLab, services: IServiceManager, mainMenu:
       term.title.icon = `${LANDSCAPE_ICON_CLASS} ${TERMINAL_ICON_CLASS}`;
       app.shell.addToMainArea(term);
       tracker.add(term);
-      services.terminals.create({ name }).then(session => {
-        term.session = session;
-        // Trigger an update of the running kernels.
-        services.terminals.listRunning();
-      });
+      let promise: Promise<TerminalSession.ISession>;
+      if (name) {
+        promise = services.terminals.connectTo(name);
+      } else {
+        promise = services.terminals.startNew();
+      }
+      promise.then(session => { term.session = session; });
     }
   });
   commands.addCommand(increaseTerminalFontSize, {
@@ -132,7 +138,7 @@ function activateTerminal(app: JupyterLab, services: IServiceManager, mainMenu:
     execute: args => {
       let name = args['name'] as string;
       // Check for a running terminal with the given name.
-      let widget = tracker.find(widget => widget.session.name === name);
+      let widget = tracker.find(value => value.session.name === name);
       if (widget) {
         app.shell.activateMain(widget.id);
       } else {

+ 5 - 6
src/toolbar/kernel.ts

@@ -125,13 +125,12 @@ function updateKernelNameItem(widget: Widget, kernel: Kernel.IKernel): void {
   }
   if (kernel.spec) {
     widget.node.textContent = kernel.spec.display_name;
-  } else {
-    kernel.getSpec().then(spec => {
-      if (!widget.isDisposed) {
-        widget.node.textContent = kernel.spec.display_name;
-      }
-    });
   }
+  kernel.ready().then(() => {
+    if (!widget.isDisposed) {
+      widget.node.textContent = kernel.spec.display_name;
+    }
+  });
 }
 
 

+ 5 - 5
test/src/console/foreign.spec.ts

@@ -150,14 +150,14 @@ describe('console/foreign', () => {
       it('should allow foreign cells to be injected if `true`', done => {
         let code = 'print("#enabled:true")';
         handler.injected.connect(() => { done(); });
-        foreign.kernel.execute({ code, stop_on_error: true });
+        foreign.kernel.requestExecute({ code, stop_on_error: true });
       });
 
       it('should reject foreign cells if `false`', done => {
         let code = 'print("#enabled:false")';
         handler.enabled = false;
         handler.rejected.connect(() => { done(); });
-        foreign.kernel.execute({ code, stop_on_error: true });
+        foreign.kernel.requestExecute({ code, stop_on_error: true });
       });
 
     });
@@ -254,7 +254,7 @@ describe('console/foreign', () => {
         let code = 'print("onIOPubMessage:disabled")';
         handler.enabled = false;
         handler.received.connect(() => { done(); });
-        foreign.kernel.execute({ code, stop_on_error: true });
+        foreign.kernel.requestExecute({ code, stop_on_error: true });
       });
 
       it('should inject relevant cells into the parent', done => {
@@ -265,7 +265,7 @@ describe('console/foreign', () => {
           expect(parent.widgets.length).to.be.greaterThan(0);
           done();
         });
-        foreign.kernel.execute({ code, stop_on_error: true });
+        foreign.kernel.requestExecute({ code, stop_on_error: true });
       });
 
       it('should not reject relevant iopub messages', done => {
@@ -279,7 +279,7 @@ describe('console/foreign', () => {
             done();
           }
         });
-        foreign.kernel.execute({ code, stop_on_error: true });
+        foreign.kernel.requestExecute({ code, stop_on_error: true });
       });
 
     });

+ 2 - 5
test/src/docmanager/savehandler.spec.ts

@@ -27,11 +27,8 @@ describe('docregistry/savehandler', () => {
   let context: Context<DocumentRegistry.IModel>;
   let handler: SaveHandler;
 
-  before((done) => {
-    ServiceManager.create().then(m => {
-      manager = m;
-      done();
-    }).catch(done);
+  before(() => {
+    manager = new ServiceManager();
   });
 
   beforeEach(() => {

+ 2 - 5
test/src/docmanager/widgetmanager.spec.ts

@@ -70,11 +70,8 @@ describe('docmanager/widgetmanager', () => {
     fileExtensions: ['.txt']
   });
 
-  before((done) => {
-    ServiceManager.create().then(m => {
-      services = m;
-      done();
-    }).catch(done);
+  before(() => {
+    services = new ServiceManager();
   });
 
   beforeEach(() => {

+ 42 - 80
test/src/docregistry/context.spec.ts

@@ -26,10 +26,8 @@ describe('docregistry/context', () => {
   let factory = new TextModelFactory();
 
   before((done) => {
-    ServiceManager.create().then(m => {
-      manager = m;
-      done();
-    }).catch(done);
+    manager = new ServiceManager();
+    manager.ready().then(done, done);
   });
 
   describe('Context', () => {
@@ -56,7 +54,7 @@ describe('docregistry/context', () => {
     describe('#kernelChanged', () => {
 
       it('should be emitted when the kernel changes', (done) => {
-        let name = manager.kernelspecs.default;
+        let name = manager.specs.default;
         context.kernelChanged.connect((sender, args) => {
           expect(sender).to.be(context);
           expect(args.name).to.be(name);
@@ -77,7 +75,9 @@ describe('docregistry/context', () => {
           expect(args).to.be('foo');
           done();
         });
-        context.setPath('foo');
+        context.save().then(() => {
+          return manager.contents.rename(context.path, 'foo');
+        }).catch(done);
       });
 
     });
@@ -95,43 +95,34 @@ describe('docregistry/context', () => {
 
     });
 
-    describe('#populated', () => {
+    describe('#isReady', () => {
 
-      it('should be emitted when the file is saved for the first time', (done) => {
-        let count = 0;
-        context.populated.connect((sender, args) => {
-          expect(sender).to.be(context);
-          expect(args).to.be(void 0);
-          count++;
-        });
-        context.save().then(() => {
-          expect(count).to.be(1);
-          return context.save();
-        }).then(() => {
-          expect(count).to.be(1);
+      it('should indicate whether the context is ready', (done) => {
+        expect(context.isReady).to.be(false);
+        context.ready().then(() => {
+          expect(context.isReady).to.be(true);
           done();
         }).catch(done);
+        context.save().catch(done);
+      });
+
+    });
+
+    describe('#ready()', () => {
+
+      it('should resolve when the file is saved for the first time', (done) => {
+        context.ready().then(done, done);
+        context.save().catch(done);
       });
 
-      it('should be emitted when the file is reverted for the first time', (done) => {
+      it('should resolve when the file is reverted for the first time', (done) => {
         manager.contents.save(context.path, {
           type: factory.contentType,
           format: factory.fileFormat,
           content: 'foo'
         });
-        let count = 0;
-        context.populated.connect((sender, args) => {
-          expect(sender).to.be(context);
-          expect(args).to.be(void 0);
-          count++;
-        });
-        context.revert().then(() => {
-          expect(count).to.be(1);
-          return context.revert();
-        }).then(() => {
-          expect(count).to.be(1);
-          done();
-        }).catch(done);
+        context.ready().then(done, done);
+        context.revert().catch(done);
       });
 
     });
@@ -164,7 +155,7 @@ describe('docregistry/context', () => {
       });
 
       it('should be set after switching kernels', (done) => {
-        let name = manager.kernelspecs.default;
+        let name = manager.specs.default;
         context.changeKernel({ name }).then(() => {
           expect(context.kernel.name).to.be(name);
           return context.changeKernel(null);
@@ -190,7 +181,7 @@ describe('docregistry/context', () => {
       });
 
       it('should be set after poulation', (done) => {
-        context.populated.connect(() => {
+        context.ready().then(() => {
           expect(context.contentsModel.name).to.be('foo');
           done();
         });
@@ -199,30 +190,6 @@ describe('docregistry/context', () => {
 
     });
 
-    describe('#kernelspecs', () => {
-
-      it('should be the kernelspecs model', () => {
-        let name = manager.kernelspecs.default;
-        expect(name in manager.kernelspecs.kernelspecs).to.be(true);
-      });
-
-    });
-
-    describe('#isPopulated', () => {
-
-      it('should be false before initial save', () => {
-        expect(context.isPopulated).to.be(false);
-      });
-
-      it('should be true after the initial save', (done) => {
-        context.save().then(() => {
-          expect(context.isPopulated).to.be(true);
-          done();
-        }).catch(done);
-      });
-
-    });
-
     describe('#factoryName', () => {
 
       it('should be the name of the factory used by the context', () => {
@@ -252,10 +219,23 @@ describe('docregistry/context', () => {
 
     });
 
+    describe('#startDefaultKernel()', () => {
+
+      it('should start the default kernel for the context', (done) => {
+        context.save().then(() => {
+          return context.startDefaultKernel();
+        }).then(kernel => {
+          expect(kernel.name).to.be.ok();
+          done();
+        }).catch(done);
+      });
+
+    });
+
     describe('#changeKernel()', () => {
 
       it('should change the kernel instance', (done) => {
-        let name = manager.kernelspecs.default;
+        let name = manager.specs.default;
         context.changeKernel({ name }).then(() => {
           expect(context.kernel.name).to.be(name);
           return context.changeKernel(null);
@@ -265,7 +245,7 @@ describe('docregistry/context', () => {
       });
 
       it('should shut down the session if given `null`', (done) => {
-        let name = manager.kernelspecs.default;
+        let name = manager.specs.default;
         context.changeKernel({ name }).then(() => {
           expect(context.kernel.name).to.be(name);
           return context.changeKernel(null);
@@ -296,6 +276,7 @@ describe('docregistry/context', () => {
 
     });
 
+
     describe('#saveAs()', () => {
 
       it('should save the document to a different path chosen by the user', (done) => {
@@ -400,25 +381,6 @@ describe('docregistry/context', () => {
 
     });
 
-    describe('#listSessions()', () => {
-
-      it('should list the running sessions', (done) => {
-        let name = manager.kernelspecs.default;
-        context.changeKernel({ name }).then(() => {
-          return context.listSessions();
-        }).then(models => {
-          for (let model of models) {
-            if (model.kernel.id === context.kernel.id) {
-              return context.changeKernel(null);
-            }
-          }
-        }).then(() => {
-          done();
-        }).catch(done);
-      });
-
-    });
-
     describe('#resolveUrl()', () => {
 
       it('should resolve a relative url to a correct server path', () => {

+ 2 - 5
test/src/docregistry/default.spec.ts

@@ -37,11 +37,8 @@ describe('docmanager/default', () => {
 
   let context: Context<DocumentRegistry.IModel>;
 
-  beforeEach((done) => {
-    createFileContext().then(c => {
-      context = c;
-      done();
-    });
+  beforeEach(() => {
+    context = createFileContext();
   });
 
   afterEach(() => {

+ 29 - 42
test/src/filebrowser/model.spec.ts

@@ -22,11 +22,8 @@ describe('filebrowser/model', () => {
   let model: FileBrowserModel;
   let name: string;
 
-  before((done) => {
-    ServiceManager.create().then(m => {
-      manager = m;
-      done();
-    });
+  before(() => {
+    manager = new ServiceManager();
   });
 
   beforeEach((done) => {
@@ -94,8 +91,9 @@ describe('filebrowser/model', () => {
       it('should be emitted when a file is created', (done) => {
         model.fileChanged.connect((sender, args) => {
           expect(sender).to.be(model);
-          expect(args.name).to.be('file');
-          expect(args.oldValue).to.be(void 0);
+          console.log(args);
+          expect(args.type).to.be('new');
+          expect(args.oldValue).to.be(null);
           expect(args.newValue.type).to.be('file');
           done();
         });
@@ -105,7 +103,7 @@ describe('filebrowser/model', () => {
       it('should be emitted when a file is renamed', (done) => {
         model.fileChanged.connect((sender, args) => {
           expect(sender).to.be(model);
-          expect(args.name).to.be('file');
+          expect(args.type).to.be('rename');
           expect(args.oldValue.path).to.be(name);
           expect(args.newValue.path).to.be(name + '.bak');
           done();
@@ -113,6 +111,15 @@ describe('filebrowser/model', () => {
         model.rename(name, name + '.bak').catch(done);
       });
 
+      it('should be emitted when a file is created outside of the model', (done) => {
+        model.fileChanged.connect((sender, args) => {
+          done();
+        });
+        manager.contents.newUntitled({ path: 'src' }).then(value => {
+          manager.contents.rename(value.path, name + 'bak');
+        }).catch(done);
+      });
+
     });
 
     describe('#path', () => {
@@ -165,14 +172,6 @@ describe('filebrowser/model', () => {
 
     });
 
-    describe('#kernelspecs', () => {
-
-      it('should get the kernelspecs models', () => {
-        expect(model.kernelspecs.default).to.be(manager.kernelspecs.default);
-      });
-
-    });
-
     describe('#dispose()', () => {
 
       it('should dispose of the resources held by the model', () => {
@@ -239,17 +238,6 @@ describe('filebrowser/model', () => {
         }).catch(done);
       });
 
-      it('should emit a fileChanged signal', (done) => {
-        model.fileChanged.connect((sender, args) => {
-          expect(sender).to.be(model);
-          expect(args.name).to.be('file');
-          expect(args.oldValue).to.be(void 0);
-          expect(args.newValue.path).to.be(`src/${name}`);
-          done();
-        });
-        model.copy(name, 'src').catch(done);
-      });
-
     });
 
     describe('#deleteFile()', () => {
@@ -267,9 +255,9 @@ describe('filebrowser/model', () => {
       it('should emit a fileChanged signal', (done) => {
         model.fileChanged.connect((sender, args) => {
           expect(sender).to.be(model);
-          expect(args.name).to.be('file');
+          expect(args.type).to.be('delete');
           expect(args.oldValue.path).to.be(name);
-          expect(args.newValue).to.be(void 0);
+          expect(args.newValue).to.be(null);
           done();
         });
         model.deleteFile(name).catch(done);
@@ -300,8 +288,8 @@ describe('filebrowser/model', () => {
       it('should emit a fileChanged signal', (done) => {
         model.fileChanged.connect((sender, args) => {
           expect(sender).to.be(model);
-          expect(args.name).to.be('file');
-          expect(args.oldValue).to.be(void 0);
+          expect(args.type).to.be('new');
+          expect(args.oldValue).to.be(null);
           expect(args.newValue.type).to.be('directory');
           done();
         });
@@ -322,7 +310,7 @@ describe('filebrowser/model', () => {
       it('should emit the fileChanged signal', (done) => {
         model.fileChanged.connect((sender, args) => {
           expect(sender).to.be(model);
-          expect(args.name).to.be('file');
+          expect(args.type).to.be('rename');
           expect(args.oldValue.path).to.be(name);
           expect(args.newValue.path).to.be(name + '.new');
           done();
@@ -370,8 +358,8 @@ describe('filebrowser/model', () => {
       it('should emit the fileChanged signal', (done) => {
         model.fileChanged.connect((sender, args) => {
           expect(sender).to.be(model);
-          expect(args.name).to.be('file');
-          expect(args.oldValue).to.be(void 0);
+          expect(args.type).to.be('save');
+          expect(args.oldValue).to.be(null);
           expect(args.newValue.path).to.be('hello3.html');
           done();
         });
@@ -386,18 +374,17 @@ describe('filebrowser/model', () => {
 
       it('should shut down a session by session id', (done) => {
         let length = 0;
-        manager.sessions.listRunning().then(running => {
-          length = running.length;
-          return model.newUntitled({ type: 'notebook' });
-        }).then(contents => {
-          return manager.sessions.startNew({ path: contents.path });
+        let sessions = manager.sessions;
+        length = toArray(sessions.running()).length;
+        model.newUntitled({ type: 'notebook' }).then(contents => {
+          return sessions.startNew({ path: contents.path });
         }).then(session => {
           session.dispose();
           return model.shutdown(session.id);
         }).then(() => {
-          return manager.sessions.listRunning();
-        }).then(running => {
-          expect(running.length).to.be(length);
+          return sessions.refreshRunning();
+        }).then(() => {
+          expect(toArray(sessions.running()).length).to.be(length);
           done();
         }).catch(done);
       });

+ 6 - 8
test/src/markdownwidget/widget.spec.ts

@@ -45,18 +45,16 @@ class LogWidget extends MarkdownWidget {
 }
 
 
-const contextPromise = createFileContext();
-
-
 describe('markdownwidget/widget', () => {
 
   let context: Context<DocumentRegistry.IModel>;
 
-  beforeEach((done) => {
-    contextPromise.then(c => {
-      context = c;
-      done();
-    });
+  beforeEach(() => {
+    context = createFileContext();
+  });
+
+  afterEach(() => {
+    context.dispose();
   });
 
   describe('MarkdownWidgetFactory', () => {

+ 59 - 115
test/src/notebook/notebook/default-toolbar.spec.ts

@@ -4,7 +4,7 @@
 import expect = require('expect.js');
 
 import {
-  Session, utils
+  Kernel
 } from '@jupyterlab/services';
 
 import {
@@ -20,8 +20,8 @@ import {
 } from 'phosphor/lib/ui/widget';
 
 import {
-  Context
-} from '../../../../lib/docregistry/context';
+  Context, DocumentRegistry
+} from '../../../../lib/docregistry';
 
 import {
  CodeCellWidget, MarkdownCellWidget
@@ -72,24 +72,30 @@ import {
  */
 const rendermime = defaultRenderMime();
 const clipboard = new MimeData();
-const sessionPromise = Session.startNew({ path: utils.uuid() });
+
+
+function startKernel(context: DocumentRegistry.IContext<INotebookModel>): Promise<Kernel.IKernel> {
+  let kernel: Kernel.IKernel;
+  return context.save().then(() => {
+    return context.startDefaultKernel();
+  }).then(k => {
+    kernel = k;
+    return kernel.ready();
+  }).then(() => {
+    return kernel;
+  });
+}
 
 
 describe('notebook/notebook/default-toolbar', () => {
 
   let context: Context<INotebookModel>;
 
-  beforeEach((done) => {
-    createNotebookContext().then(c => {
-      context = c;
-      done();
-    });
+  beforeEach(() => {
+    context = createNotebookContext();
   });
 
   afterEach(() => {
-    if (context.kernel) {
-      context.kernel.dispose();
-    }
     context.dispose();
   });
 
@@ -98,15 +104,10 @@ describe('notebook/notebook/default-toolbar', () => {
     let panel: NotebookPanel;
     const renderer = CodeMirrorNotebookPanelRenderer.defaultRenderer;
 
-    beforeEach((done) => {
+    beforeEach(() => {
       panel = new NotebookPanel({ rendermime, clipboard, renderer });
       context.model.fromJSON(DEFAULT_CONTENT);
       panel.context = context;
-      sessionPromise.then(session => {
-        return context.changeKernel({ id: session.kernel.id });
-      }).then(() => {
-        return context.kernel.interrupt();
-      }).then(done, done);
     });
 
     afterEach(() => {
@@ -221,14 +222,16 @@ describe('notebook/notebook/default-toolbar', () => {
         cell.model.outputs.clear();
         next.rendered = false;
         Widget.attach(button, document.body);
-        panel.kernel.statusChanged.connect((sender, status) => {
-          if (status === 'idle' && cell.model.outputs.length > 0) {
-            expect(next.rendered).to.be(true);
-            button.dispose();
-            done();
-          }
+        startKernel(panel.context).then(kernel => {
+          kernel.statusChanged.connect((sender, status) => {
+            if (status === 'idle' && cell.model.outputs.length > 0) {
+              expect(next.rendered).to.be(true);
+              button.dispose();
+              done();
+            }
+          });
+          button.node.click();
         });
-        button.node.click();
       });
 
       it('should have the `\'jp-Notebook-toolbarRun\'` class', () => {
@@ -240,23 +243,6 @@ describe('notebook/notebook/default-toolbar', () => {
 
     describe('#createInterruptButton()', () => {
 
-      it('should interrupt the kernel when clicked', (done) => {
-        let button = createInterruptButton(panel);
-        Widget.attach(button, document.body);
-        let clicked = false;
-        panel.kernel.statusChanged.connect((sender, status) => {
-          if (status === 'idle') {
-            if (!clicked) {
-              button.node.click();
-              clicked = true;
-            } else {
-              button.dispose();
-              done();
-            }
-          }
-        });
-      });
-
       it('should have the `\'jp-Kernel-toolbarInterrupt\'` class', () => {
         let button = createInterruptButton(panel);
         expect(button.hasClass('jp-Kernel-toolbarInterrupt')).to.be(true);
@@ -304,8 +290,7 @@ describe('notebook/notebook/default-toolbar', () => {
       it('should handle a change in context', () => {
         let item = ToolbarItems.createCellTypeItem(panel);
         context.model.fromJSON(DEFAULT_CONTENT);
-        let name = context.kernelspecs.default;
-        context.changeKernel({ name });
+        context.startDefaultKernel();
         panel.context = null;
         panel.content.activeCellIndex++;
         let node = item.node.getElementsByTagName('select')[0];
@@ -317,49 +302,53 @@ describe('notebook/notebook/default-toolbar', () => {
     describe('#createKernelNameItem()', () => {
 
       it('should display the `\'display_name\'` of the kernel', (done) => {
-        return panel.kernel.getSpec().then(spec => {
-          let item = createKernelNameItem(panel);
-          expect(item.node.textContent).to.be(spec.display_name);
+        let item = createKernelNameItem(panel);
+        startKernel(context).then(kernel => {
+          console.log('started kernel');
+          return kernel.ready();
+        }).then(() => {
+          let name = context.kernel.spec.display_name;
+          expect(item.node.textContent).to.be(name);
           done();
-        });
+        }).catch(done);
       });
 
       it('should display `\'No Kernel!\'` if there is no kernel', () => {
-        panel.context = null;
         let item = createKernelNameItem(panel);
         expect(item.node.textContent).to.be('No Kernel!');
       });
 
       it('should handle a change in context', (done) => {
         let item = createKernelNameItem(panel);
-        panel.kernel.getSpec().then(spec => {
+        startKernel(context).then(kernel => {
+          console.log('started kernel');
+          return kernel.ready();
+        }).then(() => {
           panel.context = null;
           expect(item.node.textContent).to.be('No Kernel!');
-        }).then(done, done);
+          done();
+        }).catch(done);
       });
 
     });
 
     describe('#createKernelStatusItem()', () => {
 
+      beforeEach((done) => {
+        startKernel(panel.context).then(() => {
+          done();
+        }).catch(done);
+      });
+
       it('should display a busy status if the kernel status is not idle', (done) => {
         let item = createKernelStatusItem(panel);
         panel.kernel.statusChanged.connect(() => {
-          if (!panel.kernel) {
-            return;
-          }
-          if (panel.kernel.status === 'idle') {
-            expect(item.hasClass('jp-mod-busy')).to.be(false);
-            panel.kernel.interrupt();
-          }
           if (panel.kernel.status === 'busy') {
             expect(item.hasClass('jp-mod-busy')).to.be(true);
             done();
           }
         });
-        if (panel.kernel.status === 'idle') {
-          panel.kernel.interrupt();
-        }
+        panel.kernel.requestExecute({ code: 'a = 1' });
       });
 
       it('should show the current status in the node title', (done) => {
@@ -367,42 +356,12 @@ describe('notebook/notebook/default-toolbar', () => {
         let status = panel.kernel.status;
         expect(item.node.title.toLowerCase()).to.contain(status);
         panel.kernel.statusChanged.connect(() => {
-          if (!panel.kernel) {
-            return;
-          }
-          if (panel.kernel.status === 'idle') {
-            panel.kernel.interrupt();
-          }
           if (panel.kernel.status === 'busy') {
             expect(item.node.title.toLowerCase()).to.contain('busy');
             done();
           }
         });
-        if (panel.kernel.status === 'idle') {
-          panel.kernel.interrupt();
-        }
-      });
-
-      it('should handle a change to the kernel', (done) => {
-        let item = createKernelStatusItem(panel);
-        let name = context.kernelspecs.default;
-        panel.context.changeKernel({ name }).then(() => {
-          panel.kernel.statusChanged.connect(() => {
-            if (!panel.kernel) {
-              return;
-            }
-            if (panel.kernel.status === 'idle') {
-              panel.kernel.interrupt();
-            }
-            if (panel.kernel.status === 'busy') {
-              expect(item.hasClass('jp-mod-busy')).to.be(true);
-              done();
-            }
-          });
-        }).catch(done);
-        if (panel.kernel.status === 'idle') {
-          panel.kernel.interrupt();
-        }
+        panel.kernel.requestExecute({ code: 'a = 1' });
       });
 
       it('should handle a null kernel', (done) => {
@@ -410,32 +369,17 @@ describe('notebook/notebook/default-toolbar', () => {
         panel.context.changeKernel(void 0).then(() => {
           expect(item.node.title).to.be('No Kernel!');
           expect(item.hasClass('jp-mod-busy')).to.be(true);
-        }).then(done, done);
+          done();
+        }).catch(done);
       });
 
-      it('should handle a change to the context', (done) => {
+      it('should handle a change to the context', () => {
         let item = createKernelStatusItem(panel);
-        createNotebookContext().then(c => {
-          context = c;
-          context.model.fromJSON(DEFAULT_CONTENT);
-          panel.context = c;
-          let name = context.kernelspecs.default;
-          return context.changeKernel({ name }).then(() => {
-            panel.kernel.statusChanged.connect(() => {
-              if (!panel.kernel) {
-                return;
-              }
-              if (panel.kernel.status === 'busy') {
-                expect(item.hasClass('jp-mod-busy')).to.be(true);
-                panel.kernel.interrupt();
-              }
-              if (panel.kernel.status === 'idle') {
-                expect(item.hasClass('jp-mod-busy')).to.be(false);
-                done();
-              }
-            });
-          });
-        }).catch(done);
+        context = createNotebookContext();
+        context.model.fromJSON(DEFAULT_CONTENT);
+        panel.context = context;
+        expect(item.node.title).to.be('No Kernel!');
+        expect(item.hasClass('jp-mod-busy')).to.be(true);
       });
 
     });

+ 56 - 47
test/src/notebook/notebook/panel.spec.ts

@@ -3,6 +3,10 @@
 
 import expect = require('expect.js');
 
+import {
+  ServiceManager
+} from '@jupyterlab/services';
+
 import {
   MimeData
 } from 'phosphor/lib/core/mimedata';
@@ -54,7 +58,6 @@ import {
 const rendermime = defaultRenderMime();
 const clipboard = new MimeData();
 const renderer = CodeMirrorNotebookPanelRenderer.defaultRenderer;
-const contextPromise = createNotebookContext();
 
 
 class LogNotebookPanel extends NotebookPanel {
@@ -75,11 +78,6 @@ class LogNotebookPanel extends NotebookPanel {
     super.onPathChanged(sender, path);
     this.methods.push('onPathChanged');
   }
-
-  protected onPopulated(sender: DocumentRegistry.IContext<INotebookModel>, args: void): void {
-    super.onPopulated(sender, args);
-    this.methods.push('onPopulated');
-  }
 }
 
 
@@ -94,16 +92,23 @@ function createPanel(context: Context<INotebookModel>): LogNotebookPanel {
 describe('notebook/notebook/panel', () => {
 
   let context: Context<INotebookModel>;
+  let manager: ServiceManager.IManager;
 
-  beforeEach((done) => {
-    contextPromise.then(c => {
-      context = c;
-      done();
-    });
+  before((done) => {
+    manager = new ServiceManager();
+    manager.ready().then(done, done);
+  });
+
+  beforeEach(() => {
+    context = createNotebookContext('', manager);
+  });
+
+  afterEach(() => {
+    context.dispose();
   });
 
   after(() => {
-    context.kernel.shutdown();
+    manager.dispose();
   });
 
   describe('NotebookPanel', () => {
@@ -157,10 +162,12 @@ describe('notebook/notebook/panel', () => {
         let panel = createPanel(context);
         panel.kernelChanged.connect((sender, args) => {
           expect(sender).to.be(panel);
-          expect(args.name).to.be(context.kernelspecs.default);
+          expect(args.name).to.be.ok();
           done();
         });
-        panel.context.changeKernel({ name: context.kernelspecs.default });
+        panel.context.save().then(() => {
+          return panel.context.startDefaultKernel();
+        }).catch(done);
       });
 
     });
@@ -187,9 +194,11 @@ describe('notebook/notebook/panel', () => {
 
       it('should be the current kernel used by the panel', (done) => {
         let panel = createPanel(context);
-        context.changeKernel({ name: context.kernelspecs.default });
+        context.save().then(() => {
+          return context.startDefaultKernel();
+        }).catch(done);
         context.kernelChanged.connect(() => {
-          expect(panel.kernel.name).to.be(context.kernelspecs.default);
+          expect(panel.kernel.name).to.be.ok();
           done();
         });
       });
@@ -257,6 +266,20 @@ describe('notebook/notebook/panel', () => {
         expect(called).to.be(true);
       });
 
+
+      it('should initialize the model state', (done) => {
+        let panel = new LogNotebookPanel({ rendermime, clipboard, renderer });
+        let model = context.model;
+        model.fromJSON(DEFAULT_CONTENT);
+        expect(model.cells.canUndo).to.be(true);
+        panel.context = context;
+        context.ready().then(() => {
+          expect(model.cells.canUndo).to.be(false);
+          done();
+        });
+        context.save();
+      });
+
     });
 
     describe('#dispose()', () => {
@@ -288,24 +311,6 @@ describe('notebook/notebook/panel', () => {
 
     });
 
-    describe('#onPopulated()', () => {
-
-      it('should initialize the model state', (done) => {
-        let panel = new LogNotebookPanel({ rendermime, clipboard, renderer });
-        let model = context.model;
-        model.fromJSON(DEFAULT_CONTENT);
-        expect(model.cells.canUndo).to.be(true);
-        panel.context = context;
-        context.populated.connect(() => {
-          expect(panel.methods).to.contain('onPopulated');
-          expect(model.cells.canUndo).to.be(false);
-          done();
-        });
-        context.save();
-      });
-
-    });
-
     describe('#onModelStateChanged()', () => {
 
       it('should be called when the model state changes', () => {
@@ -327,13 +332,17 @@ describe('notebook/notebook/panel', () => {
 
     describe('#onPathChanged()', () => {
 
-      // it('should be called when the path changes', () => {
-      //   let panel = createPanel(context);
-      //   panel.methods = [];
-      //   // TODO: add a saveAs mock
-      //   panel.context.setPath('');
-      //   expect(panel.methods).to.contain('onPathChanged');
-      // });
+      it('should be called when the path changes', (done) => {
+        let panel = createPanel(context);
+        panel.methods = [];
+        context.save().then(() => {
+          return manager.contents.rename(context.path, 'foo.ipynb');
+        }).catch(done);
+        context.pathChanged.connect(() => {
+          expect(panel.methods).to.contain('onPathChanged');
+          done();
+        });
+      });
 
       it('should be called when the context changes', () => {
         let panel = new LogNotebookPanel({ rendermime, clipboard, renderer });
@@ -354,8 +363,8 @@ describe('notebook/notebook/panel', () => {
       describe('#createContent()', () => {
 
         it('should create a notebook widget', () => {
-          let renderer = new CodeMirrorNotebookPanelRenderer();
-          expect(renderer.createContent(rendermime)).to.be.a(Notebook);
+          let r = new CodeMirrorNotebookPanelRenderer();
+          expect(r.createContent(rendermime)).to.be.a(Notebook);
         });
 
       });
@@ -363,8 +372,8 @@ describe('notebook/notebook/panel', () => {
       describe('#createToolbar()', () => {
 
         it('should create a notebook toolbar', () => {
-          let renderer = new CodeMirrorNotebookPanelRenderer();
-          expect(renderer.createToolbar()).to.be.a(Toolbar);
+          let r = new CodeMirrorNotebookPanelRenderer();
+          expect(r.createToolbar()).to.be.a(Toolbar);
         });
 
       });
@@ -372,8 +381,8 @@ describe('notebook/notebook/panel', () => {
       describe('#createCompleter()', () => {
 
         it('should create a completer widget', () => {
-          let renderer = new CodeMirrorNotebookPanelRenderer();
-          expect(renderer.createCompleter()).to.be.a(CompleterWidget);
+          let r = new CodeMirrorNotebookPanelRenderer();
+          expect(r.createCompleter()).to.be.a(CompleterWidget);
         });
 
       });

+ 6 - 6
test/src/notebook/notebook/widgetfactory.spec.ts

@@ -40,7 +40,6 @@ import {
 const rendermime = defaultRenderMime();
 const clipboard = new MimeData();
 const renderer = CodeMirrorNotebookPanelRenderer.defaultRenderer;
-const contextPromise = createNotebookContext();
 
 
 function createFactory(): NotebookWidgetFactory {
@@ -58,11 +57,12 @@ describe('notebook/notebook/widgetfactory', () => {
 
   let context: Context<INotebookModel>;
 
-  beforeEach((done) => {
-    contextPromise.then(c => {
-      context = c;
-      done();
-    });
+  beforeEach(() => {
+    context = createNotebookContext();
+  });
+
+  afterEach(() => {
+    context.dispose();
   });
 
   describe('NotebookWidgetFactory', () => {

+ 1 - 1
test/src/notebook/output-area/model.spec.ts

@@ -204,7 +204,7 @@ describe('notebook/output-area/model', () => {
       beforeEach((done) => {
         Kernel.startNew().then(k => {
           kernel = k;
-          return kernel.kernelInfo();
+          return kernel.ready();
         }).then(() => {
           done();
         }).catch(done);

+ 20 - 29
test/src/notebook/tracker.spec.ts

@@ -78,59 +78,50 @@ describe('notebook/tracker', () => {
         expect(tracker.activeCell).to.be(null);
       });
 
-      it('should be the active cell if a tracked notebook has one', (done) => {
+      it('should be the active cell if a tracked notebook has one', () => {
         let tracker = new NotebookTracker();
         let panel = new NotebookPanel({ rendermime, clipboard, renderer});
         tracker.add(panel);
         tracker.sync(panel);
-        createNotebookContext().then(context => {
-          panel.context = context;
-          panel.content.model.fromJSON(DEFAULT_CONTENT);
-          expect(tracker.activeCell).to.be.a(BaseCellWidget);
-          panel.dispose();
-          done();
-        }).catch(done);
+        panel.context = createNotebookContext();
+        panel.content.model.fromJSON(DEFAULT_CONTENT);
+        expect(tracker.activeCell).to.be.a(BaseCellWidget);
+        panel.dispose();
       });
 
     });
 
     describe('#activeCellChanged', () => {
 
-      it('should emit a signal when the active cell changes', (done) => {
+      it('should emit a signal when the active cell changes', () => {
         let tracker = new NotebookTracker();
         let panel = new NotebookPanel({ rendermime, clipboard, renderer });
         let count = 0;
         tracker.activeCellChanged.connect(() => { count++; });
-        createNotebookContext().then(context => {
-          panel.context = context;
-          panel.content.model.fromJSON(DEFAULT_CONTENT);
-          expect(count).to.be(0);
-          tracker.add(panel);
-          tracker.sync(panel);
-          expect(count).to.be(1);
-          panel.content.activeCellIndex = 1;
-          expect(count).to.be(2);
-          panel.dispose();
-          done();
-        }).catch(done);
+        panel.context = createNotebookContext();
+        panel.content.model.fromJSON(DEFAULT_CONTENT);
+        expect(count).to.be(0);
+        tracker.add(panel);
+        tracker.sync(panel);
+        expect(count).to.be(1);
+        panel.content.activeCellIndex = 1;
+        expect(count).to.be(2);
+        panel.dispose();
       });
 
     });
 
     describe('#onCurrentChanged()', () => {
 
-      it('should be called when the active cell changes', (done) => {
+      it('should be called when the active cell changes', () => {
         let tracker = new TestTracker();
         let panel = new NotebookPanel({ rendermime, clipboard, renderer});
         tracker.add(panel);
         tracker.sync(panel);
-        createNotebookContext().then(context => {
-          panel.context = context;
-          panel.content.model.fromJSON(DEFAULT_CONTENT);
-          expect(tracker.methods).to.contain('onCurrentChanged');
-          panel.dispose();
-          done();
-        }).catch(done);
+        panel.context = createNotebookContext();
+        panel.content.model.fromJSON(DEFAULT_CONTENT);
+        expect(tracker.methods).to.contain('onCurrentChanged');
+        panel.dispose();
       });
 
     });

+ 11 - 13
test/src/utils.ts

@@ -54,12 +54,11 @@ function defaultRenderMime(): RenderMime {
  * Create a context for a file.
  */
 export
-function createFileContext(path?: string): Promise<Context<DocumentRegistry.IModel>> {
-  return Private.servicePromise.then(manager => {
-    let factory = Private.textFactory;
-    path = path || utils.uuid() + '.txt';
-    return new Context({ manager, factory, path });
-  });
+function createFileContext(path?: string, manager?: ServiceManager.IManager): Context<DocumentRegistry.IModel> {
+  manager = manager || Private.manager;
+  let factory = Private.textFactory;
+  path = path || utils.uuid() + '.txt';
+  return new Context({ manager, factory, path });
 }
 
 
@@ -67,12 +66,11 @@ function createFileContext(path?: string): Promise<Context<DocumentRegistry.IMod
  * Create a context for a notebook.
  */
 export
-function createNotebookContext(path?: string): Promise<Context<INotebookModel>> {
-  return Private.servicePromise.then(manager => {
-    let factory = Private.notebookFactory;
-    path = path || utils.uuid() + '.ipynb';
-    return new Context({ manager, factory, path });
-  });
+function createNotebookContext(path?: string, manager?: ServiceManager.IManager): Context<INotebookModel> {
+  manager = manager || Private.manager;
+  let factory = Private.notebookFactory;
+  path = path || utils.uuid() + '.ipynb';
+  return new Context({ manager, factory, path });
 }
 
 
@@ -131,7 +129,7 @@ function dismissDialog(host: HTMLElement = document.body): Promise<void> {
  */
 namespace Private {
   export
-  const servicePromise: Promise<ServiceManager.IManager> = ServiceManager.create();
+  const manager = new ServiceManager();
 
   export
   const textFactory = new TextModelFactory();