@jupyterlab/terminal 4.0.0-alpha.2 → 4.0.0-alpha.21
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/tokens.d.ts +18 -1
- package/lib/tokens.js +1 -2
- package/lib/tokens.js.map +1 -1
- package/lib/widget.d.ts +24 -6
- package/lib/widget.js +218 -65
- package/lib/widget.js.map +1 -1
- package/package.json +20 -18
- package/src/index.ts +9 -0
- package/src/tokens.ts +171 -0
- package/src/widget.ts +626 -0
package/src/widget.ts
ADDED
|
@@ -0,0 +1,626 @@
|
|
|
1
|
+
// Copyright (c) Jupyter Development Team.
|
|
2
|
+
// Distributed under the terms of the Modified BSD License.
|
|
3
|
+
|
|
4
|
+
import { Terminal as TerminalNS } from '@jupyterlab/services';
|
|
5
|
+
import {
|
|
6
|
+
ITranslator,
|
|
7
|
+
nullTranslator,
|
|
8
|
+
TranslationBundle
|
|
9
|
+
} from '@jupyterlab/translation';
|
|
10
|
+
import { PromiseDelegate } from '@lumino/coreutils';
|
|
11
|
+
import { Platform } from '@lumino/domutils';
|
|
12
|
+
import { Message, MessageLoop } from '@lumino/messaging';
|
|
13
|
+
import { Widget } from '@lumino/widgets';
|
|
14
|
+
import type {
|
|
15
|
+
ITerminalInitOnlyOptions,
|
|
16
|
+
ITerminalOptions,
|
|
17
|
+
Terminal as Xterm
|
|
18
|
+
} from 'xterm';
|
|
19
|
+
import type { CanvasAddon } from 'xterm-addon-canvas';
|
|
20
|
+
import type { FitAddon } from 'xterm-addon-fit';
|
|
21
|
+
import type { WebLinksAddon } from 'xterm-addon-web-links';
|
|
22
|
+
import type { WebglAddon } from 'xterm-addon-webgl';
|
|
23
|
+
import { ITerminal } from '.';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The class name added to a terminal widget.
|
|
27
|
+
*/
|
|
28
|
+
const TERMINAL_CLASS = 'jp-Terminal';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The class name added to a terminal body.
|
|
32
|
+
*/
|
|
33
|
+
const TERMINAL_BODY_CLASS = 'jp-Terminal-body';
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A widget which manages a terminal session.
|
|
37
|
+
*/
|
|
38
|
+
export class Terminal extends Widget implements ITerminal.ITerminal {
|
|
39
|
+
/**
|
|
40
|
+
* Construct a new terminal widget.
|
|
41
|
+
*
|
|
42
|
+
* @param session - The terminal session object.
|
|
43
|
+
*
|
|
44
|
+
* @param options - The terminal configuration options.
|
|
45
|
+
*
|
|
46
|
+
* @param translator - The language translator.
|
|
47
|
+
*/
|
|
48
|
+
constructor(
|
|
49
|
+
session: TerminalNS.ITerminalConnection,
|
|
50
|
+
options: Partial<ITerminal.IOptions> = {},
|
|
51
|
+
translator?: ITranslator
|
|
52
|
+
) {
|
|
53
|
+
super();
|
|
54
|
+
translator = translator || nullTranslator;
|
|
55
|
+
this._trans = translator.load('jupyterlab');
|
|
56
|
+
this.session = session;
|
|
57
|
+
|
|
58
|
+
// Initialize settings.
|
|
59
|
+
this._options = { ...ITerminal.defaultOptions, ...options };
|
|
60
|
+
|
|
61
|
+
const { theme, ...other } = this._options;
|
|
62
|
+
const xtermOptions = {
|
|
63
|
+
theme: Private.getXTermTheme(theme),
|
|
64
|
+
...other
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
this.addClass(TERMINAL_CLASS);
|
|
68
|
+
|
|
69
|
+
this._setThemeAttribute(theme);
|
|
70
|
+
|
|
71
|
+
// Buffer session message while waiting for the terminal
|
|
72
|
+
let buffer = '';
|
|
73
|
+
const bufferMessage = (
|
|
74
|
+
sender: TerminalNS.ITerminalConnection,
|
|
75
|
+
msg: TerminalNS.IMessage
|
|
76
|
+
): void => {
|
|
77
|
+
switch (msg.type) {
|
|
78
|
+
case 'stdout':
|
|
79
|
+
if (msg.content) {
|
|
80
|
+
buffer += msg.content[0] as string;
|
|
81
|
+
}
|
|
82
|
+
break;
|
|
83
|
+
default:
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
session.messageReceived.connect(bufferMessage);
|
|
88
|
+
session.disposed.connect(() => {
|
|
89
|
+
if (this.getOption('closeOnExit')) {
|
|
90
|
+
this.dispose();
|
|
91
|
+
}
|
|
92
|
+
}, this);
|
|
93
|
+
|
|
94
|
+
// Create the xterm.
|
|
95
|
+
Private.createTerminal(xtermOptions)
|
|
96
|
+
.then(([term, fitAddon]) => {
|
|
97
|
+
this._term = term;
|
|
98
|
+
this._fitAddon = fitAddon;
|
|
99
|
+
this._initializeTerm();
|
|
100
|
+
|
|
101
|
+
this.id = `jp-Terminal-${Private.id++}`;
|
|
102
|
+
this.title.label = this._trans.__('Terminal');
|
|
103
|
+
this._isReady = true;
|
|
104
|
+
this._ready.resolve();
|
|
105
|
+
|
|
106
|
+
if (buffer) {
|
|
107
|
+
this._term.write(buffer);
|
|
108
|
+
}
|
|
109
|
+
session.messageReceived.disconnect(bufferMessage);
|
|
110
|
+
session.messageReceived.connect(this._onMessage, this);
|
|
111
|
+
|
|
112
|
+
if (session.connectionStatus === 'connected') {
|
|
113
|
+
this._initialConnection();
|
|
114
|
+
} else {
|
|
115
|
+
session.connectionStatusChanged.connect(
|
|
116
|
+
this._initialConnection,
|
|
117
|
+
this
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
this.update();
|
|
121
|
+
})
|
|
122
|
+
.catch(reason => {
|
|
123
|
+
console.error('Failed to create a terminal.\n', reason);
|
|
124
|
+
this._ready.reject(reason);
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* A promise that is fulfilled when the terminal is ready.
|
|
130
|
+
*/
|
|
131
|
+
get ready(): Promise<void> {
|
|
132
|
+
return this._ready.promise;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* The terminal session associated with the widget.
|
|
137
|
+
*/
|
|
138
|
+
readonly session: TerminalNS.ITerminalConnection;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Get a config option for the terminal.
|
|
142
|
+
*/
|
|
143
|
+
getOption<K extends keyof ITerminal.IOptions>(
|
|
144
|
+
option: K
|
|
145
|
+
): ITerminal.IOptions[K] {
|
|
146
|
+
return this._options[option];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Set a config option for the terminal.
|
|
151
|
+
*/
|
|
152
|
+
setOption<K extends keyof ITerminal.IOptions>(
|
|
153
|
+
option: K,
|
|
154
|
+
value: ITerminal.IOptions[K]
|
|
155
|
+
): void {
|
|
156
|
+
if (
|
|
157
|
+
option !== 'theme' &&
|
|
158
|
+
(this._options[option] === value || option === 'initialCommand')
|
|
159
|
+
) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
this._options[option] = value;
|
|
164
|
+
|
|
165
|
+
switch (option) {
|
|
166
|
+
case 'fontFamily':
|
|
167
|
+
this._term.options.fontFamily = value as string | undefined;
|
|
168
|
+
break;
|
|
169
|
+
case 'fontSize':
|
|
170
|
+
this._term.options.fontSize = value as number | undefined;
|
|
171
|
+
break;
|
|
172
|
+
case 'lineHeight':
|
|
173
|
+
this._term.options.lineHeight = value as number | undefined;
|
|
174
|
+
break;
|
|
175
|
+
case 'screenReaderMode':
|
|
176
|
+
this._term.options.screenReaderMode = value as boolean | undefined;
|
|
177
|
+
break;
|
|
178
|
+
case 'scrollback':
|
|
179
|
+
this._term.options.scrollback = value as number | undefined;
|
|
180
|
+
break;
|
|
181
|
+
case 'theme':
|
|
182
|
+
this._term.options.theme = {
|
|
183
|
+
...Private.getXTermTheme(value as ITerminal.Theme)
|
|
184
|
+
};
|
|
185
|
+
this._setThemeAttribute(value as ITerminal.Theme);
|
|
186
|
+
break;
|
|
187
|
+
case 'macOptionIsMeta':
|
|
188
|
+
this._term.options.macOptionIsMeta = value as boolean | undefined;
|
|
189
|
+
break;
|
|
190
|
+
default:
|
|
191
|
+
// Do not transmit options not listed above to XTerm
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
this._needsResize = true;
|
|
196
|
+
this.update();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Dispose of the resources held by the terminal widget.
|
|
201
|
+
*/
|
|
202
|
+
dispose(): void {
|
|
203
|
+
if (!this.session.isDisposed) {
|
|
204
|
+
if (this.getOption('shutdownOnClose')) {
|
|
205
|
+
this.session.shutdown().catch(reason => {
|
|
206
|
+
console.error(`Terminal not shut down: ${reason}`);
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
void this.ready.then(() => {
|
|
211
|
+
this._term.dispose();
|
|
212
|
+
});
|
|
213
|
+
super.dispose();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Refresh the terminal session.
|
|
218
|
+
*
|
|
219
|
+
* #### Notes
|
|
220
|
+
* Failure to reconnect to the session should be caught appropriately
|
|
221
|
+
*/
|
|
222
|
+
async refresh(): Promise<void> {
|
|
223
|
+
if (!this.isDisposed && this._isReady) {
|
|
224
|
+
await this.session.reconnect();
|
|
225
|
+
this._term.clear();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Check if terminal has any text selected.
|
|
231
|
+
*/
|
|
232
|
+
hasSelection(): boolean {
|
|
233
|
+
if (!this.isDisposed && this._isReady) {
|
|
234
|
+
return this._term.hasSelection();
|
|
235
|
+
}
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Paste text into terminal.
|
|
241
|
+
*/
|
|
242
|
+
paste(data: string): void {
|
|
243
|
+
if (!this.isDisposed && this._isReady) {
|
|
244
|
+
return this._term.paste(data);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Get selected text from terminal.
|
|
250
|
+
*/
|
|
251
|
+
getSelection(): string | null {
|
|
252
|
+
if (!this.isDisposed && this._isReady) {
|
|
253
|
+
return this._term.getSelection();
|
|
254
|
+
}
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Process a message sent to the widget.
|
|
260
|
+
*
|
|
261
|
+
* @param msg - The message sent to the widget.
|
|
262
|
+
*
|
|
263
|
+
* #### Notes
|
|
264
|
+
* Subclasses may reimplement this method as needed.
|
|
265
|
+
*/
|
|
266
|
+
processMessage(msg: Message): void {
|
|
267
|
+
super.processMessage(msg);
|
|
268
|
+
switch (msg.type) {
|
|
269
|
+
case 'fit-request':
|
|
270
|
+
this.onFitRequest(msg);
|
|
271
|
+
break;
|
|
272
|
+
default:
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Set the size of the terminal when attached if dirty.
|
|
279
|
+
*/
|
|
280
|
+
protected onAfterAttach(msg: Message): void {
|
|
281
|
+
this.update();
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Set the size of the terminal when shown if dirty.
|
|
286
|
+
*/
|
|
287
|
+
protected onAfterShow(msg: Message): void {
|
|
288
|
+
this.update();
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* On resize, use the computed row and column sizes to resize the terminal.
|
|
293
|
+
*/
|
|
294
|
+
protected onResize(msg: Widget.ResizeMessage): void {
|
|
295
|
+
this._offsetWidth = msg.width;
|
|
296
|
+
this._offsetHeight = msg.height;
|
|
297
|
+
this._needsResize = true;
|
|
298
|
+
this.update();
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* A message handler invoked on an `'update-request'` message.
|
|
303
|
+
*/
|
|
304
|
+
protected onUpdateRequest(msg: Message): void {
|
|
305
|
+
if (!this.isVisible || !this.isAttached || !this._isReady) {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Open the terminal if necessary.
|
|
310
|
+
if (!this._termOpened) {
|
|
311
|
+
this._term.open(this.node);
|
|
312
|
+
this._term.element?.classList.add(TERMINAL_BODY_CLASS);
|
|
313
|
+
this._termOpened = true;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (this._needsResize) {
|
|
317
|
+
this._resizeTerminal();
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* A message handler invoked on an `'fit-request'` message.
|
|
323
|
+
*/
|
|
324
|
+
protected onFitRequest(msg: Message): void {
|
|
325
|
+
const resize = Widget.ResizeMessage.UnknownSize;
|
|
326
|
+
MessageLoop.sendMessage(this, resize);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Handle `'activate-request'` messages.
|
|
331
|
+
*/
|
|
332
|
+
protected onActivateRequest(msg: Message): void {
|
|
333
|
+
this._term?.focus();
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
private _initialConnection() {
|
|
337
|
+
if (this.isDisposed) {
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (this.session.connectionStatus !== 'connected') {
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
this.title.label = this._trans.__('Terminal %1', this.session.name);
|
|
346
|
+
this._setSessionSize();
|
|
347
|
+
if (this._options.initialCommand) {
|
|
348
|
+
this.session.send({
|
|
349
|
+
type: 'stdin',
|
|
350
|
+
content: [this._options.initialCommand + '\r']
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Only run this initial connection logic once.
|
|
355
|
+
this.session.connectionStatusChanged.disconnect(
|
|
356
|
+
this._initialConnection,
|
|
357
|
+
this
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Initialize the terminal object.
|
|
363
|
+
*/
|
|
364
|
+
private _initializeTerm(): void {
|
|
365
|
+
const term = this._term;
|
|
366
|
+
term.onData((data: string) => {
|
|
367
|
+
if (this.isDisposed) {
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
this.session.send({
|
|
371
|
+
type: 'stdin',
|
|
372
|
+
content: [data]
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
term.onTitleChange((title: string) => {
|
|
377
|
+
this.title.label = title;
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// Do not add any Ctrl+C/Ctrl+V handling on macOS,
|
|
381
|
+
// where Cmd+C/Cmd+V works as intended.
|
|
382
|
+
if (Platform.IS_MAC) {
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
term.attachCustomKeyEventHandler(event => {
|
|
387
|
+
if (event.ctrlKey && event.key === 'c' && term.hasSelection()) {
|
|
388
|
+
// Return so that the usual OS copy happens
|
|
389
|
+
// instead of interrupt signal.
|
|
390
|
+
return false;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (event.ctrlKey && event.key === 'v' && this._options.pasteWithCtrlV) {
|
|
394
|
+
// Return so that the usual paste happens.
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return true;
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Handle a message from the terminal session.
|
|
404
|
+
*/
|
|
405
|
+
private _onMessage(
|
|
406
|
+
sender: TerminalNS.ITerminalConnection,
|
|
407
|
+
msg: TerminalNS.IMessage
|
|
408
|
+
): void {
|
|
409
|
+
switch (msg.type) {
|
|
410
|
+
case 'stdout':
|
|
411
|
+
if (msg.content) {
|
|
412
|
+
this._term.write(msg.content[0] as string);
|
|
413
|
+
}
|
|
414
|
+
break;
|
|
415
|
+
case 'disconnect':
|
|
416
|
+
this._term.write('\r\n\r\n[Finished… Term Session]\r\n');
|
|
417
|
+
break;
|
|
418
|
+
default:
|
|
419
|
+
break;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Resize the terminal based on computed geometry.
|
|
425
|
+
*/
|
|
426
|
+
private _resizeTerminal() {
|
|
427
|
+
if (this._options.autoFit) {
|
|
428
|
+
this._fitAddon.fit();
|
|
429
|
+
}
|
|
430
|
+
if (this._offsetWidth === -1) {
|
|
431
|
+
this._offsetWidth = this.node.offsetWidth;
|
|
432
|
+
}
|
|
433
|
+
if (this._offsetHeight === -1) {
|
|
434
|
+
this._offsetHeight = this.node.offsetHeight;
|
|
435
|
+
}
|
|
436
|
+
this._setSessionSize();
|
|
437
|
+
this._needsResize = false;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Set the size of the terminal in the session.
|
|
442
|
+
*/
|
|
443
|
+
private _setSessionSize(): void {
|
|
444
|
+
const content = [
|
|
445
|
+
this._term.rows,
|
|
446
|
+
this._term.cols,
|
|
447
|
+
this._offsetHeight,
|
|
448
|
+
this._offsetWidth
|
|
449
|
+
];
|
|
450
|
+
if (!this.isDisposed) {
|
|
451
|
+
this.session.send({ type: 'set_size', content });
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
private _setThemeAttribute(theme: string | null | undefined) {
|
|
456
|
+
if (this.isDisposed) {
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
this.node.setAttribute(
|
|
461
|
+
'data-term-theme',
|
|
462
|
+
theme ? theme.toLowerCase() : 'inherit'
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
private _fitAddon: FitAddon;
|
|
467
|
+
private _needsResize = true;
|
|
468
|
+
private _offsetWidth = -1;
|
|
469
|
+
private _offsetHeight = -1;
|
|
470
|
+
private _options: ITerminal.IOptions;
|
|
471
|
+
private _isReady = false;
|
|
472
|
+
private _ready = new PromiseDelegate<void>();
|
|
473
|
+
private _term: Xterm;
|
|
474
|
+
private _termOpened = false;
|
|
475
|
+
private _trans: TranslationBundle;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* A namespace for private data.
|
|
480
|
+
*/
|
|
481
|
+
namespace Private {
|
|
482
|
+
/**
|
|
483
|
+
* An incrementing counter for ids.
|
|
484
|
+
*/
|
|
485
|
+
export let id = 0;
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* The light terminal theme.
|
|
489
|
+
*/
|
|
490
|
+
export const lightTheme: ITerminal.IThemeObject = {
|
|
491
|
+
foreground: '#000',
|
|
492
|
+
background: '#fff',
|
|
493
|
+
cursor: '#616161', // md-grey-700
|
|
494
|
+
cursorAccent: '#F5F5F5', // md-grey-100
|
|
495
|
+
selectionBackground: 'rgba(97, 97, 97, 0.3)', // md-grey-700
|
|
496
|
+
selectionInactiveBackground: 'rgba(189, 189, 189, 0.3)' // md-grey-400
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* The dark terminal theme.
|
|
501
|
+
*/
|
|
502
|
+
export const darkTheme: ITerminal.IThemeObject = {
|
|
503
|
+
foreground: '#fff',
|
|
504
|
+
background: '#000',
|
|
505
|
+
cursor: '#fff',
|
|
506
|
+
cursorAccent: '#000',
|
|
507
|
+
selectionBackground: 'rgba(255, 255, 255, 0.3)',
|
|
508
|
+
selectionInactiveBackground: 'rgba(238, 238, 238, 0.3)' // md-grey-200
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* The current theme.
|
|
513
|
+
*/
|
|
514
|
+
export const inheritTheme = (): ITerminal.IThemeObject => ({
|
|
515
|
+
foreground: getComputedStyle(document.body)
|
|
516
|
+
.getPropertyValue('--jp-ui-font-color0')
|
|
517
|
+
.trim(),
|
|
518
|
+
background: getComputedStyle(document.body)
|
|
519
|
+
.getPropertyValue('--jp-layout-color0')
|
|
520
|
+
.trim(),
|
|
521
|
+
cursor: getComputedStyle(document.body)
|
|
522
|
+
.getPropertyValue('--jp-ui-font-color1')
|
|
523
|
+
.trim(),
|
|
524
|
+
cursorAccent: getComputedStyle(document.body)
|
|
525
|
+
.getPropertyValue('--jp-ui-inverse-font-color0')
|
|
526
|
+
.trim(),
|
|
527
|
+
selectionBackground: getComputedStyle(document.body)
|
|
528
|
+
.getPropertyValue('--jp-layout-color3')
|
|
529
|
+
.trim(),
|
|
530
|
+
selectionInactiveBackground: getComputedStyle(document.body)
|
|
531
|
+
.getPropertyValue('--jp-layout-color2')
|
|
532
|
+
.trim()
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
export function getXTermTheme(
|
|
536
|
+
theme: ITerminal.Theme
|
|
537
|
+
): ITerminal.IThemeObject {
|
|
538
|
+
switch (theme) {
|
|
539
|
+
case 'light':
|
|
540
|
+
return lightTheme;
|
|
541
|
+
case 'dark':
|
|
542
|
+
return darkTheme;
|
|
543
|
+
case 'inherit':
|
|
544
|
+
default:
|
|
545
|
+
return inheritTheme();
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Utility functions for creating a Terminal widget
|
|
552
|
+
*/
|
|
553
|
+
namespace Private {
|
|
554
|
+
let supportWebGL: boolean = false;
|
|
555
|
+
let Xterm_: typeof Xterm;
|
|
556
|
+
let FitAddon_: typeof FitAddon;
|
|
557
|
+
let WeblinksAddon_: typeof WebLinksAddon;
|
|
558
|
+
let Renderer_: typeof CanvasAddon | typeof WebglAddon;
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Detect if the browser supports WebGL or not.
|
|
562
|
+
*
|
|
563
|
+
* Reference: https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/By_example/Detect_WebGL
|
|
564
|
+
*/
|
|
565
|
+
function hasWebGLContext(): boolean {
|
|
566
|
+
// Create canvas element. The canvas is not added to the
|
|
567
|
+
// document itself, so it is never displayed in the
|
|
568
|
+
// browser window.
|
|
569
|
+
const canvas = document.createElement('canvas');
|
|
570
|
+
|
|
571
|
+
// Get WebGLRenderingContext from canvas element.
|
|
572
|
+
const gl =
|
|
573
|
+
canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
|
|
574
|
+
|
|
575
|
+
// Report the result.
|
|
576
|
+
try {
|
|
577
|
+
return gl instanceof WebGLRenderingContext;
|
|
578
|
+
} catch (error) {
|
|
579
|
+
return false;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function addRenderer(term: Xterm): void {
|
|
584
|
+
let renderer = new Renderer_();
|
|
585
|
+
term.loadAddon(renderer);
|
|
586
|
+
if (supportWebGL) {
|
|
587
|
+
(renderer as WebglAddon).onContextLoss(event => {
|
|
588
|
+
console.debug('WebGL context lost - reinitialize Xtermjs renderer.');
|
|
589
|
+
renderer.dispose();
|
|
590
|
+
// If the Webgl context is lost, reinitialize the addon
|
|
591
|
+
addRenderer(term);
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* Create a xterm.js terminal asynchronously.
|
|
598
|
+
*/
|
|
599
|
+
export async function createTerminal(
|
|
600
|
+
options: ITerminalOptions & ITerminalInitOnlyOptions
|
|
601
|
+
): Promise<[Xterm, FitAddon]> {
|
|
602
|
+
if (!Xterm_) {
|
|
603
|
+
supportWebGL = hasWebGLContext();
|
|
604
|
+
const [xterm_, fitAddon_, renderer_, weblinksAddon_] = await Promise.all([
|
|
605
|
+
import('xterm'),
|
|
606
|
+
import('xterm-addon-fit'),
|
|
607
|
+
supportWebGL
|
|
608
|
+
? import('xterm-addon-webgl')
|
|
609
|
+
: import('xterm-addon-canvas'),
|
|
610
|
+
import('xterm-addon-web-links')
|
|
611
|
+
]);
|
|
612
|
+
Xterm_ = xterm_.Terminal;
|
|
613
|
+
FitAddon_ = fitAddon_.FitAddon;
|
|
614
|
+
Renderer_ =
|
|
615
|
+
(renderer_ as any).WebglAddon ?? (renderer_ as any).CanvasAddon;
|
|
616
|
+
WeblinksAddon_ = weblinksAddon_.WebLinksAddon;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
const term = new Xterm_(options);
|
|
620
|
+
addRenderer(term);
|
|
621
|
+
const fitAddon = new FitAddon_();
|
|
622
|
+
term.loadAddon(fitAddon);
|
|
623
|
+
term.loadAddon(new WeblinksAddon_());
|
|
624
|
+
return [term, fitAddon];
|
|
625
|
+
}
|
|
626
|
+
}
|