From ab0ee41a86776943a46dd98a617a5f260e34e336 Mon Sep 17 00:00:00 2001 From: Jeremy Tuloup Date: Wed, 9 Dec 2020 20:13:57 +0100 Subject: [PATCH 1/4] Add terminal-extension --- builder/index.js | 3 ++- builder/package.json | 1 + builder/style.css | 1 + jupyterlab_classic/app.py | 1 + yarn.lock | 43 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 48 insertions(+), 1 deletion(-) diff --git a/builder/index.js b/builder/index.js index 0bfa6af9c..972cba294 100644 --- a/builder/index.js +++ b/builder/index.js @@ -112,7 +112,8 @@ async function main() { '@jupyterlab-classic/tree-extension:factory' ].includes(id) ), - require('@jupyterlab/running-extension') + require('@jupyterlab/running-extension'), + require('@jupyterlab/terminal-extension') ]); } else if (page === 'notebooks') { mods = mods.concat([ diff --git a/builder/package.json b/builder/package.json index a06fd39f4..0aab09e7b 100644 --- a/builder/package.json +++ b/builder/package.json @@ -27,6 +27,7 @@ "@jupyterlab/rendermime-extension": "^3.0.0-rc.12", "@jupyterlab/running-extension": "^3.0.0-rc.12", "@jupyterlab/shortcuts-extension": "^3.0.0-rc.12", + "@jupyterlab/terminal-extension": "^3.0.0-rc.12", "@jupyterlab/tooltip-extension": "^3.0.0-rc.12", "@jupyterlab/theme-light-extension": "^3.0.0-rc.12", "@jupyterlab/theme-dark-extension": "^3.0.0-rc.12", diff --git a/builder/style.css b/builder/style.css index 4d98db16d..8e96fce99 100644 --- a/builder/style.css +++ b/builder/style.css @@ -8,6 +8,7 @@ @import url('~@jupyterlab/fileeditor/style/index.css'); @import url('~@jupyterlab/tooltip/style/index.css'); @import url('~@jupyterlab/running/style/index.css'); +@import url('~@jupyterlab/terminal/style/index.css'); @import url('~@jupyterlab/codemirror-extension/style/index.css'); @import url('~@jupyterlab/docmanager-extension/style/index.css'); diff --git a/jupyterlab_classic/app.py b/jupyterlab_classic/app.py index d8e06f0cd..dea8b3630 100644 --- a/jupyterlab_classic/app.py +++ b/jupyterlab_classic/app.py @@ -77,6 +77,7 @@ class ClassicTreeHandler(ClassicPageConfigMixin, ExtensionHandlerJinjaMixin, Ext @web.authenticated def get(self, path=None): page_config = self.get_page_config() + page_config['terminalsAvailable'] = self.settings['terminals_available'] return self.write( self.render_template( "tree.html", diff --git a/yarn.lock b/yarn.lock index 4244c057e..0c06bf768 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2339,6 +2339,39 @@ react "^17.0.1" typestyle "^2.0.4" +"@jupyterlab/terminal-extension@^3.0.0-rc.12": + version "3.0.0-rc.13" + resolved "https://registry.yarnpkg.com/@jupyterlab/terminal-extension/-/terminal-extension-3.0.0-rc.13.tgz#e457ddafe23495488852cf0e26e6fbbebbefd677" + integrity sha512-nVmcZzRiD9te3lR8icnJ30HAjXtDXLJwqFS/GhpVV7CUCOxNjyQQirE5Nn4aNGMtp8/7g6syRbKYPpSyeNut7A== + dependencies: + "@jupyterlab/application" "^3.0.0-rc.13" + "@jupyterlab/apputils" "^3.0.0-rc.13" + "@jupyterlab/launcher" "^3.0.0-rc.13" + "@jupyterlab/mainmenu" "^3.0.0-rc.13" + "@jupyterlab/running" "^3.0.0-rc.13" + "@jupyterlab/services" "^6.0.0-rc.13" + "@jupyterlab/settingregistry" "^3.0.0-rc.13" + "@jupyterlab/terminal" "^3.0.0-rc.13" + "@jupyterlab/translation" "^3.0.0-rc.13" + "@jupyterlab/ui-components" "^3.0.0-rc.13" + "@lumino/algorithm" "^1.3.3" + "@lumino/widgets" "^1.16.1" + +"@jupyterlab/terminal@^3.0.0-rc.13": + version "3.0.0-rc.13" + resolved "https://registry.yarnpkg.com/@jupyterlab/terminal/-/terminal-3.0.0-rc.13.tgz#3651a03b96691bae22a0562f6a6a22f2f718ff25" + integrity sha512-n1Wl9HAeGvkpuHJIJpd5JUsrTJtrbzDP7XAdWn+SuITdTPxz5cleT5lAxk5jRukN69r9S9nicLig+WHJgQL8AA== + dependencies: + "@jupyterlab/apputils" "^3.0.0-rc.13" + "@jupyterlab/services" "^6.0.0-rc.13" + "@jupyterlab/translation" "^3.0.0-rc.13" + "@lumino/coreutils" "^1.5.3" + "@lumino/domutils" "^1.2.3" + "@lumino/messaging" "^1.4.3" + "@lumino/widgets" "^1.16.1" + xterm "~4.8.1" + xterm-addon-fit "~0.4.0" + "@jupyterlab/testutils@^3.0.0-rc.12": version "3.0.0-rc.12" resolved "https://registry.yarnpkg.com/@jupyterlab/testutils/-/testutils-3.0.0-rc.12.tgz#accdebf76e700552328913ab2a7261031844776d" @@ -12406,6 +12439,16 @@ xtend@^4.0.1, xtend@~4.0.1: resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== +xterm-addon-fit@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/xterm-addon-fit/-/xterm-addon-fit-0.4.0.tgz#06e0c5d0a6aaacfb009ef565efa1c81e93d90193" + integrity sha512-p4BESuV/g2L6pZzFHpeNLLnep9mp/DkF3qrPglMiucSFtD8iJxtMufEoEJbN8LZwB4i+8PFpFvVuFrGOSpW05w== + +xterm@~4.8.1: + version "4.8.1" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.8.1.tgz#155a1729a43e1a89b406524e22c5634339e39ca1" + integrity sha512-ax91ny4tI5eklqIfH79OUSGE2PUX2rGbwONmB6DfqpyhSZO8/cf++sqiaMWEVCMjACyMfnISW7C3gGMoNvNolQ== + y18n@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4" From ffbaab1e23cb2ab0b0bbea8fabab92c603e40b0c Mon Sep 17 00:00:00 2001 From: Jeremy Tuloup Date: Wed, 9 Dec 2020 20:55:49 +0100 Subject: [PATCH 2/4] Support for opening new terminals via the URL --- builder/index.js | 6 ++- builder/package.json | 1 + jupyterlab_classic/app.py | 17 ++++++ jupyterlab_classic/templates/terminals.html | 36 +++++++++++++ packages/application-extension/style/base.css | 4 ++ packages/application/src/shell.ts | 2 +- packages/terminal-extension/package.json | 52 +++++++++++++++++++ packages/terminal-extension/src/index.ts | 47 +++++++++++++++++ packages/terminal-extension/style/base.css | 0 packages/terminal-extension/style/index.css | 1 + packages/terminal-extension/tsconfig.json | 8 +++ yarn.lock | 2 +- 12 files changed, 173 insertions(+), 3 deletions(-) create mode 100644 jupyterlab_classic/templates/terminals.html create mode 100644 packages/terminal-extension/package.json create mode 100644 packages/terminal-extension/src/index.ts create mode 100644 packages/terminal-extension/style/base.css create mode 100644 packages/terminal-extension/style/index.css create mode 100644 packages/terminal-extension/tsconfig.json diff --git a/builder/index.js b/builder/index.js index 972cba294..88edae74c 100644 --- a/builder/index.js +++ b/builder/index.js @@ -99,6 +99,8 @@ async function main() { ), require('@jupyterlab/rendermime-extension'), require('@jupyterlab/shortcuts-extension'), + // so new terminals can be create from the menu + require('@jupyterlab/terminal-extension'), require('@jupyterlab/theme-light-extension'), require('@jupyterlab/theme-dark-extension') ]; @@ -113,7 +115,7 @@ async function main() { ].includes(id) ), require('@jupyterlab/running-extension'), - require('@jupyterlab/terminal-extension') + require('@jupyterlab-classic/terminal-extension') ]); } else if (page === 'notebooks') { mods = mods.concat([ @@ -139,6 +141,8 @@ async function main() { ['@jupyterlab-classic/tree-extension:factory'].includes(id) ) ]); + } else if (page === 'terminals') { + mods = mods.concat([require('@jupyterlab-classic/terminal-extension')]); } const extension_data = JSON.parse( diff --git a/builder/package.json b/builder/package.json index 0aab09e7b..1192f6792 100644 --- a/builder/package.json +++ b/builder/package.json @@ -14,6 +14,7 @@ "@jupyterlab-classic/application-extension": "^0.1.0", "@jupyterlab-classic/docmanager-extension": "^0.1.0", "@jupyterlab-classic/notebook-extension": "^0.1.0", + "@jupyterlab-classic/terminal-extension": "^0.1.0", "@jupyterlab-classic/tree-extension": "^0.1.0", "@jupyterlab-classic/ui-components": "^0.1.0", "@jupyterlab/apputils-extension": "^3.0.0-rc.12", diff --git a/jupyterlab_classic/app.py b/jupyterlab_classic/app.py index dea8b3630..06ba38530 100644 --- a/jupyterlab_classic/app.py +++ b/jupyterlab_classic/app.py @@ -88,6 +88,21 @@ class ClassicTreeHandler(ClassicPageConfigMixin, ExtensionHandlerJinjaMixin, Ext ) +class ClassicTerminalHandler(ClassicPageConfigMixin, ExtensionHandlerJinjaMixin, ExtensionHandlerMixin, JupyterHandler): + @web.authenticated + def get(self, path=None): + page_config = self.get_page_config() + page_config['terminalsAvailable'] = self.settings['terminals_available'] + return self.write( + self.render_template( + "terminals.html", + base_url=self.base_url, + token=self.settings["token"], + page_config=page_config, + ) + ) + + class ClassicFileHandler(ClassicPageConfigMixin, ExtensionHandlerJinjaMixin, ExtensionHandlerMixin, JupyterHandler): @web.authenticated def get(self, path=None): @@ -101,6 +116,7 @@ class ClassicFileHandler(ClassicPageConfigMixin, ExtensionHandlerJinjaMixin, Ext ) ) + class ClassicNotebookHandler(ClassicPageConfigMixin, ExtensionHandlerJinjaMixin, ExtensionHandlerMixin, JupyterHandler): @web.authenticated def get(self, path=None): @@ -133,6 +149,7 @@ class ClassicApp(NBClassicConfigShimMixin, LabServerApp): self.handlers.append(("/classic/tree(.*)", ClassicTreeHandler)) self.handlers.append(("/classic/notebooks(.*)", ClassicNotebookHandler)) self.handlers.append(("/classic/edit(.*)", ClassicFileHandler)) + self.handlers.append(("/classic/terminals/(.*)", ClassicTerminalHandler)) def initialize_templates(self): super().initialize_templates() diff --git a/jupyterlab_classic/templates/terminals.html b/jupyterlab_classic/templates/terminals.html new file mode 100644 index 000000000..c1d0ed66d --- /dev/null +++ b/jupyterlab_classic/templates/terminals.html @@ -0,0 +1,36 @@ + + + + + + {{page_config['appName'] | e}} - Terminal + + + + {# Copy so we do not modify the page_config with updates. #} + {% set page_config_full = page_config.copy() %} + + {# Set a dummy variable - we just want the side effect of the update. #} + {% set _ = page_config_full.update(baseUrl=base_url, wsUrl=ws_url) %} + + {# Sentinel value to say that we are on the tree page #} + {% set _ = page_config_full.update(classicPage='terminals') %} + + + + + + + + diff --git a/packages/application-extension/style/base.css b/packages/application-extension/style/base.css index 33b4bd84f..1c814d5cd 100644 --- a/packages/application-extension/style/base.css +++ b/packages/application-extension/style/base.css @@ -13,3 +13,7 @@ .jp-Document { height: 100%; } + +.jp-MainAreaWidget { + height: 100%; +} diff --git a/packages/application/src/shell.ts b/packages/application/src/shell.ts index 24ded8f43..8870b9423 100644 --- a/packages/application/src/shell.ts +++ b/packages/application/src/shell.ts @@ -102,7 +102,7 @@ export class ClassicShell extends Widget implements JupyterFrontEnd.IShell { if (area === 'menu') { return this._menuHandler.addWidget(widget, rank); } - if (area === 'main') { + if (area === 'main' || area === undefined) { if (this._main.widgets.length > 0) { // do not add the widget if there is already one // TODO: should the widget be closed? diff --git a/packages/terminal-extension/package.json b/packages/terminal-extension/package.json new file mode 100644 index 000000000..e7df2b7a1 --- /dev/null +++ b/packages/terminal-extension/package.json @@ -0,0 +1,52 @@ +{ + "name": "@jupyterlab-classic/terminal-extension", + "version": "0.1.0", + "description": "JupyterLab Classic - Terminal Extension", + "homepage": "https://github.com/jtpio/jupyterlab-classic", + "bugs": { + "url": "https://github.com/jtpio/jupyterlab-classic/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/jtpio/jupyterlab-classic.git" + }, + "license": "BSD-3-Clause", + "author": "Project Jupyter", + "sideEffects": [ + "style/**/*.css" + ], + "main": "lib/index.js", + "types": "lib/index.d.ts", + "style": "style/index.css", + "directories": { + "lib": "lib/" + }, + "files": [ + "lib/*.d.ts", + "lib/*.js.map", + "lib/*.js", + "schema/*.json", + "style/**/*.css" + ], + "scripts": { + "build": "tsc -b", + "clean": "rimraf lib && rimraf tsconfig.tsbuildinfo", + "docs": "typedoc src", + "prepublishOnly": "npm run build", + "watch": "tsc -b --watch" + }, + "dependencies": { + "@jupyterlab/application": "^3.0.0-rc.12", + "@jupyterlab/terminal": "^3.0.0-rc.12" + }, + "devDependencies": { + "rimraf": "~3.0.0", + "typescript": "~4.0.2" + }, + "publishConfig": { + "access": "public" + }, + "jupyterlab": { + "extension": true + } +} diff --git a/packages/terminal-extension/src/index.ts b/packages/terminal-extension/src/index.ts new file mode 100644 index 000000000..db54b0ecd --- /dev/null +++ b/packages/terminal-extension/src/index.ts @@ -0,0 +1,47 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +import { + IRouter, + JupyterFrontEnd, + JupyterFrontEndPlugin +} from '@jupyterlab/application'; + +/** + * A plugin to terminals in a new tab + */ +const opener: JupyterFrontEndPlugin = { + id: '@jupyterlab-classic/terminal-extension:opener', + requires: [IRouter], + autoStart: true, + activate: (app: JupyterFrontEnd, router: IRouter) => { + const { commands } = app; + const terminalPattern = new RegExp('/terminals/(.*)'); + + const command = 'router:terminal'; + commands.addCommand(command, { + execute: (args: any) => { + const parsed = args as IRouter.ILocation; + const matches = parsed.path.match(terminalPattern); + if (!matches) { + return; + } + const [, name] = matches; + if (!name) { + return; + } + + commands.execute('terminal:open', { name }); + } + }); + + router.register({ command, pattern: terminalPattern }); + } +}; + +/** + * Export the plugins as default. + */ +const plugins: JupyterFrontEndPlugin[] = [opener]; + +export default plugins; diff --git a/packages/terminal-extension/style/base.css b/packages/terminal-extension/style/base.css new file mode 100644 index 000000000..e69de29bb diff --git a/packages/terminal-extension/style/index.css b/packages/terminal-extension/style/index.css new file mode 100644 index 000000000..f5246e666 --- /dev/null +++ b/packages/terminal-extension/style/index.css @@ -0,0 +1 @@ +@import url('./base.css'); diff --git a/packages/terminal-extension/tsconfig.json b/packages/terminal-extension/tsconfig.json new file mode 100644 index 000000000..399b75b7a --- /dev/null +++ b/packages/terminal-extension/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfigbase", + "compilerOptions": { + "outDir": "lib", + "rootDir": "src" + }, + "include": ["src/**/*"] +} diff --git a/yarn.lock b/yarn.lock index 0c06bf768..4fecc1a69 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2357,7 +2357,7 @@ "@lumino/algorithm" "^1.3.3" "@lumino/widgets" "^1.16.1" -"@jupyterlab/terminal@^3.0.0-rc.13": +"@jupyterlab/terminal@^3.0.0-rc.12", "@jupyterlab/terminal@^3.0.0-rc.13": version "3.0.0-rc.13" resolved "https://registry.yarnpkg.com/@jupyterlab/terminal/-/terminal-3.0.0-rc.13.tgz#3651a03b96691bae22a0562f6a6a22f2f718ff25" integrity sha512-n1Wl9HAeGvkpuHJIJpd5JUsrTJtrbzDP7XAdWn+SuITdTPxz5cleT5lAxk5jRukN69r9S9nicLig+WHJgQL8AA== From 93331d44884eecb157150e1390654f9fbefce5cf Mon Sep 17 00:00:00 2001 From: Jeremy Tuloup Date: Wed, 9 Dec 2020 21:20:17 +0100 Subject: [PATCH 3/4] Open terminals in a new tab --- builder/index.js | 7 +++--- packages/application/src/shell.ts | 2 -- packages/terminal-extension/package.json | 4 +++- packages/terminal-extension/src/index.ts | 29 +++++++++++++++++++++++- 4 files changed, 34 insertions(+), 8 deletions(-) diff --git a/builder/index.js b/builder/index.js index 88edae74c..b1e968cff 100644 --- a/builder/index.js +++ b/builder/index.js @@ -72,6 +72,8 @@ async function main() { require('@jupyterlab-classic/application-extension'), require('@jupyterlab-classic/docmanager-extension'), require('@jupyterlab-classic/notebook-extension'), + // to handle opening new tabs after creating a new terminal + require('@jupyterlab-classic/terminal-extension'), // @jupyterlab plugins require('@jupyterlab/apputils-extension').default.filter(({ id }) => @@ -114,8 +116,7 @@ async function main() { '@jupyterlab-classic/tree-extension:factory' ].includes(id) ), - require('@jupyterlab/running-extension'), - require('@jupyterlab-classic/terminal-extension') + require('@jupyterlab/running-extension') ]); } else if (page === 'notebooks') { mods = mods.concat([ @@ -141,8 +142,6 @@ async function main() { ['@jupyterlab-classic/tree-extension:factory'].includes(id) ) ]); - } else if (page === 'terminals') { - mods = mods.concat([require('@jupyterlab-classic/terminal-extension')]); } const extension_data = JSON.parse( diff --git a/packages/application/src/shell.ts b/packages/application/src/shell.ts index 8870b9423..86bb6216f 100644 --- a/packages/application/src/shell.ts +++ b/packages/application/src/shell.ts @@ -105,8 +105,6 @@ export class ClassicShell extends Widget implements JupyterFrontEnd.IShell { if (area === 'main' || area === undefined) { if (this._main.widgets.length > 0) { // do not add the widget if there is already one - // TODO: should the widget be closed? - widget.close(); return; } this._main.addWidget(widget); diff --git a/packages/terminal-extension/package.json b/packages/terminal-extension/package.json index e7df2b7a1..344b33563 100644 --- a/packages/terminal-extension/package.json +++ b/packages/terminal-extension/package.json @@ -37,7 +37,9 @@ }, "dependencies": { "@jupyterlab/application": "^3.0.0-rc.12", - "@jupyterlab/terminal": "^3.0.0-rc.12" + "@jupyterlab/coreutils": "^5.0.0-rc.12", + "@jupyterlab/terminal": "^3.0.0-rc.12", + "@lumino/algorithm": "^1.3.3" }, "devDependencies": { "rimraf": "~3.0.0", diff --git a/packages/terminal-extension/src/index.ts b/packages/terminal-extension/src/index.ts index db54b0ecd..a485be675 100644 --- a/packages/terminal-extension/src/index.ts +++ b/packages/terminal-extension/src/index.ts @@ -7,6 +7,12 @@ import { JupyterFrontEndPlugin } from '@jupyterlab/application'; +import { PageConfig } from '@jupyterlab/coreutils'; + +import { ITerminalTracker } from '@jupyterlab/terminal'; + +import { find } from '@lumino/algorithm'; + /** * A plugin to terminals in a new tab */ @@ -39,9 +45,30 @@ const opener: JupyterFrontEndPlugin = { } }; +/** + * Open terminals in a new tab. + */ +const redirect: JupyterFrontEndPlugin = { + id: '@jupyterlab-classic/terminal-extension:redirect', + requires: [ITerminalTracker], + autoStart: true, + activate: (app: JupyterFrontEnd, tracker: ITerminalTracker) => { + const baseUrl = PageConfig.getBaseUrl(); + tracker.widgetAdded.connect((send, terminal) => { + const widget = find(app.shell.widgets('main'), w => w.id === terminal.id); + if (widget) { + // bail if the terminal is already added to the main area + return; + } + const name = terminal.content.session.name; + window.open(`${baseUrl}classic/terminals/${name}`); + }); + } +}; + /** * Export the plugins as default. */ -const plugins: JupyterFrontEndPlugin[] = [opener]; +const plugins: JupyterFrontEndPlugin[] = [opener, redirect]; export default plugins; From 4ee271073fe39977125259b5230c048a07232508 Mon Sep 17 00:00:00 2001 From: Jeremy Tuloup Date: Wed, 9 Dec 2020 21:25:57 +0100 Subject: [PATCH 4/4] Dispose terminal widget after redirected --- packages/terminal-extension/src/index.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/terminal-extension/src/index.ts b/packages/terminal-extension/src/index.ts index a485be675..06ef925e2 100644 --- a/packages/terminal-extension/src/index.ts +++ b/packages/terminal-extension/src/index.ts @@ -61,7 +61,10 @@ const redirect: JupyterFrontEndPlugin = { return; } const name = terminal.content.session.name; - window.open(`${baseUrl}classic/terminals/${name}`); + window.open(`${baseUrl}classic/terminals/${name}`, '_blank'); + + // dispose the widget since it is not used + terminal.dispose(); }); } };