Selaa lähdekoodia

Merge onto latest master

Max Ehrlich 5 vuotta sitten
vanhempi
commit
ed8bea1eb5

+ 4 - 1
packages/running-extension/package.json

@@ -36,7 +36,10 @@
   },
   "dependencies": {
     "@jupyterlab/application": "^1.0.1",
-    "@jupyterlab/running": "^1.0.1"
+    "@jupyterlab/coreutils": "^3.0.0",
+    "@jupyterlab/running": "^1.0.1",
+    "@jupyterlab/services": "^4.0.1",
+    "@phosphor/algorithm": "^1.1.3"
   },
   "devDependencies": {
     "rimraf": "~2.6.2",

+ 98 - 17
packages/running-extension/src/index.ts

@@ -6,15 +6,39 @@ import {
   JupyterFrontEnd,
   JupyterFrontEndPlugin
 } from '@jupyterlab/application';
+import {
+  IRunningSessions,
+  IRunningSessionManagers,
+  RunningSessionManagers,
+  RunningSessions
+} from '@jupyterlab/running';
+
+import { Session } from '@jupyterlab/services';
+import { PathExt } from '@jupyterlab/coreutils';
+import { toArray } from '@phosphor/algorithm';
+
+/**
+ * The class name added to a notebook icon.
+ */
+const NOTEBOOK_ICON_CLASS = 'jp-mod-notebook';
+
+/**
+ * The class name added to a console icon.
+ */
+const CONSOLE_ICON_CLASS = 'jp-mod-console';
 
-import { RunningSessions } from '@jupyterlab/running';
+/**
+ * The class name added to a file icon.
+ */
+const FILE_ICON_CLASS = 'jp-mod-file';
 
 /**
  * The default running sessions extension.
  */
-const plugin: JupyterFrontEndPlugin<void> = {
+const plugin: JupyterFrontEndPlugin<IRunningSessionManagers> = {
   activate,
   id: '@jupyterlab/running-extension:plugin',
+  provides: IRunningSessionManagers,
   optional: [ILayoutRestorer],
   autoStart: true
 };
@@ -30,8 +54,9 @@ export default plugin;
 function activate(
   app: JupyterFrontEnd,
   restorer: ILayoutRestorer | null
-): void {
-  let running = new RunningSessions({ manager: app.serviceManager });
+): IRunningSessionManagers {
+  let runningSessionManagers = new RunningSessionManagers();
+  let running = new RunningSessions(runningSessionManagers);
   running.id = 'jp-running-sessions';
   running.title.iconClass = 'jp-RunningIcon jp-SideBar-tabIcon';
   running.title.caption = 'Running Terminals and Kernels';
@@ -42,21 +67,77 @@ function activate(
   if (restorer) {
     restorer.add(running, 'running-sessions');
   }
+  addKernelRunningSessionManager(runningSessionManagers, app);
+  // Rank has been chosen somewhat arbitrarily to give priority to the running
+  // sessions widget in the sidebar.
+  app.shell.add(running, 'left', { rank: 200 });
 
-  running.sessionOpenRequested.connect((sender, model) => {
-    let path = model.path;
-    if (model.type.toLowerCase() === 'console') {
-      void app.commands.execute('console:open', { path });
-    } else {
-      void app.commands.execute('docmanager:open', { path });
-    }
-  });
+  return runningSessionManagers;
+}
 
-  running.terminalOpenRequested.connect((sender, model) => {
-    void app.commands.execute('terminal:open', { name: model.name });
+/**
+ * Add the running kernel manager (notebooks & consoles) to the running panel.
+ */
+function addKernelRunningSessionManager(
+  managers: IRunningSessionManagers,
+  app: JupyterFrontEnd
+) {
+  let manager = app.serviceManager.sessions;
+  function filterSessions(m: Session.IModel) {
+    return !!(
+      (m.name || PathExt.basename(m.path)).indexOf('.') !== -1 || m.name
+    );
+  }
+
+  managers.add({
+    name: 'Kernel',
+    running: () => {
+      return toArray(manager.running())
+        .filter(filterSessions)
+        .map(model => new RunningKernel(model));
+    },
+    shutdownAll: () => manager.shutdownAll(),
+    refreshRunning: () => manager.refreshRunning(),
+    runningChanged: manager.runningChanged
   });
 
-  // Rank has been chosen somewhat arbitrarily to give priority to the running
-  // sessions widget in the sidebar.
-  app.shell.add(running, 'left', { rank: 200 });
+  class RunningKernel implements IRunningSessions.IRunningItem {
+    constructor(model: Session.IModel) {
+      this._model = model;
+    }
+    open() {
+      let { path, type } = this._model;
+      if (type.toLowerCase() === 'console') {
+        app.commands.execute('console:open', { path });
+      } else {
+        app.commands.execute('docmanager:open', { path });
+      }
+    }
+    shutdown() {
+      return manager.shutdown(this._model.id);
+    }
+    iconClass() {
+      let { name, path, type } = this._model;
+      if ((name || PathExt.basename(path)).indexOf('.ipynb') !== -1) {
+        return NOTEBOOK_ICON_CLASS;
+      } else if (type.toLowerCase() === 'console') {
+        return CONSOLE_ICON_CLASS;
+      }
+      return FILE_ICON_CLASS;
+    }
+    label() {
+      return this._model.name || PathExt.basename(this._model.path);
+    }
+    labelTitle() {
+      let { kernel, path } = this._model;
+      let kernelName = kernel.name;
+      if (manager.specs) {
+        const spec = manager.specs.kernelspecs[kernelName];
+        kernelName = spec ? spec.display_name : 'unknown';
+      }
+      return `Path: ${path}\nKernel: ${kernelName}`;
+    }
+
+    private _model: Session.IModel;
+  }
 }

+ 6 - 0
packages/running-extension/tsconfig.json

@@ -9,8 +9,14 @@
     {
       "path": "../application"
     },
+    {
+      "path": "../coreutils"
+    },
     {
       "path": "../running"
+    },
+    {
+      "path": "../services"
     }
   ]
 }

+ 2 - 0
packages/running/package.json

@@ -39,6 +39,8 @@
     "@jupyterlab/coreutils": "^3.0.0",
     "@jupyterlab/services": "^4.0.1",
     "@phosphor/algorithm": "^1.1.3",
+    "@phosphor/coreutils": "^1.3.1",
+    "@phosphor/disposable": "^1.2.0",
     "@phosphor/signaling": "^1.2.3",
     "react": "~16.8.4"
   },

+ 106 - 215
packages/running/src/index.tsx

@@ -3,10 +3,7 @@
 
 import * as React from 'react';
 
-import { IIterator, toArray } from '@phosphor/algorithm';
-
-import { ISignal, Signal } from '@phosphor/signaling';
-
+import { ISignal } from '@phosphor/signaling';
 import { ReactWidget, UseSignal } from '@jupyterlab/apputils';
 
 import {
@@ -15,9 +12,7 @@ import {
   ToolbarButtonComponent
 } from '@jupyterlab/apputils';
 
-import { PathExt } from '@jupyterlab/coreutils';
-
-import { ServiceManager, Session, TerminalSession } from '@jupyterlab/services';
+import { Token } from '@phosphor/coreutils';
 
 /**
  * The class name added to a running widget.
@@ -69,107 +64,68 @@ const ITEM_LABEL_CLASS = 'jp-RunningSessions-itemLabel';
  */
 const SHUTDOWN_BUTTON_CLASS = 'jp-RunningSessions-itemShutdown';
 
+/* tslint:disable */
 /**
- * The class name added to a notebook icon.
- */
-const NOTEBOOK_ICON_CLASS = 'jp-mod-notebook';
-
-/**
- * The class name added to a console icon.
- */
-const CONSOLE_ICON_CLASS = 'jp-mod-console';
-
-/**
- * The class name added to a file icon.
+ * The running sessions token.
  */
-const FILE_ICON_CLASS = 'jp-mod-file';
+export const IRunningSessionManagers = new Token<IRunningSessionManagers>(
+  '@jupyterlab/running:IRunningSessionManagers'
+);
+/* tslint:enable */
 
 /**
- * The class name added to a terminal icon.
+ * The running interface.
  */
-const TERMINAL_ICON_CLASS = 'jp-mod-terminal';
-
-/**
- * Properties for a session list displaying items of generic type `M`.
- */
-type SessionProps<M> = {
-  /**
-   * A signal that tracks when the `open` is clicked on a session item.
-   */
-  openRequested: Signal<RunningSessions, M>;
-
-  /**
-   * The session manager.
-   */
-  manager: {
-    /**
-     * The function called when the shutdown all button is pressed.
-     */
-    shutdownAll(): void;
-
-    /**
-     * A signal that should emit a new list of items whenever they are changed.
-     */
-    runningChanged: ISignal<any, M[]>;
-
-    /**
-     * Returns a list the running models.
-     */
-    running(): IIterator<M>;
-  };
-
+export interface IRunningSessionManagers {
   /**
-   * The function called when the shutdown button is pressed on an item.
+   * Add a running item manager.
+   *
+   * @param manager - The running item manager.
+   *
    */
-  shutdown: (model: M) => void;
-
-  /**
-   * The filter that is applied to the items from `runningChanged`.
-   */
-  filterRunning?: (model: M) => boolean;
-
-  /**
-   * The name displayed to the user.
-   */
-  name: string;
-
+  add(manager: IRunningSessions.IManager): void;
   /**
-   * Returns the icon class for an item.
+   * Return an iterator of managers.
    */
-  iconClass: (model: M) => string;
+  items(): ReadonlyArray<IRunningSessions.IManager>;
+}
 
+export class RunningSessionManagers implements IRunningSessionManagers {
   /**
-   * Returns the label for an item.
+   * Add a running item manager.
+   *
+   * @param manager - The running item manager.
+   *
    */
-  label: (model: M) => string;
+  add(manager: IRunningSessions.IManager): void {
+    this._managers.push(manager);
+  }
 
   /**
-   * Called to determine the `title` attribute for each item, which is revealed
-   * on hover.
+   * Return an iterator of launcher items.
    */
-  labelTitle?: (model: M) => string;
+  items(): ReadonlyArray<IRunningSessions.IManager> {
+    return this._managers;
+  }
 
-  /**
-   * Flag that sets whether it sessions should be displayed.
-   */
-  available: boolean;
-};
+  private _managers: IRunningSessions.IManager[] = [];
+}
 
-function Item<M>(props: SessionProps<M> & { model: M }) {
-  const { model } = props;
+function Item(props: { runningItem: IRunningSessions.IRunningItem }) {
+  const { runningItem } = props;
   return (
     <li className={ITEM_CLASS}>
-      <span className={`${ITEM_ICON_CLASS} ${props.iconClass(model)}`} />
+      <span className={`${ITEM_ICON_CLASS} ${runningItem.iconClass()}`} />
       <span
         className={ITEM_LABEL_CLASS}
-        title={props.labelTitle ? props.labelTitle(model) : ''}
-        onClick={() => props.openRequested.emit(model)}
+        title={runningItem.labelTitle ? runningItem.labelTitle() : ''}
+        onClick={() => runningItem.open()}
       >
-        {props.label(model)}
+        {runningItem.label()}
       </span>
       <button
         className={`${SHUTDOWN_BUTTON_CLASS} jp-mod-styled`}
-        onClick={() => props.shutdown(model)}
+        onClick={() => runningItem.shutdown()}
       >
         SHUT&nbsp;DOWN
       </button>
@@ -177,32 +133,20 @@ function Item<M>(props: SessionProps<M> & { model: M }) {
   );
 }
 
-function ListView<M>(props: { models: M[] } & SessionProps<M>) {
-  const { models, ...rest } = props;
+function ListView(props: { runningItems: IRunningSessions.IRunningItem[] }) {
   return (
     <ul className={LIST_CLASS}>
-      {models.map((m, i) => (
-        <Item key={i} model={m} {...rest} />
+      {props.runningItems.map((item, i) => (
+        <Item key={i} runningItem={item} />
       ))}
     </ul>
   );
 }
 
-function List<M>(props: SessionProps<M>) {
-  const initialModels = toArray(props.manager.running());
-  const filterRunning = props.filterRunning || (_ => true);
-  function render(models: Array<M>) {
-    return <ListView models={models.filter(filterRunning)} {...props} />;
-  }
-  if (!props.available) {
-    return render(initialModels);
-  }
+function List(props: { manager: IRunningSessions.IManager }) {
   return (
-    <UseSignal
-      signal={props.manager.runningChanged}
-      initialArgs={initialModels}
-    >
-      {(sender: any, args: Array<M>) => render(args)}
+    <UseSignal signal={props.manager.runningChanged}>
+      {() => <ListView runningItems={props.manager.running()} />}
     </UseSignal>
   );
 }
@@ -213,14 +157,11 @@ function List<M>(props: SessionProps<M>) {
  *
  * It is specialized for each based on it's props.
  */
-function Section<M>(props: SessionProps<M>) {
+function Section(props: { manager: IRunningSessions.IManager }) {
   function onShutdown() {
-    void showDialog({
-      title: `Shut Down All ${props.name} Sessions?`,
-      buttons: [
-        Dialog.cancelButton(),
-        Dialog.warnButton({ label: 'Shut Down All' })
-      ]
+    showDialog({
+      title: `Shutdown All ${props.manager.name} Sessions?`,
+      buttons: [Dialog.cancelButton(), Dialog.warnButton({ label: 'SHUTDOWN' })]
     }).then(result => {
       if (result.button.accept) {
         props.manager.shutdownAll();
@@ -229,89 +170,41 @@ function Section<M>(props: SessionProps<M>) {
   }
   return (
     <div className={SECTION_CLASS}>
-      {props.available && (
-        <>
-          <header className={SECTION_HEADER_CLASS}>
-            <h2>{props.name} Sessions</h2>
-            <ToolbarButtonComponent
-              tooltip={`Shut Down All ${props.name} Sessions…`}
-              iconClassName="jp-CloseIcon"
-              onClick={onShutdown}
-            />
-          </header>
-
-          <div className={CONTAINER_CLASS}>
-            <List {...props} />
-          </div>
-        </>
-      )}
+      <>
+        <header className={SECTION_HEADER_CLASS}>
+          <h2>{props.manager.name} Sessions</h2>
+          <ToolbarButtonComponent
+            tooltip={`Shutdown All ${props.manager.name} Sessions…`}
+            iconClassName="jp-CloseIcon jp-Icon jp-Icon-16"
+            onClick={onShutdown}
+          />
+        </header>
+
+        <div className={CONTAINER_CLASS}>
+          <List manager={props.manager} />
+        </div>
+      </>
     </div>
   );
 }
 
-interface IRunningSessionsProps {
-  manager: ServiceManager.IManager;
-  sessionOpenRequested: Signal<RunningSessions, Session.IModel>;
-  terminalOpenRequested: Signal<RunningSessions, TerminalSession.IModel>;
-}
-
-function RunningSessionsComponent({
-  manager,
-  sessionOpenRequested,
-  terminalOpenRequested
-}: IRunningSessionsProps) {
-  const terminalsAvailable = manager.terminals.isAvailable();
-
+function RunningSessionsComponent(props: {
+  managers: IRunningSessionManagers;
+}) {
   return (
     <>
       <div className={HEADER_CLASS}>
         <ToolbarButtonComponent
           tooltip="Refresh List"
-          iconClassName="jp-RefreshIcon"
-          onClick={() => {
-            if (terminalsAvailable) {
-              void manager.terminals.refreshRunning();
-            }
-            void manager.sessions.refreshRunning();
-          }}
+          iconClassName="jp-RefreshIcon jp-Icon jp-Icon-16"
+          onClick={() =>
+            props.managers.items().forEach(manager => manager.refreshRunning())
+          }
         />
       </div>
-      <Section
-        openRequested={terminalOpenRequested}
-        manager={manager.terminals}
-        name="Terminal"
-        iconClass={() => `${ITEM_ICON_CLASS} ${TERMINAL_ICON_CLASS}`}
-        label={m => `terminals/${m.name}`}
-        available={terminalsAvailable}
-        shutdown={m => manager.terminals.shutdown(m.name)}
-      />
-      <Section
-        openRequested={sessionOpenRequested}
-        manager={manager.sessions}
-        filterRunning={m =>
-          !!((m.name || PathExt.basename(m.path)).indexOf('.') !== -1 || m.name)
-        }
-        name="Kernel"
-        iconClass={m => {
-          if ((m.name || PathExt.basename(m.path)).indexOf('.ipynb') !== -1) {
-            return NOTEBOOK_ICON_CLASS;
-          } else if (m.type.toLowerCase() === 'console') {
-            return CONSOLE_ICON_CLASS;
-          }
-          return FILE_ICON_CLASS;
-        }}
-        label={m => m.name || PathExt.basename(m.path)}
-        available={true}
-        labelTitle={m => {
-          let kernelName = m.kernel.name;
-          if (manager.specs) {
-            const spec = manager.specs.kernelspecs[kernelName];
-            kernelName = spec ? spec.display_name : 'unknown';
-          }
-          return `Path: ${m.path}\nKernel: ${kernelName}`;
-        }}
-        shutdown={m => manager.sessions.shutdown(m.id)}
-      />
+      {props.managers.items().map(manager => (
+        <Section key={manager.name} manager={manager} />
+      ))}
     </>
   );
 }
@@ -323,56 +216,54 @@ export class RunningSessions extends ReactWidget {
   /**
    * Construct a new running widget.
    */
-  constructor(options: RunningSessions.IOptions) {
+  constructor(managers: IRunningSessionManagers) {
     super();
-    this.options = options;
+    this.managers = managers;
 
     // this can't be in the react element, because then it would be too nested
     this.addClass(RUNNING_CLASS);
   }
 
   protected render() {
-    return (
-      <RunningSessionsComponent
-        manager={this.options.manager}
-        sessionOpenRequested={this._sessionOpenRequested}
-        terminalOpenRequested={this._terminalOpenRequested}
-      />
-    );
+    return <RunningSessionsComponent managers={this.managers} />;
   }
 
-  /**
-   * A signal emitted when a kernel session open is requested.
-   */
-  get sessionOpenRequested(): ISignal<this, Session.IModel> {
-    return this._sessionOpenRequested;
-  }
+  private managers: IRunningSessionManagers;
+}
 
+/**
+ * The namespace for the `IRunningSessions` class statics.
+ */
+export namespace IRunningSessions {
   /**
-   * A signal emitted when a terminal session open is requested.
+   * A manager of running items grouped under a single section.
    */
-  get terminalOpenRequested(): ISignal<this, TerminalSession.IModel> {
-    return this._terminalOpenRequested;
+  export interface IManager {
+    // Name that is shown to the user
+    name: string;
+    // called when the shutdown all button is pressed
+    shutdownAll(): void;
+    // list the running models.
+    running(): IRunningItem[];
+    // Force a refresh of the running models.
+    refreshRunning(): void;
+    // A signal that should be emitted when the item list has changed.
+    runningChanged: ISignal<any, any>;
   }
 
-  private _sessionOpenRequested = new Signal<this, Session.IModel>(this);
-  private _terminalOpenRequested = new Signal<this, TerminalSession.IModel>(
-    this
-  );
-  private options: RunningSessions.IOptions;
-}
-
-/**
- * The namespace for the `RunningSessions` class statics.
- */
-export namespace RunningSessions {
   /**
-   * An options object for creating a running sessions widget.
+   * A running item.
    */
-  export interface IOptions {
-    /**
-     * A service manager instance.
-     */
-    manager: ServiceManager.IManager;
+  export interface IRunningItem {
+    // called when the running item is clicked
+    open: () => void;
+    // called when the shutdown button is pressed on a particular item
+    shutdown: () => void;
+    // Class for the icon
+    iconClass: () => string;
+    // called to determine the label for each item
+    label: () => string;
+    // called to determine the `title` attribute for each item, which is revealed on hover
+    labelTitle?: () => string;
   }
 }

+ 3 - 0
packages/terminal-extension/package.json

@@ -41,7 +41,10 @@
     "@jupyterlab/coreutils": "^3.0.0",
     "@jupyterlab/launcher": "^1.0.1",
     "@jupyterlab/mainmenu": "^1.0.1",
+    "@jupyterlab/running": "^1.0.1",
+    "@jupyterlab/services": "^4.0.1",
     "@jupyterlab/terminal": "^1.0.1",
+    "@phosphor/algorithm": "^1.1.3",
     "@phosphor/widgets": "^1.8.0"
   },
   "devDependencies": {

+ 58 - 2
packages/terminal-extension/src/index.ts

@@ -16,17 +16,22 @@ import {
   WidgetTracker
 } from '@jupyterlab/apputils';
 
+import { TerminalSession } from '@jupyterlab/services';
+
 import { ILauncher } from '@jupyterlab/launcher';
 
 import { IFileMenu, IMainMenu } from '@jupyterlab/mainmenu';
 
 import { ITerminalTracker, ITerminal } from '@jupyterlab/terminal';
+import { IRunningSessionManagers, IRunningSessions } from '@jupyterlab/running';
 
 // Name-only import so as to not trigger inclusion in main bundle
 import * as WidgetModuleType from '@jupyterlab/terminal/lib/widget';
 
 import { Menu } from '@phosphor/widgets';
 
+import { toArray } from '@phosphor/algorithm';
+
 /**
  * The command IDs used by the terminal plugin.
  */
@@ -49,6 +54,11 @@ namespace CommandIDs {
  */
 const TERMINAL_ICON_CLASS = 'jp-TerminalIcon';
 
+/**
+ * The class name added to a running session item icon.
+ */
+const ITEM_ICON_CLASS = 'jp-RunningSessions-itemIcon';
+
 /**
  * The default terminal extension.
  */
@@ -62,7 +72,8 @@ const plugin: JupyterFrontEndPlugin<ITerminalTracker> = {
     ILauncher,
     ILayoutRestorer,
     IMainMenu,
-    IThemeManager
+    IThemeManager,
+    IRunningSessionManagers
   ],
   autoStart: true
 };
@@ -82,7 +93,8 @@ function activate(
   launcher: ILauncher | null,
   restorer: ILayoutRestorer | null,
   mainMenu: IMainMenu | null,
-  themeManager: IThemeManager
+  themeManager: IThemeManager | null,
+  runningSessionManagers: IRunningSessionManagers | null
 ): ITerminalTracker {
   const { serviceManager, commands } = app;
   const category = 'Terminal';
@@ -248,6 +260,11 @@ function activate(
     });
   }
 
+  // Add a sessions manager if the running extension is available
+  if (runningSessionManagers) {
+    addRunningSessionManager(runningSessionManagers, app);
+  }
+
   app.contextMenu.addItem({
     command: CommandIDs.refresh,
     selector: '.jp-Terminal',
@@ -257,6 +274,45 @@ function activate(
   return tracker;
 }
 
+/**
+ * Add the running terminal manager to the running panel.
+ */
+function addRunningSessionManager(
+  managers: IRunningSessionManagers,
+  app: JupyterFrontEnd
+) {
+  let manager = app.serviceManager.terminals;
+
+  managers.add({
+    name: 'Terminal',
+    running: () =>
+      toArray(manager.running()).map(model => new RunningTerminal(model)),
+    shutdownAll: () => manager.shutdownAll(),
+    refreshRunning: () => manager.refreshRunning(),
+    runningChanged: manager.runningChanged
+  });
+
+  class RunningTerminal implements IRunningSessions.IRunningItem {
+    constructor(model: TerminalSession.IModel) {
+      this._model = model;
+    }
+    open() {
+      app.commands.execute('terminal:open', { name: this._model.name });
+    }
+    iconClass() {
+      return `${ITEM_ICON_CLASS} ${TERMINAL_ICON_CLASS}`;
+    }
+    label() {
+      return `terminals/${this._model.name}`;
+    }
+    shutdown() {
+      return manager.shutdown(this._model.name);
+    }
+
+    private _model: TerminalSession.IModel;
+  }
+}
+
 /**
  * Add the commands for the terminal.
  */

+ 6 - 0
packages/terminal-extension/tsconfig.json

@@ -22,6 +22,12 @@
     {
       "path": "../mainmenu"
     },
+    {
+      "path": "../running"
+    },
+    {
+      "path": "../services"
+    },
     {
       "path": "../terminal"
     }