From 52b86988376e70e9e8294f04f67550c2e3ef039f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=ABl=20Billaud?= Date: Tue, 19 Feb 2019 10:52:26 +0900 Subject: [PATCH 1/4] Expose a running session registry --- packages/running/src/index.tsx | 294 +++++++++++++-------------------- 1 file changed, 112 insertions(+), 182 deletions(-) diff --git a/packages/running/src/index.tsx b/packages/running/src/index.tsx index 00ebfb1d1c38..e6421134fbb8 100644 --- a/packages/running/src/index.tsx +++ b/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'; import '../style/index.css'; @@ -71,71 +66,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. + * The running sessions token. */ -const NOTEBOOK_ICON_CLASS = 'jp-mod-notebook'; +export const IRunningSessionManagers = new Token( + '@jupyterlab/running:IRunningSessionManagers' +); +/* tslint:enable */ /** - * The class name added to a console icon. + * The running interface. */ -const CONSOLE_ICON_CLASS = 'jp-mod-console'; +export interface IRunningSessionManagers { + /** + * Add a running item manager. + * + * @param manager - The running item manager. + * + */ + add(manager: IRunningSessions.IManager): void; + /** + * Return an iterator of managers. + */ + items(): ReadonlyArray; +} -/** - * The class name added to a file icon. - */ -const FILE_ICON_CLASS = 'jp-mod-file'; +export class RunningSessionManagers implements IRunningSessionManagers { + /** + * Add a running item manager. + * + * @param manager - The running item manager. + * + */ + add(manager: IRunningSessions.IManager): void { + this._managers.push(manager); + } -/** - * The class name added to a terminal icon. - */ -const TERMINAL_ICON_CLASS = 'jp-mod-terminal'; + /** + * Return an iterator of launcher items. + */ + items(): ReadonlyArray { + return this._managers; + } -/** - * Props for a Session, with items of type M - */ -type SessionProps = { - // A signal that ttracks when the `open` is clicked on a session item - openRequested: Signal; - manager: { - // 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; - // list the running models. - running(): IIterator; - }; - // called when the shutdown button is pressed on a particular item - shutdown: (model: M) => void; - // optitonal filter that is applied to the items from `runningChanged` - filterRunning?: (model: M) => boolean; - // Name that is shown to the user - name: string; - // Class for the icon - iconClass: (model: M) => string; - // called to determine the label for each item - label: (model: M) => string; - // called to determine the `title` attribute for each item, which is revealed on hover - labelTitle?: (model: M) => string; - // flag that set's whether it should display - available: boolean; -}; - -function Item(props: SessionProps & { model: M }) { - const { model } = props; + private _managers: IRunningSessions.IManager[] = []; +} + +function Item(props: { runningItem: IRunningSessions.IRunningItem }) { + const { runningItem } = props; return (
  • - + props.openRequested.emit(model)} + title={runningItem.labelTitle ? runningItem.labelTitle() : ''} + onClick={() => runningItem.open()} > - {props.label(model)} + {runningItem.label()} @@ -143,32 +135,20 @@ function Item(props: SessionProps & { model: M }) { ); } -function ListView(props: { models: M[] } & SessionProps) { - const { models, ...rest } = props; +function ListView(props: { runningItems: IRunningSessions.IRunningItem[] }) { return (
      - {models.map((m, i) => ( - + {props.runningItems.map((item, i) => ( + ))}
    ); } -function List(props: SessionProps) { - const initialModels = toArray(props.manager.running()); - const filterRunning = props.filterRunning || (_ => true); - function render(models: Array) { - return ; - } - if (!props.available) { - return render(initialModels); - } +function List(props: { manager: IRunningSessions.IManager }) { return ( - - {(sender: any, args: Array) => render(args)} + + {() => } ); } @@ -178,10 +158,10 @@ function List(props: SessionProps) { * * It is specialized for each based on it's props. */ -function Section(props: SessionProps) { +function Section(props: { manager: IRunningSessions.IManager }) { function onShutdown() { showDialog({ - title: `Shutdown All ${props.name} Sessions?`, + title: `Shutdown All ${props.manager.name} Sessions?`, buttons: [Dialog.cancelButton(), Dialog.warnButton({ label: 'SHUTDOWN' })] }).then(result => { if (result.button.accept) { @@ -191,89 +171,41 @@ function Section(props: SessionProps) { } return (
    - {props.available && ( - <> -
    -

    {props.name} Sessions

    - -
    - -
    - -
    - - )} + <> +
    +

    {props.manager.name} Sessions

    + +
    + +
    + +
    +
    ); } -interface IRunningSessionsProps { - manager: ServiceManager.IManager; - sessionOpenRequested: Signal; - terminalOpenRequested: Signal; -} - -function RunningSessionsComponent({ - manager, - sessionOpenRequested, - terminalOpenRequested -}: IRunningSessionsProps) { - const terminalsAvailable = manager.terminals.isAvailable(); - +function RunningSessionsComponent(props: { + managers: IRunningSessionManagers; +}) { return ( <>
    { - if (terminalsAvailable) { - manager.terminals.refreshRunning(); - } - manager.sessions.refreshRunning(); - }} + onClick={() => + props.managers.items().forEach(manager => manager.refreshRunning()) + } />
    -
    `${ITEM_ICON_CLASS} ${TERMINAL_ICON_CLASS}`} - label={m => `terminals/${m.name}`} - available={terminalsAvailable} - shutdown={m => manager.terminals.shutdown(m.name)} - /> -
    - !!((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 => ( +
    + ))} ); } @@ -285,56 +217,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 ( - - ); + return ; } - /** - * A signal emitted when a kernel session open is requested. - */ - get sessionOpenRequested(): ISignal { - 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 { - 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; } - private _sessionOpenRequested = new Signal(this); - private _terminalOpenRequested = new Signal( - 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; } } From 43c5cd48a628522251decc9d62e02e04a3a2cfff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=ABl=20Billaud?= Date: Tue, 19 Feb 2019 10:52:58 +0900 Subject: [PATCH 2/4] Register the running kernel manager --- packages/running-extension/src/index.ts | 115 ++++++++++++++++++++---- 1 file changed, 98 insertions(+), 17 deletions(-) diff --git a/packages/running-extension/src/index.ts b/packages/running-extension/src/index.ts index edfa441e1473..b240e5669c70 100644 --- a/packages/running-extension/src/index.ts +++ b/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 = { +const plugin: JupyterFrontEndPlugin = { 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-DirectionsRunIcon 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') { - app.commands.execute('console:open', { path }); - } else { - app.commands.execute('docmanager:open', { path }); - } - }); + return runningSessionManagers; +} - running.terminalOpenRequested.connect((sender, model) => { - 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; + } } From 55ea6daf0d76f85e8ec67459fb89128e21374309 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=ABl=20Billaud?= Date: Tue, 19 Feb 2019 10:53:12 +0900 Subject: [PATCH 3/4] Register the running terminal manager --- packages/terminal-extension/src/index.ts | 65 +++++++++++++++++++++++- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/packages/terminal-extension/src/index.ts b/packages/terminal-extension/src/index.ts index 1bd786716370..359779669f97 100644 --- a/packages/terminal-extension/src/index.ts +++ b/packages/terminal-extension/src/index.ts @@ -13,14 +13,19 @@ import { MainAreaWidget } from '@jupyterlab/apputils'; +import { TerminalSession } from '@jupyterlab/services'; + import { ILauncher } from '@jupyterlab/launcher'; import { IMainMenu } from '@jupyterlab/mainmenu'; import { ITerminalTracker, Terminal } from '@jupyterlab/terminal'; +import { IRunningSessionManagers, IRunningSessions } from '@jupyterlab/running'; import { ISettingRegistry } from '@jupyterlab/coreutils'; +import { toArray } from '@phosphor/algorithm'; + /** * The command IDs used by the terminal plugin. */ @@ -43,6 +48,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. */ @@ -51,7 +61,13 @@ const plugin: JupyterFrontEndPlugin = { id: '@jupyterlab/terminal-extension:plugin', provides: ITerminalTracker, requires: [ISettingRegistry], - optional: [ICommandPalette, ILauncher, ILayoutRestorer, IMainMenu], + optional: [ + ICommandPalette, + ILauncher, + ILayoutRestorer, + IMainMenu, + IRunningSessionManagers + ], autoStart: true }; @@ -69,7 +85,8 @@ function activate( palette: ICommandPalette | null, launcher: ILauncher | null, restorer: ILayoutRestorer | null, - mainMenu: IMainMenu | null + mainMenu: IMainMenu | null, + runningSessionManagers: IRunningSessionManagers | null ): ITerminalTracker { const { serviceManager } = app; const category = 'Terminal'; @@ -180,6 +197,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', @@ -189,6 +211,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. */ From 3435924ba13c9b5bfa3ed290b83c9b8fe7db9c54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=ABl=20Billaud?= Date: Tue, 19 Feb 2019 10:54:29 +0900 Subject: [PATCH 4/4] Update packages dependencies --- packages/running-extension/package.json | 5 ++++- packages/running-extension/tsconfig.json | 6 ++++++ packages/running/package.json | 4 ++++ packages/running/tsconfig.json | 6 ++++++ packages/terminal-extension/package.json | 5 ++++- packages/terminal-extension/tsconfig.json | 6 ++++++ 6 files changed, 30 insertions(+), 2 deletions(-) diff --git a/packages/running-extension/package.json b/packages/running-extension/package.json index 43debc7b4ac2..981fb91c9ae4 100644 --- a/packages/running-extension/package.json +++ b/packages/running-extension/package.json @@ -31,7 +31,10 @@ }, "dependencies": { "@jupyterlab/application": "^1.0.0-alpha.3", - "@jupyterlab/running": "^1.0.0-alpha.3" + "@jupyterlab/coreutils": "^3.0.0-alpha.3", + "@jupyterlab/running": "^1.0.0-alpha.3", + "@jupyterlab/services": "^4.0.0-alpha.3", + "@phosphor/algorithm": "^1.1.2" }, "devDependencies": { "rimraf": "~2.6.2", diff --git a/packages/running-extension/tsconfig.json b/packages/running-extension/tsconfig.json index b86f01df47b0..63ef53b98914 100644 --- a/packages/running-extension/tsconfig.json +++ b/packages/running-extension/tsconfig.json @@ -9,8 +9,14 @@ { "path": "../application" }, + { + "path": "../coreutils" + }, { "path": "../running" + }, + { + "path": "../services" } ] } diff --git a/packages/running/package.json b/packages/running/package.json index cb13e5717511..bd84a3a8e9b4 100644 --- a/packages/running/package.json +++ b/packages/running/package.json @@ -31,10 +31,14 @@ "watch": "tsc -b --watch" }, "dependencies": { + "@jupyterlab/application": "^1.0.0-alpha.3", "@jupyterlab/apputils": "^1.0.0-alpha.3", "@jupyterlab/coreutils": "^3.0.0-alpha.3", + "@jupyterlab/running": "^1.0.0-alpha.3", "@jupyterlab/services": "^4.0.0-alpha.3", "@phosphor/algorithm": "^1.1.2", + "@phosphor/coreutils": "^1.3.0", + "@phosphor/disposable": "^1.1.2", "@phosphor/signaling": "^1.2.2", "react": "~16.4.2" }, diff --git a/packages/running/tsconfig.json b/packages/running/tsconfig.json index 96596a1f9c8e..9ab2e331b844 100644 --- a/packages/running/tsconfig.json +++ b/packages/running/tsconfig.json @@ -6,12 +6,18 @@ }, "include": ["src/*"], "references": [ + { + "path": "../application" + }, { "path": "../apputils" }, { "path": "../coreutils" }, + { + "path": "" + }, { "path": "../services" } diff --git a/packages/terminal-extension/package.json b/packages/terminal-extension/package.json index b3769f119a5b..b2c01d4cd9ff 100644 --- a/packages/terminal-extension/package.json +++ b/packages/terminal-extension/package.json @@ -36,7 +36,10 @@ "@jupyterlab/coreutils": "^3.0.0-alpha.3", "@jupyterlab/launcher": "^1.0.0-alpha.3", "@jupyterlab/mainmenu": "^1.0.0-alpha.3", - "@jupyterlab/terminal": "^1.0.0-alpha.3" + "@jupyterlab/running": "^1.0.0-alpha.3", + "@jupyterlab/services": "^4.0.0-alpha.3", + "@jupyterlab/terminal": "^1.0.0-alpha.3", + "@phosphor/algorithm": "^1.1.2" }, "devDependencies": { "rimraf": "~2.6.2", diff --git a/packages/terminal-extension/tsconfig.json b/packages/terminal-extension/tsconfig.json index 6d8f1c5034be..8d4c41e0a659 100644 --- a/packages/terminal-extension/tsconfig.json +++ b/packages/terminal-extension/tsconfig.json @@ -21,6 +21,12 @@ { "path": "../mainmenu" }, + { + "path": "../running" + }, + { + "path": "../services" + }, { "path": "../terminal" }