@jupyterlite/terminal 0.1.5 → 0.2.0-a0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.d.ts +3 -3
- package/lib/index.js +20 -42
- package/lib/manager.d.ts +79 -18
- package/lib/manager.js +139 -39
- package/lib/shell.d.ts +16 -0
- package/lib/shell.js +23 -0
- package/lib/terminal.d.ts +76 -12
- package/lib/terminal.js +115 -69
- package/lib/worker.d.ts +1 -0
- package/lib/worker.js +37 -0
- package/package.json +16 -17
- package/src/index.ts +34 -60
- package/src/manager.ts +167 -42
- package/src/shell.ts +25 -0
- package/src/terminal.ts +154 -89
- package/src/worker.ts +47 -0
- package/lib/tokens.d.ts +0 -52
- package/lib/tokens.js +0 -7
- package/src/tokens.ts +0 -66
package/lib/terminal.js
CHANGED
|
@@ -1,101 +1,147 @@
|
|
|
1
|
-
// Copyright (c) Jupyter Development Team.
|
|
2
|
-
// Distributed under the terms of the Modified BSD License.
|
|
3
|
-
import { Shell } from '@jupyterlite/cockle';
|
|
4
1
|
import { Signal } from '@lumino/signaling';
|
|
5
|
-
import {
|
|
6
|
-
|
|
2
|
+
import { Shell } from './shell';
|
|
3
|
+
/**
|
|
4
|
+
* An implementation of a terminal interface.
|
|
5
|
+
*/
|
|
6
|
+
export class LiteTerminalConnection {
|
|
7
7
|
/**
|
|
8
|
-
* Construct a new
|
|
8
|
+
* Construct a new terminal session.
|
|
9
9
|
*/
|
|
10
10
|
constructor(options) {
|
|
11
|
-
this.options = options;
|
|
12
|
-
this._disposed = new Signal(this);
|
|
13
11
|
this._isDisposed = false;
|
|
14
|
-
this.
|
|
12
|
+
this._disposed = new Signal(this);
|
|
13
|
+
this._connectionStatus = 'connecting';
|
|
14
|
+
this._connectionStatusChanged = new Signal(this);
|
|
15
|
+
this._messageReceived = new Signal(this);
|
|
16
|
+
this._name = options.model.name;
|
|
17
|
+
this._serverSettings = options.serverSettings;
|
|
18
|
+
const { baseUrl } = this._serverSettings;
|
|
19
|
+
const { browsingContextId } = options;
|
|
15
20
|
this._shell = new Shell({
|
|
16
21
|
mountpoint: '/drive',
|
|
17
|
-
driveFsBaseUrl:
|
|
18
|
-
wasmBaseUrl:
|
|
19
|
-
outputCallback: this._outputCallback.bind(this)
|
|
22
|
+
driveFsBaseUrl: baseUrl,
|
|
23
|
+
wasmBaseUrl: baseUrl + 'extensions/@jupyterlite/terminal/static/wasm/',
|
|
24
|
+
outputCallback: this._outputCallback.bind(this),
|
|
25
|
+
browsingContextId
|
|
20
26
|
});
|
|
21
27
|
this._shell.disposed.connect(() => this.dispose());
|
|
28
|
+
this._shell.start().then(() => this._updateConnectionStatus('connected'));
|
|
22
29
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
30
|
+
/**
|
|
31
|
+
* The current connection status of the terminal connection.
|
|
32
|
+
*/
|
|
33
|
+
get connectionStatus() {
|
|
34
|
+
return this._connectionStatus;
|
|
28
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* A signal emitted when the terminal connection status changes.
|
|
38
|
+
*/
|
|
39
|
+
get connectionStatusChanged() {
|
|
40
|
+
return this._connectionStatusChanged;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Dispose of the resources held by the session.
|
|
44
|
+
*/
|
|
29
45
|
dispose() {
|
|
30
46
|
if (this._isDisposed) {
|
|
31
47
|
return;
|
|
32
48
|
}
|
|
33
|
-
console.log('Terminal.dispose');
|
|
34
49
|
this._isDisposed = true;
|
|
35
|
-
if (this._socket !== undefined) {
|
|
36
|
-
// Disconnect from frontend.
|
|
37
|
-
this._socket.send(JSON.stringify(['disconnect']));
|
|
38
|
-
this._socket.close();
|
|
39
|
-
this._socket = undefined;
|
|
40
|
-
}
|
|
41
|
-
if (this._server !== undefined) {
|
|
42
|
-
this._server.close();
|
|
43
|
-
this._server = undefined;
|
|
44
|
-
}
|
|
45
50
|
this._shell.dispose();
|
|
46
51
|
this._disposed.emit();
|
|
52
|
+
this._updateConnectionStatus('disconnected');
|
|
53
|
+
Signal.clearData(this);
|
|
47
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* A signal emitted when the session is disposed.
|
|
57
|
+
*/
|
|
48
58
|
get disposed() {
|
|
49
59
|
return this._disposed;
|
|
50
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Test whether the session is disposed.
|
|
63
|
+
*/
|
|
51
64
|
get isDisposed() {
|
|
52
65
|
return this._isDisposed;
|
|
53
66
|
}
|
|
54
67
|
/**
|
|
55
|
-
*
|
|
68
|
+
* A signal emitted when a message is received from the server.
|
|
69
|
+
*/
|
|
70
|
+
get messageReceived() {
|
|
71
|
+
return this._messageReceived;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Get the model for the terminal session.
|
|
75
|
+
*/
|
|
76
|
+
get model() {
|
|
77
|
+
return { name: this._name };
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Get the name of the terminal session.
|
|
56
81
|
*/
|
|
57
82
|
get name() {
|
|
58
|
-
return this.
|
|
83
|
+
return this._name;
|
|
59
84
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
// Return handshake.
|
|
92
|
-
const res = JSON.stringify(['setup']);
|
|
93
|
-
console.log('==> Returning handshake via socket', res);
|
|
94
|
-
socket.send(res);
|
|
95
|
-
if (!this._running) {
|
|
96
|
-
this._running = true;
|
|
97
|
-
await this._shell.start();
|
|
85
|
+
/**
|
|
86
|
+
* Reconnect to a terminal.
|
|
87
|
+
*
|
|
88
|
+
* #### Notes
|
|
89
|
+
* This may try multiple times to reconnect to a terminal, and will sever
|
|
90
|
+
* any existing connection.
|
|
91
|
+
*/
|
|
92
|
+
async reconnect() {
|
|
93
|
+
console.log('==> LiteTerminalConnection.reconnect not implemented');
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Send a message to the terminal session.
|
|
97
|
+
*
|
|
98
|
+
* #### Notes
|
|
99
|
+
* If the connection is down, the message will be queued for sending when
|
|
100
|
+
* the connection comes back up.
|
|
101
|
+
*/
|
|
102
|
+
send(message) {
|
|
103
|
+
const { content } = message;
|
|
104
|
+
if (content === undefined) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
switch (message.type) {
|
|
108
|
+
case 'stdin':
|
|
109
|
+
this._shell.input(content[0]); // async
|
|
110
|
+
break;
|
|
111
|
+
case 'set_size': {
|
|
112
|
+
const rows = content[0];
|
|
113
|
+
const columns = content[1];
|
|
114
|
+
this._shell.setSize(rows, columns); // async
|
|
115
|
+
break;
|
|
98
116
|
}
|
|
99
|
-
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* The server settings for the session.
|
|
121
|
+
*/
|
|
122
|
+
get serverSettings() {
|
|
123
|
+
return this._serverSettings;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Shut down the terminal session.
|
|
127
|
+
*/
|
|
128
|
+
async shutdown() {
|
|
129
|
+
this.dispose();
|
|
130
|
+
}
|
|
131
|
+
_outputCallback(text) {
|
|
132
|
+
// 'stdout' or 'disconnect' as MessageType.
|
|
133
|
+
// Cockle is not yet using the 'disconnect'.
|
|
134
|
+
this._messageReceived.emit({ type: 'stdout', content: [text] });
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Handle connection status changes.
|
|
138
|
+
*/
|
|
139
|
+
_updateConnectionStatus(connectionStatus) {
|
|
140
|
+
if (this._connectionStatus === connectionStatus) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
this._connectionStatus = connectionStatus;
|
|
144
|
+
// Notify others that the connection status changed.
|
|
145
|
+
this._connectionStatusChanged.emit(connectionStatus);
|
|
100
146
|
}
|
|
101
147
|
}
|
package/lib/worker.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/lib/worker.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { expose } from 'comlink';
|
|
2
|
+
import { BaseShellWorker } from '@jupyterlite/cockle';
|
|
3
|
+
import { DriveFS } from '@jupyterlite/contents';
|
|
4
|
+
/**
|
|
5
|
+
* Shell web worker that uses DriveFS via service worker.
|
|
6
|
+
* Note that this is not exported as it is accessed from Shell via the filename.
|
|
7
|
+
*/
|
|
8
|
+
class ShellWorker extends BaseShellWorker {
|
|
9
|
+
/**
|
|
10
|
+
* Initialize the DriveFS to mount an external file system, if available.
|
|
11
|
+
*/
|
|
12
|
+
initDriveFS(options) {
|
|
13
|
+
const { browsingContextId, driveFsBaseUrl, fileSystem, mountpoint } = options;
|
|
14
|
+
console.log('Terminal initDriveFS', driveFsBaseUrl, mountpoint, browsingContextId);
|
|
15
|
+
if (mountpoint !== '' &&
|
|
16
|
+
driveFsBaseUrl !== undefined &&
|
|
17
|
+
browsingContextId !== undefined) {
|
|
18
|
+
const { FS, ERRNO_CODES, PATH } = fileSystem;
|
|
19
|
+
const driveFS = new DriveFS({
|
|
20
|
+
FS,
|
|
21
|
+
PATH,
|
|
22
|
+
ERRNO_CODES,
|
|
23
|
+
baseUrl: driveFsBaseUrl,
|
|
24
|
+
driveName: '',
|
|
25
|
+
mountpoint,
|
|
26
|
+
browsingContextId
|
|
27
|
+
});
|
|
28
|
+
FS.mount(driveFS, {}, mountpoint);
|
|
29
|
+
console.log('Terminal connected to shared drive');
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
console.warn('Terminal not connected to shared drive');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const worker = new ShellWorker();
|
|
37
|
+
expose(worker);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jupyterlite/terminal",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0-a0",
|
|
4
4
|
"description": "A terminal for JupyterLite",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jupyter",
|
|
@@ -30,12 +30,14 @@
|
|
|
30
30
|
"url": "https://github.com/jupyterlite/terminal.git"
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
|
-
"build": "jlpm build:lib && jlpm build:labextension:dev",
|
|
34
|
-
"build:prod": "jlpm clean && jlpm build:lib:prod && jlpm build:labextension",
|
|
33
|
+
"build": "jlpm build:lib && jlpm build:labextension:dev && jlpm build:webworker",
|
|
34
|
+
"build:prod": "jlpm clean && jlpm build:lib:prod && jlpm build:labextension && jlpm build:webworker:prod",
|
|
35
35
|
"build:labextension": "jupyter labextension build .",
|
|
36
36
|
"build:labextension:dev": "jupyter labextension build --development True .",
|
|
37
37
|
"build:lib": "tsc --sourceMap",
|
|
38
38
|
"build:lib:prod": "tsc",
|
|
39
|
+
"build:webworker": "webpack -c webpack.worker.config.js --env=dev",
|
|
40
|
+
"build:webworker:prod": "webpack -c webpack.worker.config.js",
|
|
39
41
|
"clean": "jlpm clean:lib",
|
|
40
42
|
"clean:lib": "rimraf lib tsconfig.tsbuildinfo",
|
|
41
43
|
"clean:lintcache": "rimraf .eslintcache .stylelintcache",
|
|
@@ -57,19 +59,19 @@
|
|
|
57
59
|
"watch:labextension": "jupyter labextension watch ."
|
|
58
60
|
},
|
|
59
61
|
"dependencies": {
|
|
60
|
-
"@jupyterlab/coreutils": "^6.
|
|
61
|
-
"@jupyterlab/services": "^7.
|
|
62
|
-
"@jupyterlab/terminal": "^4.
|
|
63
|
-
"@jupyterlab/terminal-extension": "^4.
|
|
64
|
-
"@jupyterlite/cockle": "^0.0.
|
|
65
|
-
"@jupyterlite/contents": "
|
|
66
|
-
"@jupyterlite/server": "
|
|
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",
|
|
67
69
|
"@lumino/coreutils": "^2.2.0",
|
|
68
70
|
"mock-socket": "^9.3.1"
|
|
69
71
|
},
|
|
70
72
|
"devDependencies": {
|
|
71
|
-
"@jupyterlab/builder": "^4.
|
|
72
|
-
"@jupyterlab/testutils": "^4.
|
|
73
|
+
"@jupyterlab/builder": "^4.4.2",
|
|
74
|
+
"@jupyterlab/testutils": "^4.4.2",
|
|
73
75
|
"@types/jest": "^29.2.0",
|
|
74
76
|
"@types/json-schema": "^7.0.11",
|
|
75
77
|
"@types/react": "^18.0.26",
|
|
@@ -91,6 +93,7 @@
|
|
|
91
93
|
"stylelint-config-standard": "^34.0.0",
|
|
92
94
|
"stylelint-csstree-validator": "^3.0.0",
|
|
93
95
|
"stylelint-prettier": "^4.0.0",
|
|
96
|
+
"ts-loader": "^9.5.2",
|
|
94
97
|
"typescript": "~5.0.2",
|
|
95
98
|
"webpack": "^5.87.0",
|
|
96
99
|
"webpack-cli": "^5.1.4",
|
|
@@ -106,11 +109,7 @@
|
|
|
106
109
|
},
|
|
107
110
|
"jupyterlab": {
|
|
108
111
|
"extension": true,
|
|
109
|
-
"outputDir": "jupyterlite_terminal/labextension"
|
|
110
|
-
"webpackConfig": "./webpack.extra.config.js"
|
|
111
|
-
},
|
|
112
|
-
"jupyterlite": {
|
|
113
|
-
"liteExtension": true
|
|
112
|
+
"outputDir": "jupyterlite_terminal/labextension"
|
|
114
113
|
},
|
|
115
114
|
"eslintIgnore": [
|
|
116
115
|
"node_modules",
|
package/src/index.ts
CHANGED
|
@@ -2,84 +2,58 @@
|
|
|
2
2
|
// Distributed under the terms of the Modified BSD License.
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
JupyterFrontEnd,
|
|
6
|
+
JupyterFrontEndPlugin
|
|
7
|
+
} from '@jupyterlab/application';
|
|
8
|
+
import {
|
|
9
|
+
ITerminalManager,
|
|
10
|
+
ServiceManagerPlugin,
|
|
11
|
+
Terminal
|
|
12
|
+
} from '@jupyterlab/services';
|
|
13
|
+
import { IServiceWorkerManager } from '@jupyterlite/server';
|
|
9
14
|
|
|
10
|
-
import {
|
|
11
|
-
import { ITerminalManager } from './tokens';
|
|
15
|
+
import { isILiteTerminalManager, LiteTerminalManager } from './manager';
|
|
12
16
|
|
|
13
17
|
/**
|
|
14
|
-
* The
|
|
18
|
+
* The terminal manager plugin, replacing the JupyterLab terminal manager.
|
|
15
19
|
*/
|
|
16
|
-
const
|
|
20
|
+
const terminalManagerPlugin: ServiceManagerPlugin<Terminal.IManager> = {
|
|
17
21
|
id: '@jupyterlite/terminal:plugin',
|
|
18
|
-
description: 'A terminal
|
|
22
|
+
description: 'A JupyterLite extension providing a custom terminal manager',
|
|
19
23
|
autoStart: true,
|
|
20
24
|
provides: ITerminalManager,
|
|
21
|
-
activate:
|
|
25
|
+
activate: (_: null): Terminal.IManager => {
|
|
22
26
|
console.log(
|
|
23
27
|
'JupyterLite extension @jupyterlite/terminal:plugin is activated!'
|
|
24
28
|
);
|
|
25
|
-
|
|
26
|
-
const { serviceManager } = app;
|
|
27
|
-
const { serverSettings, terminals } = serviceManager;
|
|
28
|
-
console.log('terminals available:', terminals.isAvailable());
|
|
29
|
-
console.log('terminals ready:', terminals.isReady); // Not ready
|
|
30
|
-
console.log('terminals active:', terminals.isActive);
|
|
31
|
-
|
|
32
|
-
// Not sure this is necessary?
|
|
33
|
-
await terminals.ready;
|
|
34
|
-
console.log('terminals ready after await:', terminals.isReady); // Ready
|
|
35
|
-
|
|
36
|
-
return new TerminalManager(serverSettings.wsUrl);
|
|
29
|
+
return new LiteTerminalManager();
|
|
37
30
|
}
|
|
38
31
|
};
|
|
39
32
|
|
|
40
33
|
/**
|
|
41
|
-
* A plugin
|
|
34
|
+
* A plugin that sets the browsingContextId of the terminal manager.
|
|
42
35
|
*/
|
|
43
|
-
const
|
|
44
|
-
id: '@jupyterlite/terminal:
|
|
36
|
+
const browsingContextIdSetter: JupyterFrontEndPlugin<void> = {
|
|
37
|
+
id: '@jupyterlite/terminal:browsing-context-id',
|
|
45
38
|
autoStart: true,
|
|
39
|
+
optional: [IServiceWorkerManager],
|
|
46
40
|
requires: [ITerminalManager],
|
|
47
|
-
activate: (
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
// POST /api/terminals - Start a terminal
|
|
61
|
-
app.router.post('/api/terminals', async (req: Router.IRequest) => {
|
|
62
|
-
const res = await terminalManager.startNew();
|
|
63
|
-
// Should return last_activity too.
|
|
64
|
-
return new Response(JSON.stringify(res));
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
// DELETE /api/terminals/{terminal name} - Delete a terminal
|
|
68
|
-
app.router.delete(
|
|
69
|
-
'/api/terminals/(.+)',
|
|
70
|
-
async (req: Router.IRequest, name: string) => {
|
|
71
|
-
const exists = terminalManager.has(name);
|
|
72
|
-
if (exists) {
|
|
73
|
-
await terminalManager.shutdownTerminal(name);
|
|
74
|
-
} else {
|
|
75
|
-
const msg = `The terminal session "${name}"" does not exist`;
|
|
76
|
-
console.warn(msg);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return new Response(null, { status: exists ? 204 : 404 });
|
|
41
|
+
activate: (
|
|
42
|
+
_: JupyterFrontEnd,
|
|
43
|
+
terminalManager: Terminal.IManager,
|
|
44
|
+
serviceWorkerManager?: IServiceWorkerManager
|
|
45
|
+
): void => {
|
|
46
|
+
if (serviceWorkerManager !== undefined) {
|
|
47
|
+
if (isILiteTerminalManager(terminalManager)) {
|
|
48
|
+
const { browsingContextId } = serviceWorkerManager;
|
|
49
|
+
terminalManager.browsingContextId = browsingContextId;
|
|
50
|
+
} else {
|
|
51
|
+
console.warn(
|
|
52
|
+
'Terminal manager does not support setting browsingContextId'
|
|
53
|
+
);
|
|
80
54
|
}
|
|
81
|
-
|
|
55
|
+
}
|
|
82
56
|
}
|
|
83
57
|
};
|
|
84
58
|
|
|
85
|
-
export default [
|
|
59
|
+
export default [terminalManagerPlugin, browsingContextIdSetter];
|