Skip to content

Commit b53fd3b

Browse files
authored
Support use of service worker to handle stdin (#51)
* Update dependencies (cockle, jupyterlite, jupyterlab) * Support use of service worker to handle stdin * Update CI dependencies * Update to cockle 0.1.0-a1 * Linting * Add stdin tests * Add extra waits in tests
1 parent c30a4fd commit b53fd3b

File tree

17 files changed

+935
-814
lines changed

17 files changed

+935
-814
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ jobs:
106106
- name: Install the extension
107107
run: |
108108
set -eux
109-
python -m pip install --pre "jupyterlite-core==0.6.0b0" "jupyterlab>=4,<5" jupyterlite_terminal*.whl
109+
python -m pip install --pre "jupyterlite-core==0.6.0rc0" "jupyterlab>=4,<5" jupyterlite_terminal*.whl
110110
111111
- name: Micromamba needed for cockle_wasm_env
112112
uses: mamba-org/setup-micromamba@main

.github/workflows/deploy.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222

2323
- name: Install the dependencies
2424
run: |
25-
python -m pip install --pre "jupyterlite-core==0.6.0b0" "jupyterlite-pyodide-kernel==0.6.0b1"
25+
python -m pip install --pre "jupyterlite-core==0.6.0rc0" "jupyterlite-pyodide-kernel==0.6.0b1"
2626
2727
# install a dev version of the terminal extension
2828
python -m pip install .

deploy/requirements-deploy.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
jupyterlab
2-
jupyterlite-core==0.6.0b0
2+
jupyterlite-core==0.6.0rc0
33
jupyterlite-pyodide-kernel==0.6.0b1
44
..

package.json

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -59,19 +59,19 @@
5959
"watch:labextension": "jupyter labextension watch ."
6060
},
6161
"dependencies": {
62-
"@jupyterlab/coreutils": "^6.4.2",
63-
"@jupyterlab/services": "^7.4.2",
64-
"@jupyterlab/terminal": "^4.4.2",
65-
"@jupyterlab/terminal-extension": "^4.4.2",
66-
"@jupyterlite/cockle": "^0.0.19",
67-
"@jupyterlite/contents": "0.6.0-beta.0",
68-
"@jupyterlite/server": "0.6.0-beta.0",
62+
"@jupyterlab/coreutils": "^6.4.3",
63+
"@jupyterlab/services": "^7.4.3",
64+
"@jupyterlab/terminal": "^4.4.3",
65+
"@jupyterlab/terminal-extension": "^4.4.3",
66+
"@jupyterlite/cockle": "^0.1.0-a1",
67+
"@jupyterlite/contents": "0.6.0-rc.0",
68+
"@jupyterlite/server": "0.6.0-rc.0",
6969
"@lumino/coreutils": "^2.2.0",
7070
"mock-socket": "^9.3.1"
7171
},
7272
"devDependencies": {
73-
"@jupyterlab/builder": "^4.4.2",
74-
"@jupyterlab/testutils": "^4.4.2",
73+
"@jupyterlab/builder": "^4.4.3",
74+
"@jupyterlab/testutils": "^4.4.3",
7575
"@types/jest": "^29.2.0",
7676
"@types/json-schema": "^7.0.11",
7777
"@types/react": "^18.0.26",

src/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ const browsingContextIdSetter: JupyterFrontEndPlugin<void> = {
4747
if (isILiteTerminalManager(terminalManager)) {
4848
const { browsingContextId } = serviceWorkerManager;
4949
terminalManager.browsingContextId = browsingContextId;
50+
51+
serviceWorkerManager.registerStdinHandler(
52+
'terminal',
53+
terminalManager.handleStdin.bind(terminalManager)
54+
);
5055
} else {
5156
console.warn(
5257
'Terminal manager does not support setting browsingContextId'

src/manager.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,26 @@
11
import { BaseManager, Terminal, TerminalManager } from '@jupyterlab/services';
2+
import {
3+
IShellManager,
4+
IStdinReply,
5+
IStdinRequest,
6+
ShellManager
7+
} from '@jupyterlite/cockle';
28
import { ISignal, Signal } from '@lumino/signaling';
39
import { LiteTerminalConnection } from './terminal';
410

511
/**
612
* Interface for Lite terminal manager, supports setting browserContextId.
713
*/
814
interface ILiteTerminalManager extends Terminal.IManager {
15+
/**
16+
* Identifier for communicating with service worker.
17+
*/
918
browsingContextId: string;
19+
20+
/**
21+
* Function that handles stdin requests received from service worker.
22+
*/
23+
handleStdin(request: any): Promise<any>;
1024
}
1125

1226
/**
@@ -15,7 +29,7 @@ interface ILiteTerminalManager extends Terminal.IManager {
1529
export function isILiteTerminalManager(
1630
obj: Terminal.IManager
1731
): obj is ILiteTerminalManager {
18-
return 'browsingContextId' in obj;
32+
return 'browsingContextId' in obj && 'handleStdin' in obj;
1933
}
2034

2135
/**
@@ -31,6 +45,8 @@ export class LiteTerminalManager
3145
constructor(options: TerminalManager.IOptions = {}) {
3246
super(options);
3347

48+
this._shellManager = new ShellManager();
49+
3450
// Initialize internal data.
3551
this._ready = (async () => {
3652
this._isReady = true;
@@ -73,12 +89,20 @@ export class LiteTerminalManager
7389
const terminal = new LiteTerminalConnection({
7490
browsingContextId: this._browsingContextId,
7591
model,
76-
serverSettings
92+
serverSettings,
93+
shellManager: this._shellManager
7794
});
7895
terminal.disposed.connect(() => this.shutdown(name));
7996
return terminal;
8097
}
8198

99+
/**
100+
* Function that handles stdin requests received from service worker.
101+
*/
102+
async handleStdin(request: IStdinRequest): Promise<IStdinReply> {
103+
return await this._shellManager.handleStdin(request);
104+
}
105+
82106
/**
83107
* Whether the terminal service is available.
84108
*/
@@ -172,7 +196,8 @@ export class LiteTerminalManager
172196
const terminal = new LiteTerminalConnection({
173197
browsingContextId: this._browsingContextId,
174198
model,
175-
serverSettings
199+
serverSettings,
200+
shellManager: this._shellManager
176201
});
177202
terminal.disposed.connect(() => this.shutdown(name));
178203
this._terminalConnections.set(name, terminal);
@@ -200,6 +225,7 @@ export class LiteTerminalManager
200225
private _isReady = false;
201226
private _ready: Promise<void>;
202227
private _runningChanged = new Signal<this, Terminal.IModel[]>(this);
228+
private _shellManager: IShellManager;
203229
private _terminalConnections = new Map<
204230
string,
205231
Terminal.ITerminalConnection

src/terminal.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { ServerConnection, Terminal } from '@jupyterlab/services';
22
import { ISignal, Signal } from '@lumino/signaling';
33
import { Shell } from './shell';
4-
import { IShell } from '@jupyterlite/cockle';
4+
import { IShell, IShellManager } from '@jupyterlite/cockle';
55

66
/**
77
* An implementation of a terminal interface.
@@ -11,17 +11,18 @@ export class LiteTerminalConnection implements Terminal.ITerminalConnection {
1111
* Construct a new terminal session.
1212
*/
1313
constructor(options: LiteTerminalConnection.IOptions) {
14-
this._name = options.model.name;
1514
this._serverSettings = options.serverSettings!;
1615
const { baseUrl } = this._serverSettings;
17-
const { browsingContextId } = options;
16+
const { browsingContextId, shellManager } = options;
1817

1918
this._shell = new Shell({
2019
mountpoint: '/drive',
21-
driveFsBaseUrl: baseUrl,
20+
baseUrl,
2221
wasmBaseUrl: baseUrl + 'extensions/@jupyterlite/terminal/static/wasm/',
23-
outputCallback: this._outputCallback.bind(this),
24-
browsingContextId
22+
browsingContextId,
23+
shellId: options.model.name,
24+
shellManager,
25+
outputCallback: this._outputCallback.bind(this)
2526
});
2627
this._shell.disposed.connect(() => this.dispose());
2728

@@ -87,14 +88,14 @@ export class LiteTerminalConnection implements Terminal.ITerminalConnection {
8788
* Get the model for the terminal session.
8889
*/
8990
get model(): Terminal.IModel {
90-
return { name: this._name };
91+
return { name: this._shell.shellId };
9192
}
9293

9394
/**
9495
* Get the name of the terminal session.
9596
*/
9697
get name(): string {
97-
return this._name;
98+
return this._shell.shellId;
9899
}
99100

100101
/**
@@ -173,7 +174,6 @@ export class LiteTerminalConnection implements Terminal.ITerminalConnection {
173174
private _isDisposed = false;
174175
private _disposed = new Signal<this, void>(this);
175176

176-
private _name: string;
177177
private _serverSettings: ServerConnection.ISettings;
178178
private _connectionStatus: Terminal.ConnectionStatus = 'connecting';
179179
private _connectionStatusChanged = new Signal<
@@ -191,5 +191,7 @@ export namespace LiteTerminalConnection {
191191
* The ID of the browsing context where the request originated.
192192
*/
193193
browsingContextId?: string;
194+
195+
shellManager: IShellManager;
194196
}
195197
}

src/worker.ts

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,19 @@ class ShellWorker extends BaseShellWorker {
1212
* Initialize the DriveFS to mount an external file system, if available.
1313
*/
1414
protected override initDriveFS(options: IDriveFSOptions): void {
15-
const { browsingContextId, driveFsBaseUrl, fileSystem, mountpoint } =
16-
options;
17-
console.log(
18-
'Terminal initDriveFS',
19-
driveFsBaseUrl,
20-
mountpoint,
21-
browsingContextId
22-
);
15+
const { baseUrl, browsingContextId, fileSystem, mountpoint } = options;
16+
console.log('Terminal initDriveFS', baseUrl, mountpoint, browsingContextId);
2317
if (
2418
mountpoint !== '' &&
25-
driveFsBaseUrl !== undefined &&
19+
baseUrl !== undefined &&
2620
browsingContextId !== undefined
2721
) {
2822
const { FS, ERRNO_CODES, PATH } = fileSystem;
2923
const driveFS = new DriveFS({
3024
FS,
3125
PATH,
3226
ERRNO_CODES,
33-
baseUrl: driveFsBaseUrl,
27+
baseUrl,
3428
driveName: '',
3529
mountpoint,
3630
browsingContextId

ui-tests/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"test:update": "jlpm playwright test --update-snapshots"
1414
},
1515
"devDependencies": {
16-
"@jupyterlab/galata": "^5.4.2",
16+
"@jupyterlab/galata": "^5.4.3",
1717
"@playwright/test": "^1.51.0",
1818
"rimraf": "^6.0.1"
1919
}

ui-tests/tests/fs.spec.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { expect, test } from '@jupyterlab/galata';
22

33
import { ContentsHelper } from './utils/contents';
4-
import { TERMINAL_SELECTOR, decode64, inputLine } from './utils/misc';
4+
import { TERMINAL_SELECTOR, WAIT_MS, decode64, inputLine } from './utils/misc';
55

66
const MONTHS_TXT =
77
'January\nFebruary\nMarch\nApril\nMay\nJune\nJuly\nAugust\nSeptember\nOctober\nNovember\nDecember\n';
@@ -18,6 +18,7 @@ const FACT_LUA =
1818
test.describe('Filesystem', () => {
1919
test.beforeEach(async ({ page }) => {
2020
await page.goto();
21+
await page.waitForTimeout(WAIT_MS);
2122

2223
// Overwrite the (read-only) page.contents with our own ContentsHelper.
2324
// @ts-ignore
@@ -26,6 +27,7 @@ test.describe('Filesystem', () => {
2627
await page.menu.clickMenuItem('File>New>Terminal');
2728
await page.locator(TERMINAL_SELECTOR).waitFor();
2829
await page.locator('div.xterm-screen').click(); // sets focus for keyboard input
30+
await page.waitForTimeout(WAIT_MS);
2931
});
3032

3133
test('should have initial files', async ({ page }) => {
@@ -51,13 +53,15 @@ test.describe('Filesystem', () => {
5153
await page.menu.clickMenuItem('File>New>Terminal');
5254
await page.locator(TERMINAL_SELECTOR).waitFor();
5355
await page.locator('div.xterm-screen').click(); // sets focus for keyboard input
56+
await page.waitForTimeout(WAIT_MS);
5457

5558
await inputLine(page, 'echo Hello > out.txt');
5659
await page.getByTitle('Name: out.txt').waitFor();
5760
});
5861

5962
test('should support cp', async ({ page }) => {
6063
await inputLine(page, 'cp months.txt other.txt');
64+
await page.waitForTimeout(WAIT_MS);
6165
await page.filebrowser.refresh();
6266

6367
expect(await page.contents.fileExists('months.txt')).toBeTruthy();
@@ -67,16 +71,9 @@ test.describe('Filesystem', () => {
6771
expect(other?.content).toEqual(MONTHS_TXT);
6872
});
6973

70-
// rm of files added via --contents is not reliable.
71-
test.skip('should support rm', async ({ page }) => {
72-
await inputLine(page, 'rm fact.lua');
73-
await page.filebrowser.refresh();
74-
75-
expect(await page.contents.fileExists('fact.lua')).toBeFalsy();
76-
});
77-
7874
test('should support touch', async ({ page }) => {
7975
await inputLine(page, 'touch touched.txt');
76+
await page.waitForTimeout(WAIT_MS);
8077
await page.filebrowser.refresh();
8178

8279
expect(await page.contents.fileExists('touched.txt')).toBeTruthy();

0 commit comments

Comments
 (0)