Ver código fonte

Update jupyter-js-services

Steven Silvester 8 anos atrás
pai
commit
67c3e2bebb

+ 1 - 1
jupyterlab/package.json

@@ -8,7 +8,7 @@
   "dependencies": {
     "es6-promise": "^3.1.2",
     "font-awesome": "^4.6.1",
-    "jupyter-js-services": "^0.12.5",
+    "jupyter-js-services": "^0.13.0",
     "jupyterlab": "file:../",
     "phosphide": "^0.9.5"
   },

+ 1 - 1
package.json

@@ -13,7 +13,7 @@
     "file-loader": "^0.8.5",
     "jquery": "^2.2.0",
     "jquery-ui": "^1.10.5 <1.12",
-    "jupyter-js-services": "^0.12.5",
+    "jupyter-js-services": "^0.14.0",
     "jupyter-js-utils": "^0.4.0",
     "jupyter-js-widgets": "2.0.0-dev.0",
     "marked": "^0.3.5",

+ 8 - 8
src/console/plugin.ts

@@ -1,6 +1,10 @@
 // Copyright (c) Jupyter Development Team.
 // Distributed under the terms of the Modified BSD License.
 
+import {
+  ServiceManager
+} from 'jupyter-js-services';
+
 import {
   ConsolePanel
 } from './widget';
@@ -29,10 +33,6 @@ import {
   Widget
 } from 'phosphor-widget';
 
-import {
-  JupyterServices
-} from '../services/plugin';
-
 
 /**
  * The console extension.
@@ -40,7 +40,7 @@ import {
 export
 const consoleExtension = {
   id: 'jupyter.extensions.console',
-  requires: [JupyterServices, RenderMime],
+  requires: [ServiceManager, RenderMime],
   activate: activateConsole
 };
 
@@ -58,9 +58,9 @@ const CONSOLE_ICON_CLASS = 'jp-ImageConsole';
 /**
  * Activate the console extension.
  */
-function activateConsole(app: Application, services: JupyterServices, rendermime: RenderMime<Widget>): Promise<void> {
+function activateConsole(app: Application, services: ServiceManager, rendermime: RenderMime<Widget>): Promise<void> {
   let tracker = new WidgetTracker<ConsolePanel>();
-  let manager = services.sessionManager;
+  let manager = services.sessions;
 
   // Add the ability to create new consoles for each kernel.
   let specs = services.kernelspecs;
@@ -143,7 +143,7 @@ function activateConsole(app: Application, services: JupyterServices, rendermime
             specs,
             sessions,
             preferredLanguage: lang,
-            kernel: session.kernel,
+            kernel: session.kernel.model,
             host: widget.parent.node
           };
           return selectKernel(options);

+ 2 - 2
src/docregistry/kernelselector.ts

@@ -97,12 +97,12 @@ function selectKernel(options: IKernelSelection): Promise<IKernel.IModel> {
 export
 function selectKernelForContext(context: IDocumentContext<IDocumentModel>, host?: HTMLElement): Promise<void> {
   return context.listSessions().then(sessions => {
-    let options = {
+    let options: IKernelSelection = {
       name: context.path.split('/').pop(),
       specs: context.kernelspecs,
       sessions,
       preferredLanguage: context.model.defaultKernelLanguage,
-      kernel: context.kernel,
+      kernel: context.kernel.model,
       host
     };
     return selectKernel(options);

+ 8 - 8
src/filebrowser/plugin.ts

@@ -1,6 +1,10 @@
 // Copyright (c) Jupyter Development Team.
 // Distributed under the terms of the Modified BSD License.
 
+import {
+  ServiceManager
+} from 'jupyter-js-services';
+
 import {
   IWidgetOpener, FileBrowserWidget
 } from './browser';
@@ -29,10 +33,6 @@ import {
   Widget
 } from 'phosphor-widget';
 
-import {
-  JupyterServices
-} from '../services/plugin';
-
 import {
   WidgetTracker
 } from '../widgettracker';
@@ -44,7 +44,7 @@ import {
 export
 const fileBrowserExtension = {
   id: 'jupyter.extensions.fileBrowser',
-  requires: [JupyterServices, DocumentRegistry],
+  requires: [ServiceManager, DocumentRegistry],
   activate: activateFileBrowser
 };
 
@@ -67,9 +67,9 @@ const TEXTEDITOR_ICON_CLASS = 'jp-ImageTextEditor';
 /**
  * Activate the file browser.
  */
-function activateFileBrowser(app: Application, provider: JupyterServices, registry: DocumentRegistry): Promise<void> {
-  let contents = provider.contentsManager;
-  let sessions = provider.sessionManager;
+function activateFileBrowser(app: Application, provider: ServiceManager, registry: DocumentRegistry): Promise<void> {
+  let contents = provider.contents;
+  let sessions = provider.sessions;
   let id = 0;
 
   let tracker = new WidgetTracker<Widget>();

+ 2 - 26
src/filebrowser/utils.ts

@@ -1,10 +1,6 @@
 // Copyright (c) Jupyter Development Team.
 // Distributed under the terms of the Modified BSD License.
 
-import {
-  hitTest
-} from 'phosphor-domutil';
-
 import {
   Widget
 } from 'phosphor-widget';
@@ -13,6 +9,8 @@ import {
   okButton, showDialog
 } from '../dialog';
 
+export * from '../utils';
+
 
 /**
  * The class name added to FileBrowser instances.
@@ -62,25 +60,3 @@ function showErrorMessage(host: Widget, title: string, error: Error): Promise<vo
   };
   return showDialog(options).then(() => {});
 }
-
-
-/**
- * Get the index of the node at a client position, or `-1`.
- */
-export
-function hitTestNodes(nodes: HTMLElement[] | NodeList, x: number, y: number): number {
-  for (let i = 0, n = nodes.length; i < n; ++i) {
-    if (hitTest(nodes[i] as HTMLElement, x, y)) return i;
-  }
-  return -1;
-}
-
-
-/**
- * Find the first element matching a class name.
- */
-export
-function findElement(parent: HTMLElement, className: string): HTMLElement {
-  let elements = parent.getElementsByClassName(className);
-  if (elements.length) return elements[0] as HTMLElement;
-}

+ 6 - 6
src/landing/plugin.ts

@@ -1,6 +1,10 @@
 // Copyright (c) Jupyter Development Team.
 // Distributed under the terms of the Modified BSD License.
 
+import {
+  ServiceManager
+} from 'jupyter-js-services';
+
 import {
   Application
 } from 'phosphide/lib/core/application';
@@ -9,10 +13,6 @@ import {
   Widget
 } from 'phosphor-widget';
 
-import {
-  JupyterServices
-} from '../services/plugin';
-
 
 /**
  * The landing page extension.
@@ -20,12 +20,12 @@ import {
 export
 const landingExtension = {
   id: 'jupyter.extensions.landing',
-  requires: [JupyterServices],
+  requires: [ServiceManager],
   activate: activateLanding
 };
 
 
-function activateLanding(app: Application, services: JupyterServices): void {
+function activateLanding(app: Application, services: ServiceManager): void {
   let widget = new Widget();
   widget.id = 'landing-jupyterlab';
   widget.title.text = 'JupyterLab';

+ 6 - 6
src/notebook/plugin.ts

@@ -1,6 +1,10 @@
 // Copyright (c) Jupyter Development Team.
 // Distributed under the terms of the Modified BSD License.
 
+import {
+  ServiceManager
+} from 'jupyter-js-services';
+
 import {
   Application
 } from 'phosphide/lib/core/application';
@@ -21,10 +25,6 @@ import {
   RenderMime
 } from '../rendermime';
 
-import {
-  JupyterServices
-} from '../services/plugin';
-
 import {
   WidgetTracker
 } from '../widgettracker';
@@ -103,7 +103,7 @@ class NotebookTracker extends WidgetTracker<NotebookPanel> { }
 export
 const notebookHandlerExtension = {
   id: 'jupyter.extensions.notebookHandler',
-  requires: [DocumentRegistry, JupyterServices, RenderMime, IClipboard],
+  requires: [DocumentRegistry, ServiceManager, RenderMime, IClipboard],
   activate: activateNotebookHandler
 };
 
@@ -124,7 +124,7 @@ const notebookTrackerProvider = {
 /**
  * Activate the notebook handler extension.
  */
-function activateNotebookHandler(app: Application, registry: DocumentRegistry, services: JupyterServices, rendermime: RenderMime<Widget>, clipboard: IClipboard): void {
+function activateNotebookHandler(app: Application, registry: DocumentRegistry, services: ServiceManager, rendermime: RenderMime<Widget>, clipboard: IClipboard): void {
 
   let widgetFactory = new NotebookWidgetFactory(rendermime, clipboard);
   let options: IWidgetFactoryOptions = {

+ 301 - 43
src/running/index.ts

@@ -2,13 +2,29 @@
 // Distributed under the terms of the Modified BSD License.
 
 import {
-  ISession, ITerminalSession
+  IServiceManager, ISession, ITerminalSession
 } from 'jupyter-js-services';
 
+import {
+  hitTest
+} from 'phosphor-domutil';
+
+import {
+  Message
+} from 'phosphor-messaging';
+
+import {
+  ISignal, Signal
+} from 'phosphor-signaling';
+
 import {
   Widget
 } from 'phosphor-widget';
 
+import {
+  hitTestNodes, findElement
+} from '../utils';
+
 
 /**
  * The class name added to a running widget.
@@ -36,29 +52,64 @@ const REFRESH_CLASS = 'jp-RunningSessions-headerRefresh';
 const TERMINALS_CLASS = 'jp-RunningSessions-terminals';
 
 /**
- * The class name added to the running terminal sessions section header.
+ * The class name added to the running kernel sessions section.
  */
-const TERMINALS_HEADER_CLASS = 'jp-RunningSessions-terminalsHeader';
+const SESSIONS_CLASS = 'jp-RunningSessions-sessions';
+
+/**
+ * The class name added to the running sessions section header.
+ */
+const SECTION_HEADER_CLASS = 'jp-RunningSessions-sectionHeader';
+
+/**
+ * The class name added to the running kernel sessions section list.
+ */
+const SESSION_LIST_CLASS = 'jp-RunningSessions-sessionList';
 
 /**
  * The class name added to the running terminal sessions section list.
  */
-const TERMINALS_LIST_CLASS = 'jp-RunningSessions-terminalsList';
+const TERMINAL_LIST_CLASS = 'jp-RunningSessions-terminalList';
 
 /**
- * The class name added to the running kernel sessions section.
+ * The class name added to the running sessions items.
  */
-const SESSIONS_CLASS = 'jp-RunningSessions-sessions';
+const ITEM_CLASS = 'jp-RunningSessions-item';
 
 /**
- * The class name added to the running kernel sessions section header.
+ * The class name added to a running session item icon.
  */
-const SESSIONS_HEADER_CLASS = 'jp-RunningSessions-sessionsHeader';
+const ITEM_ICON_CLASS = 'jp-RunningSessions-itemIcon';
 
 /**
- * The class name added to the running kernel sessions section list.
+ * The class name added to a running session item label.
+ */
+const ITEM_LABEL_CLASS = 'jp-RunningSessions-itemLabel';
+
+/**
+ * The class name added to a running session item kernel name.
+ */
+const KERNEL_NAME_CLASS = 'jp-RunningSessions-itemKernelName';
+
+/**
+ * The class name added to a running session item shutdown button.
+ */
+const SHUTDOWN_BUTTON_CLASS = 'jp-RunningSessions-itemShutdownButton';
+
+/**
+ * The class name added to a notebook icon.
+ */
+const NOTEBOOK_ICON_CLASS = 'jp-mod-notebook';
+
+/**
+ * The class name added to a file icon.
+ */
+const FILE_ICON_CLASS = 'jp-mod-file';
+
+/**
+ * The class name added to a terminal icon.
  */
-const SESSIONS_LIST_CLASS = 'jp-RunningSessions-sessionsList';
+const TERMINAL_ICON_CLASS = 'jp-mod-terminal';
 
 
 /**
@@ -97,28 +148,44 @@ class RunningSessions extends Widget {
    * Construct a new running widget.
    */
   constructor(options: RunningSessions.IOptions) {
-    this._terminals = options.terminalManager;
-    this._sessions = options.sessionManager;
+    super();
+    this._manager = options.manager;
     this._renderer = options.renderer || RunningSessions.defaultRenderer;
     this.addClass(RUNNING_CLASS);
 
-    let termNode = this.node.getElementsByClassName(TERMINALS_CLASS)[0] as HTMLElement;
+    // Populate the terminals section.
+    let termNode = findElement(this.node, TERMINALS_CLASS);
     let termHeader = this._renderer.createTerminalHeaderNode();
-    termHeader.className = TERMINALS_HEADER_CLASS;
+    termHeader.className = SECTION_HEADER_CLASS;
     termNode.appendChild(termHeader);
     let termList = document.createElement('ul');
-    termList.className = TERMINALS_LIST_CLASS;
+    termList.className = TERMINAL_LIST_CLASS;
     termNode.appendChild(termList);
 
-    let sessionNode = this.node.getElementsByClassName(SESSIONS_CLASS)[0] as HTMLElement;
+    // Populate the sessions section.
+    let sessionNode = findElement(this.node, SESSIONS_CLASS);
     let sessionHeader = this._renderer.createSessionHeaderNode();
-    sessionHeader.className = SESSIONS_HEADER_CLASS;
+    sessionHeader.className = SECTION_HEADER_CLASS;
     sessionNode.appendChild(sessionHeader);
     let sessionList = document.createElement('ul');
-    sessionList.className = SESSIONS_LIST_CLASS;
+    sessionList.className = SESSION_LIST_CLASS;
     sessionNode.appendChild(sessionList);
   }
 
+  /**
+   * A signal emitted when a kernel session open is requested.
+   */
+  get sessionOpenRequested(): ISignal<RunningSessions, ISession.IModel> {
+    return Private.sessionOpenRequestedSignal.bind(this);
+  }
+
+  /**
+   * A signal emitted when a terminal session open is requested.
+   */
+  get terminalOpenRequested(): ISignal<RunningSessions, ITerminalSession.IModel> {
+    return Private.terminalOpenRequestedSignal.bind(this);
+  }
+
   /**
    * The renderer used by the running sessions widget.
    *
@@ -129,6 +196,42 @@ class RunningSessions extends Widget {
     return this._renderer;
   }
 
+  /**
+   * Test whether the widget is disposed.
+   *
+   * #### Notes
+   * This is a read-only property.
+   */
+  get isDisposed(): boolean {
+    return this._manager === null;
+  }
+
+  /**
+   * Dispose of the resources used by the widget.
+   */
+  dispose(): void {
+    if (this.isDisposed) {
+      return;
+    }
+    this._manager = null;
+    this._runningSessions = null;
+    this._runningTerminals = null;
+    this._renderer = null;
+  }
+
+  /**
+   * Refresh the widget.
+   */
+  refresh(): Promise<void> {
+    return this._manager.terminals.listRunning().then(running => {
+      this._runningTerminals = running;
+      return this._manager.sessions.listRunning();
+    }).then(running => {
+      this._runningSessions = running;
+      this.update();
+    });
+  }
+
   /**
    * Handle the DOM events for the widget.
    *
@@ -150,7 +253,7 @@ class RunningSessions extends Widget {
    */
   protected onAfterAttach(msg: Message): void {
     this.node.addEventListener('click', this);
-    this.update();
+    this.refresh();
   }
 
   /**
@@ -164,7 +267,43 @@ class RunningSessions extends Widget {
    * A message handler invoked on an `'update-request'` message.
    */
   protected onUpdateRequest(msg: Message): void {
+    // Fetch common variables.
+    let termList = findElement(this.node, TERMINAL_LIST_CLASS);
+    let sessionList = findElement(this.node, SESSION_LIST_CLASS);
+    let renderer = this._renderer;
+    let kernelspecs = this._manager.kernelspecs.kernelspecs;
+
+    // Remove any excess item nodes.
+    while (termList.children.length > this._runningTerminals.length) {
+      termList.removeChild(termList.firstChild);
+    }
+    while (sessionList.children.length > this._runningSessions.length) {
+      sessionList.removeChild(sessionList.firstChild);
+    }
 
+    // Add any missing item nodes.
+    while (termList.children.length < this._runningTerminals.length) {
+      let node = renderer.createTerminalNode();
+      node.classList.add(ITEM_CLASS);
+      termList.appendChild(node);
+    }
+    while (sessionList.children.length < this._runningSessions.length) {
+      let node = renderer.createSessionNode();
+      node.classList.add(ITEM_CLASS);
+      sessionList.appendChild(node);
+    }
+
+    // Populate the nodes.
+    for (let i = 0; i < this._runningTerminals.length; i++) {
+      let node = termList.children[i] as HTMLLIElement;
+      renderer.updateTerminalNode(node, this._runningTerminals[i]);
+    }
+    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].spec.display_name;
+      renderer.updateSessionNode(node, model, kernelName);
+    }
   }
 
   /**
@@ -174,12 +313,55 @@ class RunningSessions extends Widget {
    * This listener is attached to the document node.
    */
   private _evtClick(event: MouseEvent): void {
+    // Fetch common variables.
+    let termList = findElement(this.node, TERMINAL_LIST_CLASS);
+    let sessionList = findElement(this.node, SESSION_LIST_CLASS);
+    let refresh = findElement(this.node, REFRESH_CLASS);
+    let renderer = this._renderer;
+    let clientX = event.clientX;
+    let clientY = event.clientY;
+
+    // Check for a refresh.
+    if (hitTest(refresh, clientX, clientY)) {
+      this.refresh();
+      return;
+    }
 
+    // Check for a terminal item click.
+    let index = hitTestNodes(termList.children, clientX, clientY);
+    if (index !== -1) {
+      let node = termList.children[index] as HTMLLIElement;
+      let shutdown = renderer.getTerminalShutdown(node);
+      let model = this._runningTerminals[index];
+      if (hitTest(shutdown, clientX, clientY)) {
+        this._manager.terminals.shutdown(model.name).then(() => {
+          this.refresh();
+        });
+        return;
+      }
+      this.terminalOpenRequested.emit(model);
+    }
+
+    // Check for a session item click.
+    index = hitTestNodes(sessionList.children, clientX, clientY);
+    if (index !== -1) {
+      let node = sessionList.children[index] as HTMLLIElement;
+      let shutdown = renderer.getSessionShutdown(node);
+      let model = this._runningSessions[index];
+      if (hitTest(shutdown, clientX, clientY)) {
+        this._manager.sessions.shutdown(model.id).then(() => {
+          this.refresh();
+        });
+        return;
+      }
+      this.sessionOpenRequested.emit(model);
+    }
   }
 
-  private _terminals: ITerminalSession.IManager = null;
-  private _sessions: ISession.IManager = null;
+  private _manager: IServiceManager = null;
   private _renderer: RunningSessions.IRenderer = null;
+  private _runningSessions: ISession.IModel[] = [];
+  private _runningTerminals: ITerminalSession.IModel[] = [];
 }
 
 
@@ -194,14 +376,9 @@ namespace RunningSessions {
   export
   interface IOptions {
     /**
-     * A terminal session manager instance.
+     * A service manager instance.
      */
-    terminalManager: ITerminalSession.IManager;
-
-    /**
-     * A kernel session manager instance.
-     */
-    sessionManager: ISession.IManager;
+    manager: IServiceManager;
 
     /**
      * The renderer for the running sessions widget.
@@ -228,7 +405,7 @@ namespace RunningSessions {
      *
      * @returns A new node for a running kernel session header.
      */
-    createSessionsHeaderNode(): HTMLElement;
+    createSessionHeaderNode(): HTMLElement;
 
     /**
      * Create a node for a running terminal session item.
@@ -265,7 +442,7 @@ namespace RunningSessions {
      * A click on this node is considered a shutdown request.
      * A click anywhere else on the node is considered an open request.
      */
-    getTerminalShutdown(): HTMLElement;
+    getTerminalShutdown(node: HTMLLIElement): HTMLElement;
 
     /**
      * Get the shutdown node for a session node.
@@ -278,7 +455,7 @@ namespace RunningSessions {
      * A click on this node is considered a shutdown request.
      * A click anywhere else on the node is considered an open request.
      */
-    getSessionShutdown(): HTMLElement;
+    getSessionShutdown(node: HTMLLIElement): HTMLElement;
 
     /**
      * Populate a node with running terminal session data.
@@ -291,7 +468,7 @@ namespace RunningSessions {
      * This method should completely reset the state of the node to
      * reflect the data for the session models.
      */
-    updateTerminalNode(node: HTMLLIElement, models: ITerminalSession.IModels): void;
+    updateTerminalNode(node: HTMLLIElement, model: ITerminalSession.IModel): void;
 
     /**
      * Populate a node with running kernel session data.
@@ -300,11 +477,13 @@ namespace RunningSessions {
      *
      * @param models - The list of kernel session models.
      *
+     * @param kernelName - The kernel display name.
+     *
      * #### Notes
      * This method should completely reset the state of the node to
      * reflect the data for the session models.
      */
-    updateTerminalNode(node: HTMLLIElement, models: ISession.IModels): void;
+    updateSessionNode(node: HTMLLIElement, model: ISession.IModel, kernelName: string): void;
   }
 
 
@@ -313,6 +492,28 @@ namespace RunningSessions {
    */
   export
   class Renderer implements IRenderer {
+    /**
+     * Create a fully populated header node for the terminals section.
+     *
+     * @returns A new node for a running terminal session header.
+     */
+    createTerminalHeaderNode(): HTMLElement {
+      let node = document.createElement('div');
+      node.textContent = 'Terminal Sessions';
+      return node;
+    }
+
+    /**
+     * Create a fully populated header node for the sessions section.
+     *
+     * @returns A new node for a running kernel session header.
+     */
+    createSessionHeaderNode(): HTMLElement {
+      let node = document.createElement('div');
+      node.textContent = 'Kernel Sessions';
+      return node;
+    }
+
     /**
      * Create a node for a running terminal session item.
      *
@@ -324,7 +525,18 @@ namespace RunningSessions {
      * The `updateTerminalNode` method will be called for initialization.
      */
     createTerminalNode(): HTMLLIElement {
-
+      let node = document.createElement('li');
+      let icon = document.createElement('span');
+      icon.className = `${ITEM_ICON_CLASS} ${TERMINAL_ICON_CLASS}`;
+      let label = document.createElement('span');
+      label.className = ITEM_LABEL_CLASS;
+      let shutdown = document.createElement('span');
+      shutdown.className = SHUTDOWN_BUTTON_CLASS;
+
+      node.appendChild(icon);
+      node.appendChild(label);
+      node.appendChild(shutdown);
+      return node;
     }
 
     /**
@@ -338,7 +550,22 @@ namespace RunningSessions {
      * The `updateSessionNode` method will be called for initialization.
      */
     createSessionNode(): HTMLLIElement {
-
+      let node = document.createElement('li');
+      let icon = document.createElement('span');
+      icon.className = ITEM_ICON_CLASS;
+      let label = document.createElement('span');
+      label.className = ITEM_LABEL_CLASS;
+      let kernel = document.createElement('span');
+      kernel.className = KERNEL_NAME_CLASS;
+      let shutdown = document.createElement('span');
+      shutdown.className = SHUTDOWN_BUTTON_CLASS;
+      shutdown.textContent = 'SHUTDOWN';
+
+      node.appendChild(icon);
+      node.appendChild(label);
+      node.appendChild(kernel);
+      node.appendChild(shutdown);
+      return node;
     }
 
     /**
@@ -352,8 +579,8 @@ namespace RunningSessions {
      * A click on this node is considered a shutdown request.
      * A click anywhere else on the node is considered an open request.
      */
-    getTerminalShutdown(): HTMLElement {
-
+    getTerminalShutdown(node: HTMLLIElement): HTMLElement {
+      return findElement(node, SHUTDOWN_BUTTON_CLASS);
     }
 
     /**
@@ -367,9 +594,10 @@ namespace RunningSessions {
      * A click on this node is considered a shutdown request.
      * A click anywhere else on the node is considered an open request.
      */
-    getSessionShutdown(): HTMLElement {
-
+    getSessionShutdown(node: HTMLLIElement): HTMLElement {
+      return findElement(node, SHUTDOWN_BUTTON_CLASS);
     }
+
     /**
      * Populate a node with running terminal session data.
      *
@@ -381,8 +609,9 @@ namespace RunningSessions {
      * This method should completely reset the state of the node to
      * reflect the data for the session models.
      */
-    updateTerminalNode(node: HTMLLIElement, models: ITerminalSession.IModels): void {
-
+    updateTerminalNode(node: HTMLLIElement, model: ITerminalSession.IModel): void {
+      let label = findElement(node, ITEM_LABEL_CLASS);
+      label.textContent = `terminals/${model.name}`;
     }
 
     /**
@@ -392,12 +621,23 @@ namespace RunningSessions {
      *
      * @param models - The list of kernel session models.
      *
+     * @param kernelName - The kernel display name.
+     *
      * #### Notes
      * This method should completely reset the state of the node to
      * reflect the data for the session models.
      */
-    updateTerminalNode(node: HTMLLIElement, models: ISession.IModels): void {
-
+    updateSessionNode(node: HTMLLIElement, model: ISession.IModel, kernelName: string): void {
+      let icon = findElement(node, ITEM_ICON_CLASS);
+      if (model.notebook.path.indexOf('.ipynb') !== -1) {
+        icon.className = `${ITEM_ICON_CLASS} ${NOTEBOOK_ICON_CLASS}`;
+      } else {
+        icon.className = `${ITEM_ICON_CLASS} ${FILE_ICON_CLASS}`;
+      }
+      let label = findElement(node, ITEM_LABEL_CLASS);
+      label.textContent = model.notebook.path.split('/').pop();
+      let kernel = findElement(node, KERNEL_NAME_CLASS);
+      kernel.textContent = kernelName;
     }
   }
 
@@ -407,3 +647,21 @@ namespace RunningSessions {
   export
   const defaultRenderer = new Renderer();
 }
+
+
+/**
+ * The namespace for the private module data.
+ */
+namespace Private {
+  /**
+   * A signal emitted when a kernel session open is requested.
+   */
+  export
+  const sessionOpenRequestedSignal = new Signal<RunningSessions, ISession.IModel>();
+
+  /**
+   * A signal emitted when a terminal session open is requested.
+   */
+  export
+  const terminalOpenRequestedSignal = new Signal<RunningSessions, ITerminalSession.IModel>();
+}

+ 3 - 86
src/services/plugin.ts

@@ -2,87 +2,9 @@
 // Distributed under the terms of the Modified BSD License.
 
 import {
-  IAjaxSettings, IContents, IKernel, ISession, ITerminalSession,
-  ContentsManager, KernelManager, SessionManager, TerminalManager,
-  getKernelSpecs
+  ServiceManager, createServiceManager
 } from 'jupyter-js-services';
 
-import {
-  getBaseUrl, getConfigOption
-} from 'jupyter-js-utils';
-
-
-/**
- * An implementation of a services provider.
- */
-export
-class JupyterServices {
-  /**
-   * Construct a new services provider.
-   */
-  constructor(baseUrl: string, ajaxSettings: IAjaxSettings, specs: IKernel.ISpecModels) {
-    let options = { baseUrl, ajaxSettings };
-    this._kernelspecs = specs;
-    this._kernelManager = new KernelManager(options);
-    this._sessionManager = new SessionManager(options);
-    this._contentsManager = new ContentsManager(options);
-    this._terminalManager = new TerminalManager(options);
-  }
-
-  /**
-   * Get kernel specs.
-   */
-  get kernelspecs(): IKernel.ISpecModels {
-    return this._kernelspecs;
-  }
-
-  /**
-   * Get kernel manager instance.
-   *
-   * #### Notes
-   * This is a read-only property.
-   */
-  get kernelManager(): IKernel.IManager {
-    return this._kernelManager;
-  }
-
-  /**
-   * Get the session manager instance.
-   *
-   * #### Notes
-   * This is a read-only property.
-   */
-  get sessionManager(): ISession.IManager {
-    return this._sessionManager;
-  }
-
-  /**
-   * Get the contents manager instance.
-   *
-   * #### Notes
-   * This is a read-only property.
-   */
-  get contentsManager(): IContents.IManager {
-    return this._contentsManager;
-  }
-
-  /**
-   * Get the terminal manager instance.
-   *
-   * #### Notes
-   * This is a read-only property.
-   */
-  get terminalManager(): ITerminalSession.IManager {
-    return this._terminalManager;
-  }
-
-  private _kernelManager: KernelManager = null;
-  private _sessionManager: SessionManager = null;
-  private _contentsManager: ContentsManager = null;
-  private _terminalManager: TerminalManager = null;
-  private _kernelspecs: IKernel.ISpecModels = null;
-}
-
 
 /**
  * The default services provider.
@@ -90,13 +12,8 @@ class JupyterServices {
 export
 const servicesProvider = {
   id: 'jupyter.services.services',
-  provides: JupyterServices,
+  provides: ServiceManager,
   resolve: () => {
-    let baseUrl = getBaseUrl();
-    let ajaxSettings = getConfigOption('ajaxSettings');
-    let options = { baseUrl, ajaxSettings };
-    return getKernelSpecs(options).then(specs => {
-      return new JupyterServices(baseUrl, ajaxSettings, specs);
-    });
+    return createServiceManager() as Promise<ServiceManager>;
   }
 };

+ 7 - 7
src/terminal/plugin.ts

@@ -2,12 +2,12 @@
 // Distributed under the terms of the Modified BSD License.
 
 import {
-  Application
-} from 'phosphide/lib/core/application';
+  ServiceManager
+} from 'jupyter-js-services';
 
 import {
-  JupyterServices
-} from '../services/plugin';
+  Application
+} from 'phosphide/lib/core/application';
 
 import {
   WidgetTracker
@@ -24,7 +24,7 @@ import {
 export
 const terminalExtension = {
   id: 'jupyter.extensions.terminal',
-  requires: [JupyterServices],
+  requires: [ServiceManager],
   activate: activateTerminal
 };
 
@@ -39,7 +39,7 @@ const LANDSCAPE_ICON_CLASS = 'jp-MainAreaLandscapeIcon';
 const TERMINAL_ICON_CLASS = 'jp-ImageTerminal';
 
 
-function activateTerminal(app: Application, services: JupyterServices): void {
+function activateTerminal(app: Application, services: ServiceManager): void {
 
   let newTerminalId = 'terminal:create-new';
   let increaseTerminalFontSize = 'terminal:increase-font';
@@ -62,7 +62,7 @@ function activateTerminal(app: Application, services: JupyterServices): void {
         term.title.icon = `${LANDSCAPE_ICON_CLASS} ${TERMINAL_ICON_CLASS}`;
         app.shell.addToMainArea(term);
         tracker.addWidget(term);
-        services.terminalManager.createNew().then(session => {
+        services.terminals.createNew().then(session => {
           term.session = session;
         });
       }

+ 32 - 0
src/utils.ts

@@ -0,0 +1,32 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+
+import {
+  hitTest
+} from 'phosphor-domutil';
+
+
+/**
+ * Get the index of the node at a client position, or `-1`.
+ */
+export
+function hitTestNodes(nodes: HTMLElement[] | NodeList, x: number, y: number): number {
+  for (let i = 0, n = nodes.length; i < n; ++i) {
+    if (hitTest(nodes[i] as HTMLElement, x, y)) {
+      return i;
+    }
+  }
+  return -1;
+}
+
+
+/**
+ * Find the first element matching a class name.
+ */
+export
+function findElement(parent: HTMLElement, className: string): HTMLElement {
+  let elements = parent.getElementsByClassName(className);
+  if (elements.length) {
+    return elements[0] as HTMLElement;
+  }
+}