@difizen/libro-terminal 0.1.0
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/LICENSE +21 -0
- package/README.md +1 -0
- package/es/configuration.js +226 -0
- package/es/connection.d.ts +108 -0
- package/es/connection.d.ts.map +1 -0
- package/es/connection.js +385 -0
- package/es/index.d.ts +2 -0
- package/es/index.d.ts.map +1 -0
- package/es/index.js +1 -0
- package/es/manager.d.ts +104 -0
- package/es/manager.d.ts.map +1 -0
- package/es/manager.js +469 -0
- package/es/module.d.ts +3 -0
- package/es/module.d.ts.map +1 -0
- package/es/module.js +22 -0
- package/es/protocol.d.ts +87 -0
- package/es/protocol.d.ts.map +1 -0
- package/es/protocol.js +16 -0
- package/es/restapi.d.ts +11 -0
- package/es/restapi.d.ts.map +1 -0
- package/es/restapi.js +181 -0
- package/es/theme-service.js +183 -0
- package/es/view.d.ts +85 -0
- package/es/view.d.ts.map +1 -0
- package/es/view.js +317 -0
- package/package.json +63 -0
- package/src/configuration.ts +276 -0
- package/src/connection.ts +347 -0
- package/src/index.spec.ts +8 -0
- package/src/index.ts +1 -0
- package/src/manager.ts +280 -0
- package/src/module.ts +29 -0
- package/src/protocol.ts +102 -0
- package/src/restapi.ts +75 -0
- package/src/theme-service.ts +180 -0
- package/src/view.tsx +327 -0
package/src/protocol.ts
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { JSONPrimitive } from '@difizen/libro-common';
|
|
2
|
+
|
|
3
|
+
import type { TerminalConnection } from './connection.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The server model for a terminal session.
|
|
7
|
+
*/
|
|
8
|
+
export interface TerminalModel {
|
|
9
|
+
/**
|
|
10
|
+
* The name of the terminal session.
|
|
11
|
+
*/
|
|
12
|
+
readonly name: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface TerminalOption {
|
|
16
|
+
name?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Current working directory.
|
|
19
|
+
*/
|
|
20
|
+
cwd?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const TerminalOption = Symbol('TerminalOption');
|
|
24
|
+
|
|
25
|
+
export const TerminalConnectionFactory = Symbol('TerminalConnectionFactory');
|
|
26
|
+
export type TerminalConnectionFactory = (options: TerminalOption) => TerminalConnection;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A message from the terminal session.
|
|
30
|
+
*/
|
|
31
|
+
export interface TerminalMessage {
|
|
32
|
+
/**
|
|
33
|
+
* The type of the message.
|
|
34
|
+
*/
|
|
35
|
+
readonly type: TerminalMessageType;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The content of the message.
|
|
39
|
+
*/
|
|
40
|
+
readonly content?: JSONPrimitive[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Valid message types for the terminal.
|
|
45
|
+
*/
|
|
46
|
+
export type TerminalMessageType = 'stdout' | 'disconnect' | 'set_size' | 'stdin';
|
|
47
|
+
|
|
48
|
+
export type TerminalConnectionStatus = 'connected' | 'connecting' | 'disconnected';
|
|
49
|
+
|
|
50
|
+
export interface TerminalViewOption extends TerminalOption {
|
|
51
|
+
name: string;
|
|
52
|
+
/**
|
|
53
|
+
* Human readable terminal representation on the UI.
|
|
54
|
+
*/
|
|
55
|
+
readonly title?: string;
|
|
56
|
+
|
|
57
|
+
initialCommand?: string;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Path to the executable shell. For example: `/bin/bash`, `bash`, `sh`.
|
|
61
|
+
*/
|
|
62
|
+
readonly shellPath?: string;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Shell arguments to executable shell, for example: [`-l`] - without login.
|
|
66
|
+
*/
|
|
67
|
+
readonly shellArgs?: string[];
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Environment variables for terminal.
|
|
71
|
+
*/
|
|
72
|
+
readonly env?: { [key: string]: string | null };
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* In case `destroyTermOnClose` is true - terminal process will be destroyed on close terminal widget, otherwise will be kept
|
|
76
|
+
* alive.
|
|
77
|
+
*/
|
|
78
|
+
readonly destroyOnClose?: boolean;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Terminal server side can send to the client `terminal title` to display this value on the UI. If
|
|
82
|
+
* useServerTitle = true then display this title, otherwise display title defined by 'title' argument.
|
|
83
|
+
*/
|
|
84
|
+
readonly useServerTitle?: boolean;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Whether it is a pseudo terminal where an extension controls its input and output.
|
|
88
|
+
*/
|
|
89
|
+
readonly isPseudoTerminal?: boolean;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Terminal attributes. Can be useful to apply some implementation specific information.
|
|
93
|
+
*/
|
|
94
|
+
readonly attributes?: { [key: string]: string | null };
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Terminal kind that indicates whether a terminal is created by a user or by some extension for a user
|
|
98
|
+
*/
|
|
99
|
+
readonly kind?: 'user' | string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export const TerminalViewOption = Symbol('TerminalViewOption');
|
package/src/restapi.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { URL } from '@difizen/libro-common';
|
|
2
|
+
import type { ISettings } from '@difizen/libro-kernel';
|
|
3
|
+
import { createResponseError } from '@difizen/libro-kernel';
|
|
4
|
+
import { ServerConnection } from '@difizen/libro-kernel';
|
|
5
|
+
import { inject, singleton } from '@difizen/mana-app';
|
|
6
|
+
|
|
7
|
+
import type { TerminalModel, TerminalOption } from './protocol.js';
|
|
8
|
+
|
|
9
|
+
export const TERMINAL_SERVICE_URL = 'api/terminals';
|
|
10
|
+
|
|
11
|
+
@singleton()
|
|
12
|
+
export class TerminalRestAPI {
|
|
13
|
+
@inject(ServerConnection) serverConnection: ServerConnection;
|
|
14
|
+
|
|
15
|
+
async startNew(
|
|
16
|
+
options: TerminalOption,
|
|
17
|
+
serverSettings?: Partial<ISettings>,
|
|
18
|
+
): Promise<TerminalModel> {
|
|
19
|
+
const settings = { ...this.serverConnection.settings, ...serverSettings };
|
|
20
|
+
const url = URL.join(settings.baseUrl, TERMINAL_SERVICE_URL);
|
|
21
|
+
const init = {
|
|
22
|
+
method: 'POST',
|
|
23
|
+
body: JSON.stringify(options),
|
|
24
|
+
};
|
|
25
|
+
const response = await this.serverConnection.makeRequest(url, init, settings);
|
|
26
|
+
|
|
27
|
+
if (response.status !== 201) {
|
|
28
|
+
const err = await createResponseError(response);
|
|
29
|
+
throw err;
|
|
30
|
+
}
|
|
31
|
+
const data = await response.json();
|
|
32
|
+
return data;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async listRunning(serverSettings?: Partial<ISettings>): Promise<TerminalModel[]> {
|
|
36
|
+
const settings = { ...this.serverConnection.settings, ...serverSettings };
|
|
37
|
+
const url = URL.join(settings.baseUrl, TERMINAL_SERVICE_URL);
|
|
38
|
+
const response = await this.serverConnection.makeRequest(url, {}, settings);
|
|
39
|
+
if (response.status !== 200) {
|
|
40
|
+
const err = await createResponseError(response);
|
|
41
|
+
throw err;
|
|
42
|
+
}
|
|
43
|
+
const data = await response.json();
|
|
44
|
+
|
|
45
|
+
if (!Array.isArray(data)) {
|
|
46
|
+
throw new Error('Invalid Session list');
|
|
47
|
+
}
|
|
48
|
+
return data;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async shutdown(name: string, serverSettings?: Partial<ISettings>): Promise<void> {
|
|
52
|
+
if (!name) {
|
|
53
|
+
console.warn('No terminal name specified');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const settings = { ...this.serverConnection.settings, ...serverSettings };
|
|
57
|
+
const url = URL.join(settings.baseUrl, TERMINAL_SERVICE_URL, name);
|
|
58
|
+
|
|
59
|
+
const init = {
|
|
60
|
+
method: 'DELETE',
|
|
61
|
+
};
|
|
62
|
+
const response = await this.serverConnection.makeRequest(url, init, settings);
|
|
63
|
+
if (response.status === 404) {
|
|
64
|
+
const data = await response.json();
|
|
65
|
+
const msg =
|
|
66
|
+
data.message ?? `The terminal session "${name}"" does not exist on the server`;
|
|
67
|
+
console.warn(msg);
|
|
68
|
+
} else {
|
|
69
|
+
if (response.status !== 204) {
|
|
70
|
+
const err = await createResponseError(response);
|
|
71
|
+
throw err;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import type { ColorDefinition } from '@difizen/mana-app';
|
|
2
|
+
import { ColorRegistry, ThemeService } from '@difizen/mana-app';
|
|
3
|
+
import { inject, singleton } from '@difizen/mana-app';
|
|
4
|
+
import type { ITheme } from 'xterm';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* It should be aligned with https://github.com/microsoft/vscode/blob/0dfa355b3ad185a6289ba28a99c141ab9e72d2be/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts#L40
|
|
8
|
+
*/
|
|
9
|
+
export const terminalAnsiColorMap: {
|
|
10
|
+
[key: string]: { index: number; defaults: ColorDefinition['defaults'] };
|
|
11
|
+
} = {
|
|
12
|
+
'terminal.ansiBlack': {
|
|
13
|
+
index: 0,
|
|
14
|
+
defaults: {
|
|
15
|
+
light: '#000000',
|
|
16
|
+
dark: '#000000',
|
|
17
|
+
hc: '#000000',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
'terminal.ansiRed': {
|
|
21
|
+
index: 1,
|
|
22
|
+
defaults: {
|
|
23
|
+
light: '#cd3131',
|
|
24
|
+
dark: '#cd3131',
|
|
25
|
+
hc: '#cd0000',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
'terminal.ansiGreen': {
|
|
29
|
+
index: 2,
|
|
30
|
+
defaults: {
|
|
31
|
+
light: '#00BC00',
|
|
32
|
+
dark: '#0DBC79',
|
|
33
|
+
hc: '#00cd00',
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
'terminal.ansiYellow': {
|
|
37
|
+
index: 3,
|
|
38
|
+
defaults: {
|
|
39
|
+
light: '#949800',
|
|
40
|
+
dark: '#e5e510',
|
|
41
|
+
hc: '#cdcd00',
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
'terminal.ansiBlue': {
|
|
45
|
+
index: 4,
|
|
46
|
+
defaults: {
|
|
47
|
+
light: '#0451a5',
|
|
48
|
+
dark: '#2472c8',
|
|
49
|
+
hc: '#0000ee',
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
'terminal.ansiMagenta': {
|
|
53
|
+
index: 5,
|
|
54
|
+
defaults: {
|
|
55
|
+
light: '#bc05bc',
|
|
56
|
+
dark: '#bc3fbc',
|
|
57
|
+
hc: '#cd00cd',
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
'terminal.ansiCyan': {
|
|
61
|
+
index: 6,
|
|
62
|
+
defaults: {
|
|
63
|
+
light: '#0598bc',
|
|
64
|
+
dark: '#11a8cd',
|
|
65
|
+
hc: '#00cdcd',
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
'terminal.ansiWhite': {
|
|
69
|
+
index: 7,
|
|
70
|
+
defaults: {
|
|
71
|
+
light: '#555555',
|
|
72
|
+
dark: '#e5e5e5',
|
|
73
|
+
hc: '#e5e5e5',
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
'terminal.ansiBrightBlack': {
|
|
77
|
+
index: 8,
|
|
78
|
+
defaults: {
|
|
79
|
+
light: '#666666',
|
|
80
|
+
dark: '#666666',
|
|
81
|
+
hc: '#7f7f7f',
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
'terminal.ansiBrightRed': {
|
|
85
|
+
index: 9,
|
|
86
|
+
defaults: {
|
|
87
|
+
light: '#cd3131',
|
|
88
|
+
dark: '#f14c4c',
|
|
89
|
+
hc: '#ff0000',
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
'terminal.ansiBrightGreen': {
|
|
93
|
+
index: 10,
|
|
94
|
+
defaults: {
|
|
95
|
+
light: '#14CE14',
|
|
96
|
+
dark: '#23d18b',
|
|
97
|
+
hc: '#00ff00',
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
'terminal.ansiBrightYellow': {
|
|
101
|
+
index: 11,
|
|
102
|
+
defaults: {
|
|
103
|
+
light: '#b5ba00',
|
|
104
|
+
dark: '#f5f543',
|
|
105
|
+
hc: '#ffff00',
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
'terminal.ansiBrightBlue': {
|
|
109
|
+
index: 12,
|
|
110
|
+
defaults: {
|
|
111
|
+
light: '#0451a5',
|
|
112
|
+
dark: '#3b8eea',
|
|
113
|
+
hc: '#5c5cff',
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
'terminal.ansiBrightMagenta': {
|
|
117
|
+
index: 13,
|
|
118
|
+
defaults: {
|
|
119
|
+
light: '#bc05bc',
|
|
120
|
+
dark: '#d670d6',
|
|
121
|
+
hc: '#ff00ff',
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
'terminal.ansiBrightCyan': {
|
|
125
|
+
index: 14,
|
|
126
|
+
defaults: {
|
|
127
|
+
light: '#0598bc',
|
|
128
|
+
dark: '#29b8db',
|
|
129
|
+
hc: '#00ffff',
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
'terminal.ansiBrightWhite': {
|
|
133
|
+
index: 15,
|
|
134
|
+
defaults: {
|
|
135
|
+
light: '#a5a5a5',
|
|
136
|
+
dark: '#e5e5e5',
|
|
137
|
+
hc: '#ffffff',
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
@singleton()
|
|
143
|
+
export class TerminalThemeService {
|
|
144
|
+
@inject(ColorRegistry)
|
|
145
|
+
protected readonly colorRegistry: ColorRegistry;
|
|
146
|
+
|
|
147
|
+
readonly onDidChange = ThemeService.get().onDidColorThemeChange;
|
|
148
|
+
|
|
149
|
+
get theme(): ITheme {
|
|
150
|
+
const foregroundColor = this.colorRegistry.getCurrentColor('terminal.foreground');
|
|
151
|
+
const backgroundColor =
|
|
152
|
+
this.colorRegistry.getCurrentColor('terminal.background') ||
|
|
153
|
+
this.colorRegistry.getCurrentColor('panel.background');
|
|
154
|
+
const cursorColor =
|
|
155
|
+
this.colorRegistry.getCurrentColor('terminalCursor.foreground') ||
|
|
156
|
+
foregroundColor;
|
|
157
|
+
const cursorAccentColor =
|
|
158
|
+
this.colorRegistry.getCurrentColor('terminalCursor.background') ||
|
|
159
|
+
backgroundColor;
|
|
160
|
+
const selectionColor = this.colorRegistry.getCurrentColor(
|
|
161
|
+
'terminal.selectionBackground',
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
const theme: ITheme = {
|
|
165
|
+
background: backgroundColor,
|
|
166
|
+
foreground: foregroundColor,
|
|
167
|
+
cursor: cursorColor,
|
|
168
|
+
cursorAccent: cursorAccentColor,
|
|
169
|
+
selection: selectionColor,
|
|
170
|
+
};
|
|
171
|
+
// eslint-disable-next-line guard-for-in
|
|
172
|
+
for (const id in terminalAnsiColorMap) {
|
|
173
|
+
const colorId = id.substring(13);
|
|
174
|
+
const colorName = colorId.charAt(0).toLowerCase() + colorId.slice(1);
|
|
175
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
176
|
+
(theme as any)[colorName] = this.colorRegistry.getCurrentColor(id);
|
|
177
|
+
}
|
|
178
|
+
return theme;
|
|
179
|
+
}
|
|
180
|
+
}
|
package/src/view.tsx
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import { CodeOutlined } from '@ant-design/icons';
|
|
2
|
+
import { isFirefox } from '@difizen/mana-app';
|
|
3
|
+
import {
|
|
4
|
+
BaseView,
|
|
5
|
+
view,
|
|
6
|
+
transient,
|
|
7
|
+
inject,
|
|
8
|
+
Disposable,
|
|
9
|
+
KeyCode,
|
|
10
|
+
isOSX,
|
|
11
|
+
DisposableCollection,
|
|
12
|
+
Emitter,
|
|
13
|
+
} from '@difizen/mana-app';
|
|
14
|
+
import { forwardRef } from 'react';
|
|
15
|
+
import type { RendererType } from 'xterm';
|
|
16
|
+
import { Terminal } from 'xterm';
|
|
17
|
+
import { FitAddon } from 'xterm-addon-fit';
|
|
18
|
+
|
|
19
|
+
import type { CursorStyle, TerminalRendererType } from './configuration.js';
|
|
20
|
+
import {
|
|
21
|
+
DEFAULT_TERMINAL_RENDERER_TYPE,
|
|
22
|
+
isTerminalRendererType,
|
|
23
|
+
} from './configuration.js';
|
|
24
|
+
import { TerminalConfiguration } from './configuration.js';
|
|
25
|
+
import type { TerminalConnection } from './connection.js';
|
|
26
|
+
import { TerminalManager } from './manager.js';
|
|
27
|
+
import type { TerminalMessage } from './protocol.js';
|
|
28
|
+
import { TerminalViewOption } from './protocol.js';
|
|
29
|
+
import { TerminalThemeService } from './theme-service.js';
|
|
30
|
+
|
|
31
|
+
export const TerminalComponent = forwardRef<HTMLDivElement>(function TerminalComponent(
|
|
32
|
+
_props: { top?: number },
|
|
33
|
+
ref,
|
|
34
|
+
) {
|
|
35
|
+
// const instance = useInject<LibroTerminalView>(ViewInstance);
|
|
36
|
+
return (
|
|
37
|
+
<div tabIndex={1} className="libro-terminal" ref={ref}>
|
|
38
|
+
/
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
@transient()
|
|
44
|
+
@view('libro-terminal-view')
|
|
45
|
+
export class LibroTerminalView extends BaseView {
|
|
46
|
+
protected term: Terminal;
|
|
47
|
+
override view = TerminalComponent;
|
|
48
|
+
protected options: TerminalViewOption;
|
|
49
|
+
protected termOpened = false;
|
|
50
|
+
protected initialData = '';
|
|
51
|
+
protected fitAddon: FitAddon;
|
|
52
|
+
@inject(TerminalConfiguration)
|
|
53
|
+
protected readonly config: TerminalConfiguration;
|
|
54
|
+
@inject(TerminalThemeService)
|
|
55
|
+
protected readonly themeService: TerminalThemeService;
|
|
56
|
+
@inject(TerminalManager)
|
|
57
|
+
protected readonly terminalManager: TerminalManager;
|
|
58
|
+
|
|
59
|
+
protected readonly toDisposeOnConnect = new DisposableCollection();
|
|
60
|
+
|
|
61
|
+
protected readonly onDidOpenEmitter = new Emitter<void>();
|
|
62
|
+
readonly onDidOpen = this.onDidOpenEmitter.event;
|
|
63
|
+
|
|
64
|
+
protected readonly onDidOpenFailureEmitter = new Emitter<void>();
|
|
65
|
+
readonly onDidOpenFailure = this.onDidOpenFailureEmitter.event;
|
|
66
|
+
|
|
67
|
+
protected readonly onSizeChangedEmitter = new Emitter<{
|
|
68
|
+
cols: number;
|
|
69
|
+
rows: number;
|
|
70
|
+
}>();
|
|
71
|
+
readonly onSizeChanged = this.onSizeChangedEmitter.event;
|
|
72
|
+
|
|
73
|
+
protected readonly onDataEmitter = new Emitter<string>();
|
|
74
|
+
readonly onData = this.onDataEmitter.event;
|
|
75
|
+
|
|
76
|
+
protected readonly onKeyEmitter = new Emitter<{
|
|
77
|
+
key: string;
|
|
78
|
+
domEvent: KeyboardEvent;
|
|
79
|
+
}>();
|
|
80
|
+
readonly onKey = this.onKeyEmitter.event;
|
|
81
|
+
protected readonly onDidCloseEmitter = new Emitter<LibroTerminalView>();
|
|
82
|
+
readonly onDidClose = this.onDidCloseEmitter.event;
|
|
83
|
+
|
|
84
|
+
protected connection?: TerminalConnection;
|
|
85
|
+
|
|
86
|
+
constructor(@inject(TerminalViewOption) options: TerminalViewOption) {
|
|
87
|
+
super();
|
|
88
|
+
this.options = options;
|
|
89
|
+
this.title.icon = CodeOutlined;
|
|
90
|
+
this.createTerm();
|
|
91
|
+
this.createConnection();
|
|
92
|
+
if (this.options.destroyOnClose === true) {
|
|
93
|
+
this.toDispose.push(Disposable.create(() => this.term.dispose()));
|
|
94
|
+
}
|
|
95
|
+
this.toDispose.push(
|
|
96
|
+
this.themeService.onDidChange(() =>
|
|
97
|
+
this.term.setOption('theme', this.themeService.theme),
|
|
98
|
+
),
|
|
99
|
+
);
|
|
100
|
+
this.toDispose.push(
|
|
101
|
+
this.term.onTitleChange((title: string) => {
|
|
102
|
+
if (this.options.useServerTitle) {
|
|
103
|
+
this.title.label = title;
|
|
104
|
+
}
|
|
105
|
+
}),
|
|
106
|
+
);
|
|
107
|
+
this.toDispose.push(this.onDidCloseEmitter);
|
|
108
|
+
this.toDispose.push(this.onDidOpenEmitter);
|
|
109
|
+
this.toDispose.push(this.onDidOpenFailureEmitter);
|
|
110
|
+
this.toDispose.push(this.onSizeChangedEmitter);
|
|
111
|
+
this.toDispose.push(this.onDataEmitter);
|
|
112
|
+
this.toDispose.push(this.onKeyEmitter);
|
|
113
|
+
|
|
114
|
+
this.toDispose.push(
|
|
115
|
+
this.term.onResize((data) => {
|
|
116
|
+
this.onSizeChangedEmitter.fire(data);
|
|
117
|
+
}),
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
this.toDispose.push(
|
|
121
|
+
this.term.onData((data) => {
|
|
122
|
+
this.onDataEmitter.fire(data);
|
|
123
|
+
}),
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
this.toDispose.push(
|
|
127
|
+
this.term.onBinary((data) => {
|
|
128
|
+
this.onDataEmitter.fire(data);
|
|
129
|
+
}),
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
this.toDispose.push(
|
|
133
|
+
this.term.onKey((data) => {
|
|
134
|
+
this.onKeyEmitter.fire(data);
|
|
135
|
+
}),
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
protected createConnection = async () => {
|
|
139
|
+
const connection = await this.terminalManager.getOrCreate(this.options);
|
|
140
|
+
this.connection = connection;
|
|
141
|
+
connection.messageReceived(this.onMessage);
|
|
142
|
+
if (this.isDisposed) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
this.initialConnection();
|
|
146
|
+
this.toDispose.push(connection.connectionStatusChanged(this.initialConnection));
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Handle a message from the terminal session.
|
|
151
|
+
*/
|
|
152
|
+
protected onMessage(msg: TerminalMessage): void {
|
|
153
|
+
switch (msg.type) {
|
|
154
|
+
case 'stdout':
|
|
155
|
+
if (msg.content) {
|
|
156
|
+
this.term.write(msg.content[0] as string);
|
|
157
|
+
}
|
|
158
|
+
break;
|
|
159
|
+
case 'disconnect':
|
|
160
|
+
this.term.write('\r\n\r\n[Finished… Term Session]\r\n');
|
|
161
|
+
break;
|
|
162
|
+
default:
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
protected initialConnection() {
|
|
168
|
+
if (this.isDisposed) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (this.connection?.connectionStatus !== 'connected') {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
this.title.label = `Terminal ${this.connection.name}`;
|
|
175
|
+
if (this.connection && this.options.initialCommand) {
|
|
176
|
+
this.connection?.send({
|
|
177
|
+
type: 'stdin',
|
|
178
|
+
content: [this.options.initialCommand + '\r'],
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
protected createTerm = () => {
|
|
184
|
+
const term = new Terminal({
|
|
185
|
+
cursorBlink: this.config.get('terminal.integrated.cursorBlinking'),
|
|
186
|
+
cursorStyle: this.getCursorStyle(),
|
|
187
|
+
cursorWidth: this.config.get('terminal.integrated.cursorWidth'),
|
|
188
|
+
fontFamily: this.config.get('terminal.integrated.fontFamily'),
|
|
189
|
+
fontSize: this.config.get('terminal.integrated.fontSize'),
|
|
190
|
+
fontWeight: this.config.get('terminal.integrated.fontWeight'),
|
|
191
|
+
fontWeightBold: this.config.get('terminal.integrated.fontWeightBold'),
|
|
192
|
+
drawBoldTextInBrightColors: this.config.get(
|
|
193
|
+
'terminal.integrated.drawBoldTextInBrightColors',
|
|
194
|
+
),
|
|
195
|
+
letterSpacing: this.config.get('terminal.integrated.letterSpacing'),
|
|
196
|
+
lineHeight: this.config.get('terminal.integrated.lineHeight'),
|
|
197
|
+
scrollback: this.config.get('terminal.integrated.scrollback'),
|
|
198
|
+
fastScrollSensitivity: this.config.get(
|
|
199
|
+
'terminal.integrated.fastScrollSensitivity',
|
|
200
|
+
),
|
|
201
|
+
rendererType: this.getTerminalRendererType(
|
|
202
|
+
this.config.get('terminal.integrated.rendererType'),
|
|
203
|
+
),
|
|
204
|
+
theme: this.themeService.theme,
|
|
205
|
+
});
|
|
206
|
+
const fitAddon = new FitAddon();
|
|
207
|
+
term.loadAddon(fitAddon);
|
|
208
|
+
this.fitAddon = fitAddon;
|
|
209
|
+
this.term = term;
|
|
210
|
+
this.term.attachCustomKeyEventHandler((e) => this.customKeyHandler(e));
|
|
211
|
+
};
|
|
212
|
+
protected get enableCopy(): boolean {
|
|
213
|
+
return this.config.get('terminal.enableCopy');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
protected get enablePaste(): boolean {
|
|
217
|
+
return this.config.get('terminal.enablePaste');
|
|
218
|
+
}
|
|
219
|
+
protected get copyOnSelection(): boolean {
|
|
220
|
+
return this.config.get('terminal.integrated.copyOnSelection');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
protected customKeyHandler = (event: KeyboardEvent): boolean => {
|
|
224
|
+
const keyBindings = KeyCode.createKeyCode(event).toString();
|
|
225
|
+
const ctrlCmdCopy =
|
|
226
|
+
(isOSX && keyBindings === 'meta+c') || (!isOSX && keyBindings === 'ctrl+c');
|
|
227
|
+
const ctrlCmdPaste =
|
|
228
|
+
(isOSX && keyBindings === 'meta+v') || (!isOSX && keyBindings === 'ctrl+v');
|
|
229
|
+
if (ctrlCmdCopy && this.enableCopy && this.term.hasSelection()) {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
if (ctrlCmdPaste && this.enablePaste) {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
return true;
|
|
236
|
+
};
|
|
237
|
+
/**
|
|
238
|
+
* Get the cursor style compatible with `xterm`.
|
|
239
|
+
* @returns CursorStyle
|
|
240
|
+
*/
|
|
241
|
+
protected getCursorStyle = (): CursorStyle => {
|
|
242
|
+
const value = this.config.get('terminal.integrated.cursorStyle');
|
|
243
|
+
return value === 'line' ? 'bar' : value;
|
|
244
|
+
};
|
|
245
|
+
/**
|
|
246
|
+
* Returns given renderer type if it is valid and supported or default renderer otherwise.
|
|
247
|
+
*
|
|
248
|
+
* @param terminalRendererType desired terminal renderer type
|
|
249
|
+
*/
|
|
250
|
+
protected getTerminalRendererType = (
|
|
251
|
+
terminalRendererType?: string | TerminalRendererType,
|
|
252
|
+
): RendererType => {
|
|
253
|
+
if (terminalRendererType && isTerminalRendererType(terminalRendererType)) {
|
|
254
|
+
return terminalRendererType;
|
|
255
|
+
}
|
|
256
|
+
return DEFAULT_TERMINAL_RENDERER_TYPE;
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
protected resizeTerminal = (): void => {
|
|
260
|
+
const geo = this.fitAddon.proposeDimensions();
|
|
261
|
+
const cols = geo.cols;
|
|
262
|
+
const rows = geo.rows - 1; // subtract one row for margin
|
|
263
|
+
this.term.resize(cols, rows);
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
override onViewResize = (): void => {
|
|
267
|
+
if (!this.isVisible || !this.isAttached) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
this.open();
|
|
271
|
+
this.resizeTerminal();
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
protected open = (): void => {
|
|
275
|
+
if (this.termOpened) {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
const node = this.container?.current;
|
|
279
|
+
if (node) {
|
|
280
|
+
this.term.open(node);
|
|
281
|
+
}
|
|
282
|
+
if (this.initialData) {
|
|
283
|
+
this.term.write(this.initialData);
|
|
284
|
+
}
|
|
285
|
+
this.termOpened = true;
|
|
286
|
+
this.initialData = '';
|
|
287
|
+
|
|
288
|
+
if (isFirefox) {
|
|
289
|
+
// The software scrollbars don't work with xterm.js, so we disable the scrollbar if we are on firefox.
|
|
290
|
+
if (this.term.element) {
|
|
291
|
+
(this.term.element?.children.item(0) as HTMLElement).style.overflow = 'hidden';
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
scrollLineUp = (): void => {
|
|
297
|
+
this.term.scrollLines(-1);
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
scrollLineDown = (): void => {
|
|
301
|
+
this.term.scrollLines(1);
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
scrollToTop = (): void => {
|
|
305
|
+
this.term.scrollToTop();
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
scrollToBottom = (): void => {
|
|
309
|
+
this.term.scrollToBottom();
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
scrollPageUp = (): void => {
|
|
313
|
+
this.term.scrollPages(-1);
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
scrollPageDown = (): void => {
|
|
317
|
+
this.term.scrollPages(1);
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
resetTerminal = (): void => {
|
|
321
|
+
this.term.reset();
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
writeLine = (text: string): void => {
|
|
325
|
+
this.term.writeln(text);
|
|
326
|
+
};
|
|
327
|
+
}
|