@theia/plugin-dev 1.45.1 → 1.46.0-next.72
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/README.md +31 -31
- package/lib/browser/hosted-plugin-controller.d.ts +74 -74
- package/lib/browser/hosted-plugin-controller.js +352 -352
- package/lib/browser/hosted-plugin-frontend-contribution.d.ts +6 -6
- package/lib/browser/hosted-plugin-frontend-contribution.js +56 -56
- package/lib/browser/hosted-plugin-informer.d.ts +25 -25
- package/lib/browser/hosted-plugin-informer.js +102 -102
- package/lib/browser/hosted-plugin-log-viewer.d.ts +14 -14
- package/lib/browser/hosted-plugin-log-viewer.js +69 -69
- package/lib/browser/hosted-plugin-manager-client.d.ts +80 -80
- package/lib/browser/hosted-plugin-manager-client.js +410 -410
- package/lib/browser/hosted-plugin-preferences.d.ts +13 -13
- package/lib/browser/hosted-plugin-preferences.js +60 -60
- package/lib/browser/plugin-dev-frontend-module.d.ts +3 -3
- package/lib/browser/plugin-dev-frontend-module.js +42 -42
- package/lib/common/index.d.ts +2 -2
- package/lib/common/index.js +31 -31
- package/lib/common/plugin-dev-protocol.d.ts +24 -24
- package/lib/common/plugin-dev-protocol.js +20 -20
- package/lib/node/hosted-instance-manager.d.ts +104 -104
- package/lib/node/hosted-instance-manager.js +320 -320
- package/lib/node/hosted-instance-manager.js.map +1 -1
- package/lib/node/hosted-plugin-reader.d.ts +11 -11
- package/lib/node/hosted-plugin-reader.js +68 -68
- package/lib/node/hosted-plugin-reader.js.map +1 -1
- package/lib/node/hosted-plugin-uri-postprocessor.d.ts +6 -6
- package/lib/node/hosted-plugin-uri-postprocessor.js +27 -27
- package/lib/node/hosted-plugins-manager.d.ts +41 -41
- package/lib/node/hosted-plugins-manager.js +119 -119
- package/lib/node/plugin-dev-backend-module.d.ts +4 -4
- package/lib/node/plugin-dev-backend-module.js +53 -53
- package/lib/node/plugin-dev-service.d.ts +25 -25
- package/lib/node/plugin-dev-service.js +108 -108
- package/lib/node-electron/plugin-dev-electron-backend-module.d.ts +3 -3
- package/lib/node-electron/plugin-dev-electron-backend-module.js +28 -28
- package/lib/package.spec.js +25 -25
- package/package.json +9 -9
- package/src/browser/hosted-plugin-controller.ts +356 -356
- package/src/browser/hosted-plugin-frontend-contribution.ts +45 -45
- package/src/browser/hosted-plugin-informer.ts +93 -93
- package/src/browser/hosted-plugin-log-viewer.ts +52 -52
- package/src/browser/hosted-plugin-manager-client.ts +430 -430
- package/src/browser/hosted-plugin-preferences.ts +71 -71
- package/src/browser/plugin-dev-frontend-module.ts +45 -45
- package/src/common/index.ts +21 -21
- package/src/common/plugin-dev-protocol.ts +45 -45
- package/src/node/hosted-instance-manager.ts +382 -382
- package/src/node/hosted-plugin-reader.ts +58 -58
- package/src/node/hosted-plugin-uri-postprocessor.ts +32 -32
- package/src/node/hosted-plugins-manager.ts +146 -146
- package/src/node/plugin-dev-backend-module.ts +54 -54
- package/src/node/plugin-dev-service.ts +107 -107
- package/src/node-electron/plugin-dev-electron-backend-module.ts +29 -29
- package/src/package.spec.ts +28 -28
|
@@ -1,430 +1,430 @@
|
|
|
1
|
-
// *****************************************************************************
|
|
2
|
-
// Copyright (C) 2018 Red Hat, Inc. and others.
|
|
3
|
-
//
|
|
4
|
-
// This program and the accompanying materials are made available under the
|
|
5
|
-
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
-
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
-
//
|
|
8
|
-
// This Source Code may also be made available under the following Secondary
|
|
9
|
-
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
-
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
-
// with the GNU Classpath Exception which is available at
|
|
12
|
-
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
-
//
|
|
14
|
-
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
-
// *****************************************************************************
|
|
16
|
-
|
|
17
|
-
import { injectable, inject, postConstruct } from '@theia/core/shared/inversify';
|
|
18
|
-
import URI from '@theia/core/lib/common/uri';
|
|
19
|
-
import { Path } from '@theia/core/lib/common/path';
|
|
20
|
-
import { MessageService, Command, Emitter, Event } from '@theia/core/lib/common';
|
|
21
|
-
import { LabelProvider, isNative, AbstractDialog } from '@theia/core/lib/browser';
|
|
22
|
-
import { WindowService } from '@theia/core/lib/browser/window/window-service';
|
|
23
|
-
import { WorkspaceService } from '@theia/workspace/lib/browser';
|
|
24
|
-
import { FileDialogService } from '@theia/filesystem/lib/browser';
|
|
25
|
-
import { PluginDebugConfiguration, PluginDevServer } from '../common/plugin-dev-protocol';
|
|
26
|
-
import { LaunchVSCodeArgument, LaunchVSCodeRequest, LaunchVSCodeResult } from '@theia/debug/lib/browser/debug-contribution';
|
|
27
|
-
import { DebugSessionManager } from '@theia/debug/lib/browser/debug-session-manager';
|
|
28
|
-
import { HostedPluginPreferences } from './hosted-plugin-preferences';
|
|
29
|
-
import { FileService } from '@theia/filesystem/lib/browser/file-service';
|
|
30
|
-
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
|
|
31
|
-
import { DebugSessionConnection } from '@theia/debug/lib/browser/debug-session-connection';
|
|
32
|
-
import { nls } from '@theia/core/lib/common/nls';
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Commands to control Hosted plugin instances.
|
|
36
|
-
*/
|
|
37
|
-
export namespace HostedPluginCommands {
|
|
38
|
-
const HOSTED_PLUGIN_CATEGORY_KEY = 'theia/plugin-dev/hostedPlugin';
|
|
39
|
-
const HOSTED_PLUGIN_CATEGORY = 'Hosted Plugin';
|
|
40
|
-
export const START = Command.toLocalizedCommand({
|
|
41
|
-
id: 'hosted-plugin:start',
|
|
42
|
-
category: HOSTED_PLUGIN_CATEGORY,
|
|
43
|
-
label: 'Start Instance'
|
|
44
|
-
}, 'theia/plugin-dev/startInstance', HOSTED_PLUGIN_CATEGORY_KEY);
|
|
45
|
-
|
|
46
|
-
export const DEBUG = Command.toLocalizedCommand({
|
|
47
|
-
id: 'hosted-plugin:debug',
|
|
48
|
-
category: HOSTED_PLUGIN_CATEGORY,
|
|
49
|
-
label: 'Debug Instance'
|
|
50
|
-
}, 'theia/plugin-dev/debugInstance', HOSTED_PLUGIN_CATEGORY_KEY);
|
|
51
|
-
|
|
52
|
-
export const STOP = Command.toLocalizedCommand({
|
|
53
|
-
id: 'hosted-plugin:stop',
|
|
54
|
-
category: HOSTED_PLUGIN_CATEGORY,
|
|
55
|
-
label: 'Stop Instance'
|
|
56
|
-
}, 'theia/plugin-dev/stopInstance', HOSTED_PLUGIN_CATEGORY_KEY);
|
|
57
|
-
|
|
58
|
-
export const RESTART = Command.toLocalizedCommand({
|
|
59
|
-
id: 'hosted-plugin:restart',
|
|
60
|
-
category: HOSTED_PLUGIN_CATEGORY,
|
|
61
|
-
label: 'Restart Instance'
|
|
62
|
-
}, 'theia/plugin-dev/restartInstance', HOSTED_PLUGIN_CATEGORY_KEY);
|
|
63
|
-
|
|
64
|
-
export const SELECT_PATH = Command.toLocalizedCommand({
|
|
65
|
-
id: 'hosted-plugin:select-path',
|
|
66
|
-
category: HOSTED_PLUGIN_CATEGORY,
|
|
67
|
-
label: 'Select Path'
|
|
68
|
-
}, 'theia/plugin-dev/selectPath', HOSTED_PLUGIN_CATEGORY_KEY);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Available states of hosted plugin instance.
|
|
73
|
-
*/
|
|
74
|
-
export enum HostedInstanceState {
|
|
75
|
-
STOPPED = 'stopped',
|
|
76
|
-
STARTING = 'starting',
|
|
77
|
-
RUNNING = 'running',
|
|
78
|
-
STOPPING = 'stopping',
|
|
79
|
-
FAILED = 'failed'
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export interface HostedInstanceData {
|
|
83
|
-
state: HostedInstanceState;
|
|
84
|
-
pluginLocation: URI;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Responsible for UI to set up and control Hosted Plugin Instance.
|
|
89
|
-
*/
|
|
90
|
-
@injectable()
|
|
91
|
-
export class HostedPluginManagerClient {
|
|
92
|
-
private openNewTabAskDialog: OpenHostedInstanceLinkDialog;
|
|
93
|
-
|
|
94
|
-
private connection: DebugSessionConnection;
|
|
95
|
-
|
|
96
|
-
// path to the plugin on the file system
|
|
97
|
-
protected pluginLocation: URI | undefined;
|
|
98
|
-
|
|
99
|
-
// URL to the running plugin instance
|
|
100
|
-
protected pluginInstanceURL: string | undefined;
|
|
101
|
-
|
|
102
|
-
protected isDebug = false;
|
|
103
|
-
|
|
104
|
-
protected readonly stateChanged = new Emitter<HostedInstanceData>();
|
|
105
|
-
|
|
106
|
-
get onStateChanged(): Event<HostedInstanceData> {
|
|
107
|
-
return this.stateChanged.event;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
@inject(PluginDevServer)
|
|
111
|
-
protected readonly hostedPluginServer: PluginDevServer;
|
|
112
|
-
@inject(MessageService)
|
|
113
|
-
protected readonly messageService: MessageService;
|
|
114
|
-
@inject(LabelProvider)
|
|
115
|
-
protected readonly labelProvider: LabelProvider;
|
|
116
|
-
@inject(WindowService)
|
|
117
|
-
protected readonly windowService: WindowService;
|
|
118
|
-
@inject(FileService)
|
|
119
|
-
protected readonly fileService: FileService;
|
|
120
|
-
@inject(EnvVariablesServer)
|
|
121
|
-
protected readonly environments: EnvVariablesServer;
|
|
122
|
-
@inject(WorkspaceService)
|
|
123
|
-
protected readonly workspaceService: WorkspaceService;
|
|
124
|
-
@inject(DebugSessionManager)
|
|
125
|
-
protected readonly debugSessionManager: DebugSessionManager;
|
|
126
|
-
@inject(HostedPluginPreferences)
|
|
127
|
-
protected readonly hostedPluginPreferences: HostedPluginPreferences;
|
|
128
|
-
@inject(FileDialogService)
|
|
129
|
-
protected readonly fileDialogService: FileDialogService;
|
|
130
|
-
|
|
131
|
-
@postConstruct()
|
|
132
|
-
protected init(): void {
|
|
133
|
-
this.doInit();
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
protected async doInit(): Promise<void> {
|
|
137
|
-
this.openNewTabAskDialog = new OpenHostedInstanceLinkDialog(this.windowService);
|
|
138
|
-
|
|
139
|
-
// is needed for case when page is loaded when hosted instance is already running.
|
|
140
|
-
if (await this.hostedPluginServer.isHostedPluginInstanceRunning()) {
|
|
141
|
-
this.pluginLocation = new URI(await this.hostedPluginServer.getHostedPluginURI());
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
get lastPluginLocation(): string | undefined {
|
|
146
|
-
if (this.pluginLocation) {
|
|
147
|
-
return this.pluginLocation.toString();
|
|
148
|
-
}
|
|
149
|
-
return undefined;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
async start(debugConfig?: PluginDebugConfiguration): Promise<void> {
|
|
153
|
-
if (await this.hostedPluginServer.isHostedPluginInstanceRunning()) {
|
|
154
|
-
this.messageService.warn(nls.localize('theia/plugin-dev/alreadyRunning', 'Hosted instance is already running.'));
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
if (!this.pluginLocation) {
|
|
159
|
-
await this.selectPluginPath();
|
|
160
|
-
if (!this.pluginLocation) {
|
|
161
|
-
// selection was cancelled
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
try {
|
|
167
|
-
this.stateChanged.fire({ state: HostedInstanceState.STARTING, pluginLocation: this.pluginLocation });
|
|
168
|
-
this.messageService.info(nls.localize('theia/plugin-dev/starting', 'Starting hosted instance server ...'));
|
|
169
|
-
|
|
170
|
-
if (debugConfig) {
|
|
171
|
-
this.isDebug = true;
|
|
172
|
-
this.pluginInstanceURL = await this.hostedPluginServer.runDebugHostedPluginInstance(this.pluginLocation.toString(), debugConfig);
|
|
173
|
-
} else {
|
|
174
|
-
this.isDebug = false;
|
|
175
|
-
this.pluginInstanceURL = await this.hostedPluginServer.runHostedPluginInstance(this.pluginLocation.toString());
|
|
176
|
-
}
|
|
177
|
-
await this.openPluginWindow();
|
|
178
|
-
|
|
179
|
-
this.messageService.info(`${nls.localize('theia/plugin-dev/running', 'Hosted instance is running at:')} ${this.pluginInstanceURL}`);
|
|
180
|
-
this.stateChanged.fire({ state: HostedInstanceState.RUNNING, pluginLocation: this.pluginLocation });
|
|
181
|
-
} catch (error) {
|
|
182
|
-
this.messageService.error(nls.localize('theia/plugin-dev/failed', 'Failed to run hosted plugin instance: {0}', this.getErrorMessage(error)));
|
|
183
|
-
this.stateChanged.fire({ state: HostedInstanceState.FAILED, pluginLocation: this.pluginLocation });
|
|
184
|
-
this.stop();
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
async debug(config?: PluginDebugConfiguration): Promise<string | undefined> {
|
|
189
|
-
await this.start(this.setDebugConfig(config));
|
|
190
|
-
await this.startDebugSessionManager();
|
|
191
|
-
|
|
192
|
-
return this.pluginInstanceURL;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
async startDebugSessionManager(): Promise<void> {
|
|
196
|
-
let outFiles: string[] | undefined = undefined;
|
|
197
|
-
if (this.pluginLocation && this.hostedPluginPreferences['hosted-plugin.launchOutFiles'].length > 0) {
|
|
198
|
-
const fsPath = await this.fileService.fsPath(this.pluginLocation);
|
|
199
|
-
if (fsPath) {
|
|
200
|
-
outFiles = this.hostedPluginPreferences['hosted-plugin.launchOutFiles'].map(outFile =>
|
|
201
|
-
outFile.replace('${pluginPath}', new Path(fsPath).toString())
|
|
202
|
-
);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
const name = nls.localize('theia/plugin-dev/hostedPlugin', 'Hosted Plugin');
|
|
206
|
-
await this.debugSessionManager.start({
|
|
207
|
-
name,
|
|
208
|
-
configuration: {
|
|
209
|
-
type: 'node',
|
|
210
|
-
request: 'attach',
|
|
211
|
-
timeout: 30000,
|
|
212
|
-
name,
|
|
213
|
-
smartStep: true,
|
|
214
|
-
sourceMaps: !!outFiles,
|
|
215
|
-
outFiles
|
|
216
|
-
}
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
async stop(checkRunning: boolean = true): Promise<void> {
|
|
221
|
-
if (checkRunning && !await this.hostedPluginServer.isHostedPluginInstanceRunning()) {
|
|
222
|
-
this.messageService.warn(nls.localize('theia/plugin-dev/notRunning', 'Hosted instance is not running.'));
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
try {
|
|
226
|
-
this.stateChanged.fire({ state: HostedInstanceState.STOPPING, pluginLocation: this.pluginLocation! });
|
|
227
|
-
await this.hostedPluginServer.terminateHostedPluginInstance();
|
|
228
|
-
this.messageService.info((this.pluginInstanceURL
|
|
229
|
-
? nls.localize('theia/plugin-dev/instanceTerminated', '{0} has been terminated', this.pluginInstanceURL)
|
|
230
|
-
: nls.localize('theia/plugin-dev/unknownTerminated', 'The instance has been terminated')));
|
|
231
|
-
this.stateChanged.fire({ state: HostedInstanceState.STOPPED, pluginLocation: this.pluginLocation! });
|
|
232
|
-
} catch (error) {
|
|
233
|
-
this.messageService.error(this.getErrorMessage(error));
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
async restart(): Promise<void> {
|
|
238
|
-
if (await this.hostedPluginServer.isHostedPluginInstanceRunning()) {
|
|
239
|
-
await this.stop(false);
|
|
240
|
-
|
|
241
|
-
this.messageService.info(nls.localize('theia/plugin-dev/starting', 'Starting hosted instance server ...'));
|
|
242
|
-
|
|
243
|
-
// It takes some time before OS released all resources e.g. port.
|
|
244
|
-
// Keep trying to run hosted instance with delay.
|
|
245
|
-
this.stateChanged.fire({ state: HostedInstanceState.STARTING, pluginLocation: this.pluginLocation! });
|
|
246
|
-
let lastError;
|
|
247
|
-
for (let tries = 0; tries < 15; tries++) {
|
|
248
|
-
try {
|
|
249
|
-
if (this.isDebug) {
|
|
250
|
-
this.pluginInstanceURL = await this.hostedPluginServer.runDebugHostedPluginInstance(this.pluginLocation!.toString(), {
|
|
251
|
-
debugMode: this.hostedPluginPreferences['hosted-plugin.debugMode']
|
|
252
|
-
});
|
|
253
|
-
await this.startDebugSessionManager();
|
|
254
|
-
} else {
|
|
255
|
-
this.pluginInstanceURL = await this.hostedPluginServer.runHostedPluginInstance(this.pluginLocation!.toString());
|
|
256
|
-
}
|
|
257
|
-
await this.openPluginWindow();
|
|
258
|
-
this.messageService.info(`${nls.localize('theia/plugin-dev/running', 'Hosted instance is running at:')} ${this.pluginInstanceURL}`);
|
|
259
|
-
this.stateChanged.fire({
|
|
260
|
-
state: HostedInstanceState.RUNNING,
|
|
261
|
-
pluginLocation: this.pluginLocation!
|
|
262
|
-
});
|
|
263
|
-
return;
|
|
264
|
-
} catch (error) {
|
|
265
|
-
lastError = error;
|
|
266
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
this.messageService.error(nls.localize('theia/plugin-dev/failed', 'Failed to run hosted plugin instance: {0}', this.getErrorMessage(lastError)));
|
|
270
|
-
this.stateChanged.fire({ state: HostedInstanceState.FAILED, pluginLocation: this.pluginLocation! });
|
|
271
|
-
this.stop();
|
|
272
|
-
} else {
|
|
273
|
-
this.messageService.warn(nls.localize('theia/plugin-dev/notRunning', 'Hosted instance is not running.'));
|
|
274
|
-
this.start();
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* Creates directory choose dialog and set selected folder into pluginLocation field.
|
|
280
|
-
*/
|
|
281
|
-
async selectPluginPath(): Promise<void> {
|
|
282
|
-
const workspaceFolder = (await this.workspaceService.roots)[0] || await this.fileService.resolve(new URI(await this.environments.getHomeDirUri()));
|
|
283
|
-
if (!workspaceFolder) {
|
|
284
|
-
throw new Error('Unable to find the root');
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
const result = await this.fileDialogService.showOpenDialog({
|
|
288
|
-
title: HostedPluginCommands.SELECT_PATH.label!,
|
|
289
|
-
openLabel: nls.localize('theia/plugin-dev/select', 'Select'),
|
|
290
|
-
canSelectFiles: false,
|
|
291
|
-
canSelectFolders: true,
|
|
292
|
-
canSelectMany: false
|
|
293
|
-
}, workspaceFolder);
|
|
294
|
-
|
|
295
|
-
if (result) {
|
|
296
|
-
if (await this.hostedPluginServer.isPluginValid(result.toString())) {
|
|
297
|
-
this.pluginLocation = result;
|
|
298
|
-
this.messageService.info(nls.localize('theia/plugin-dev/pluginFolder', 'Plugin folder is set to: {0}', this.labelProvider.getLongName(result)));
|
|
299
|
-
} else {
|
|
300
|
-
this.messageService.error(nls.localize('theia/plugin-dev/noValidPlugin', 'Specified folder does not contain valid plugin.'));
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
register(configType: string, connection: DebugSessionConnection): void {
|
|
306
|
-
if (configType === 'pwa-extensionHost') {
|
|
307
|
-
this.connection = connection;
|
|
308
|
-
this.connection.onRequest('launchVSCode', (request: LaunchVSCodeRequest) => this.launchVSCode(request));
|
|
309
|
-
|
|
310
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
311
|
-
this.connection.on('exited', async (args: any) => {
|
|
312
|
-
await this.stop();
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* Opens window with URL to the running plugin instance.
|
|
319
|
-
*/
|
|
320
|
-
protected async openPluginWindow(): Promise<void> {
|
|
321
|
-
// do nothing for electron browser
|
|
322
|
-
if (isNative) {
|
|
323
|
-
return;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
if (this.pluginInstanceURL) {
|
|
327
|
-
try {
|
|
328
|
-
this.windowService.openNewWindow(this.pluginInstanceURL);
|
|
329
|
-
} catch (err) {
|
|
330
|
-
// browser blocked opening of a new tab
|
|
331
|
-
this.openNewTabAskDialog.showOpenNewTabAskDialog(this.pluginInstanceURL);
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
protected async launchVSCode({ arguments: { args } }: LaunchVSCodeRequest): Promise<LaunchVSCodeResult> {
|
|
337
|
-
let result = {};
|
|
338
|
-
let instanceURI;
|
|
339
|
-
|
|
340
|
-
const sessions = this.debugSessionManager.sessions.filter(session => session.id !== this.connection.sessionId);
|
|
341
|
-
|
|
342
|
-
/* if `launchVSCode` is invoked and sessions do not exist - it means that `start` debug was invoked.
|
|
343
|
-
if `launchVSCode` is invoked and sessions do exist - it means that `restartSessions()` was invoked,
|
|
344
|
-
which invoked `this.sendRequest('restart', {})`, which restarted `vscode-builtin-js-debug` plugin which is
|
|
345
|
-
connected to first session (sessions[0]), which means that other existing (child) sessions need to be terminated
|
|
346
|
-
and new ones will be created by running `startDebugSessionManager()`
|
|
347
|
-
*/
|
|
348
|
-
if (sessions.length > 0) {
|
|
349
|
-
sessions.forEach(session => this.debugSessionManager.terminateSession(session));
|
|
350
|
-
await this.startDebugSessionManager();
|
|
351
|
-
instanceURI = this.pluginInstanceURL;
|
|
352
|
-
} else {
|
|
353
|
-
instanceURI = await this.debug(this.getDebugPluginConfig(args));
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
if (instanceURI) {
|
|
357
|
-
const instanceURL = new URL(instanceURI);
|
|
358
|
-
if (instanceURL.port) {
|
|
359
|
-
result = Object.assign(result, { rendererDebugPort: instanceURL.port });
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
return result;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
366
|
-
protected getErrorMessage(error: any): string {
|
|
367
|
-
return error?.message?.substring(error.message.indexOf(':') + 1) || '';
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
private setDebugConfig(config?: PluginDebugConfiguration): PluginDebugConfiguration {
|
|
371
|
-
config = Object.assign(config || {}, { debugMode: this.hostedPluginPreferences['hosted-plugin.debugMode'] });
|
|
372
|
-
if (config.pluginLocation) {
|
|
373
|
-
this.pluginLocation = new URI((!config.pluginLocation.startsWith('/') ? '/' : '') + config.pluginLocation.replace(/\\/g, '/')).withScheme('file');
|
|
374
|
-
}
|
|
375
|
-
return config;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
private getDebugPluginConfig(args: LaunchVSCodeArgument[]): PluginDebugConfiguration {
|
|
379
|
-
let pluginLocation;
|
|
380
|
-
for (const arg of args) {
|
|
381
|
-
if (arg?.prefix === '--extensionDevelopmentPath=') {
|
|
382
|
-
pluginLocation = arg.path;
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
return {
|
|
387
|
-
pluginLocation
|
|
388
|
-
};
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
class OpenHostedInstanceLinkDialog extends AbstractDialog<string> {
|
|
393
|
-
protected readonly windowService: WindowService;
|
|
394
|
-
protected readonly openButton: HTMLButtonElement;
|
|
395
|
-
protected readonly messageNode: HTMLDivElement;
|
|
396
|
-
protected readonly linkNode: HTMLAnchorElement;
|
|
397
|
-
value: string;
|
|
398
|
-
|
|
399
|
-
constructor(windowService: WindowService) {
|
|
400
|
-
super({
|
|
401
|
-
title: nls.localize('theia/plugin-dev/preventedNewTab', 'Your browser prevented opening of a new tab')
|
|
402
|
-
});
|
|
403
|
-
this.windowService = windowService;
|
|
404
|
-
|
|
405
|
-
this.linkNode = document.createElement('a');
|
|
406
|
-
this.linkNode.target = '_blank';
|
|
407
|
-
this.linkNode.setAttribute('style', 'color: var(--theia-editorWidget-foreground);');
|
|
408
|
-
this.contentNode.appendChild(this.linkNode);
|
|
409
|
-
|
|
410
|
-
const messageNode = document.createElement('div');
|
|
411
|
-
messageNode.innerText = nls.localize('theia/plugin-dev/running', 'Hosted instance is running at:') + ' ';
|
|
412
|
-
messageNode.appendChild(this.linkNode);
|
|
413
|
-
this.contentNode.appendChild(messageNode);
|
|
414
|
-
|
|
415
|
-
this.appendCloseButton();
|
|
416
|
-
this.openButton = this.appendAcceptButton(nls.localizeByDefault('Open'));
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
showOpenNewTabAskDialog(uri: string): void {
|
|
420
|
-
this.value = uri;
|
|
421
|
-
|
|
422
|
-
this.linkNode.textContent = uri;
|
|
423
|
-
this.linkNode.href = uri;
|
|
424
|
-
this.openButton.onclick = () => {
|
|
425
|
-
this.windowService.openNewWindow(uri);
|
|
426
|
-
};
|
|
427
|
-
|
|
428
|
-
this.open();
|
|
429
|
-
}
|
|
430
|
-
}
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2018 Red Hat, Inc. and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import { injectable, inject, postConstruct } from '@theia/core/shared/inversify';
|
|
18
|
+
import URI from '@theia/core/lib/common/uri';
|
|
19
|
+
import { Path } from '@theia/core/lib/common/path';
|
|
20
|
+
import { MessageService, Command, Emitter, Event } from '@theia/core/lib/common';
|
|
21
|
+
import { LabelProvider, isNative, AbstractDialog } from '@theia/core/lib/browser';
|
|
22
|
+
import { WindowService } from '@theia/core/lib/browser/window/window-service';
|
|
23
|
+
import { WorkspaceService } from '@theia/workspace/lib/browser';
|
|
24
|
+
import { FileDialogService } from '@theia/filesystem/lib/browser';
|
|
25
|
+
import { PluginDebugConfiguration, PluginDevServer } from '../common/plugin-dev-protocol';
|
|
26
|
+
import { LaunchVSCodeArgument, LaunchVSCodeRequest, LaunchVSCodeResult } from '@theia/debug/lib/browser/debug-contribution';
|
|
27
|
+
import { DebugSessionManager } from '@theia/debug/lib/browser/debug-session-manager';
|
|
28
|
+
import { HostedPluginPreferences } from './hosted-plugin-preferences';
|
|
29
|
+
import { FileService } from '@theia/filesystem/lib/browser/file-service';
|
|
30
|
+
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
|
|
31
|
+
import { DebugSessionConnection } from '@theia/debug/lib/browser/debug-session-connection';
|
|
32
|
+
import { nls } from '@theia/core/lib/common/nls';
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Commands to control Hosted plugin instances.
|
|
36
|
+
*/
|
|
37
|
+
export namespace HostedPluginCommands {
|
|
38
|
+
const HOSTED_PLUGIN_CATEGORY_KEY = 'theia/plugin-dev/hostedPlugin';
|
|
39
|
+
const HOSTED_PLUGIN_CATEGORY = 'Hosted Plugin';
|
|
40
|
+
export const START = Command.toLocalizedCommand({
|
|
41
|
+
id: 'hosted-plugin:start',
|
|
42
|
+
category: HOSTED_PLUGIN_CATEGORY,
|
|
43
|
+
label: 'Start Instance'
|
|
44
|
+
}, 'theia/plugin-dev/startInstance', HOSTED_PLUGIN_CATEGORY_KEY);
|
|
45
|
+
|
|
46
|
+
export const DEBUG = Command.toLocalizedCommand({
|
|
47
|
+
id: 'hosted-plugin:debug',
|
|
48
|
+
category: HOSTED_PLUGIN_CATEGORY,
|
|
49
|
+
label: 'Debug Instance'
|
|
50
|
+
}, 'theia/plugin-dev/debugInstance', HOSTED_PLUGIN_CATEGORY_KEY);
|
|
51
|
+
|
|
52
|
+
export const STOP = Command.toLocalizedCommand({
|
|
53
|
+
id: 'hosted-plugin:stop',
|
|
54
|
+
category: HOSTED_PLUGIN_CATEGORY,
|
|
55
|
+
label: 'Stop Instance'
|
|
56
|
+
}, 'theia/plugin-dev/stopInstance', HOSTED_PLUGIN_CATEGORY_KEY);
|
|
57
|
+
|
|
58
|
+
export const RESTART = Command.toLocalizedCommand({
|
|
59
|
+
id: 'hosted-plugin:restart',
|
|
60
|
+
category: HOSTED_PLUGIN_CATEGORY,
|
|
61
|
+
label: 'Restart Instance'
|
|
62
|
+
}, 'theia/plugin-dev/restartInstance', HOSTED_PLUGIN_CATEGORY_KEY);
|
|
63
|
+
|
|
64
|
+
export const SELECT_PATH = Command.toLocalizedCommand({
|
|
65
|
+
id: 'hosted-plugin:select-path',
|
|
66
|
+
category: HOSTED_PLUGIN_CATEGORY,
|
|
67
|
+
label: 'Select Path'
|
|
68
|
+
}, 'theia/plugin-dev/selectPath', HOSTED_PLUGIN_CATEGORY_KEY);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Available states of hosted plugin instance.
|
|
73
|
+
*/
|
|
74
|
+
export enum HostedInstanceState {
|
|
75
|
+
STOPPED = 'stopped',
|
|
76
|
+
STARTING = 'starting',
|
|
77
|
+
RUNNING = 'running',
|
|
78
|
+
STOPPING = 'stopping',
|
|
79
|
+
FAILED = 'failed'
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface HostedInstanceData {
|
|
83
|
+
state: HostedInstanceState;
|
|
84
|
+
pluginLocation: URI;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Responsible for UI to set up and control Hosted Plugin Instance.
|
|
89
|
+
*/
|
|
90
|
+
@injectable()
|
|
91
|
+
export class HostedPluginManagerClient {
|
|
92
|
+
private openNewTabAskDialog: OpenHostedInstanceLinkDialog;
|
|
93
|
+
|
|
94
|
+
private connection: DebugSessionConnection;
|
|
95
|
+
|
|
96
|
+
// path to the plugin on the file system
|
|
97
|
+
protected pluginLocation: URI | undefined;
|
|
98
|
+
|
|
99
|
+
// URL to the running plugin instance
|
|
100
|
+
protected pluginInstanceURL: string | undefined;
|
|
101
|
+
|
|
102
|
+
protected isDebug = false;
|
|
103
|
+
|
|
104
|
+
protected readonly stateChanged = new Emitter<HostedInstanceData>();
|
|
105
|
+
|
|
106
|
+
get onStateChanged(): Event<HostedInstanceData> {
|
|
107
|
+
return this.stateChanged.event;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@inject(PluginDevServer)
|
|
111
|
+
protected readonly hostedPluginServer: PluginDevServer;
|
|
112
|
+
@inject(MessageService)
|
|
113
|
+
protected readonly messageService: MessageService;
|
|
114
|
+
@inject(LabelProvider)
|
|
115
|
+
protected readonly labelProvider: LabelProvider;
|
|
116
|
+
@inject(WindowService)
|
|
117
|
+
protected readonly windowService: WindowService;
|
|
118
|
+
@inject(FileService)
|
|
119
|
+
protected readonly fileService: FileService;
|
|
120
|
+
@inject(EnvVariablesServer)
|
|
121
|
+
protected readonly environments: EnvVariablesServer;
|
|
122
|
+
@inject(WorkspaceService)
|
|
123
|
+
protected readonly workspaceService: WorkspaceService;
|
|
124
|
+
@inject(DebugSessionManager)
|
|
125
|
+
protected readonly debugSessionManager: DebugSessionManager;
|
|
126
|
+
@inject(HostedPluginPreferences)
|
|
127
|
+
protected readonly hostedPluginPreferences: HostedPluginPreferences;
|
|
128
|
+
@inject(FileDialogService)
|
|
129
|
+
protected readonly fileDialogService: FileDialogService;
|
|
130
|
+
|
|
131
|
+
@postConstruct()
|
|
132
|
+
protected init(): void {
|
|
133
|
+
this.doInit();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
protected async doInit(): Promise<void> {
|
|
137
|
+
this.openNewTabAskDialog = new OpenHostedInstanceLinkDialog(this.windowService);
|
|
138
|
+
|
|
139
|
+
// is needed for case when page is loaded when hosted instance is already running.
|
|
140
|
+
if (await this.hostedPluginServer.isHostedPluginInstanceRunning()) {
|
|
141
|
+
this.pluginLocation = new URI(await this.hostedPluginServer.getHostedPluginURI());
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
get lastPluginLocation(): string | undefined {
|
|
146
|
+
if (this.pluginLocation) {
|
|
147
|
+
return this.pluginLocation.toString();
|
|
148
|
+
}
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async start(debugConfig?: PluginDebugConfiguration): Promise<void> {
|
|
153
|
+
if (await this.hostedPluginServer.isHostedPluginInstanceRunning()) {
|
|
154
|
+
this.messageService.warn(nls.localize('theia/plugin-dev/alreadyRunning', 'Hosted instance is already running.'));
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (!this.pluginLocation) {
|
|
159
|
+
await this.selectPluginPath();
|
|
160
|
+
if (!this.pluginLocation) {
|
|
161
|
+
// selection was cancelled
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
this.stateChanged.fire({ state: HostedInstanceState.STARTING, pluginLocation: this.pluginLocation });
|
|
168
|
+
this.messageService.info(nls.localize('theia/plugin-dev/starting', 'Starting hosted instance server ...'));
|
|
169
|
+
|
|
170
|
+
if (debugConfig) {
|
|
171
|
+
this.isDebug = true;
|
|
172
|
+
this.pluginInstanceURL = await this.hostedPluginServer.runDebugHostedPluginInstance(this.pluginLocation.toString(), debugConfig);
|
|
173
|
+
} else {
|
|
174
|
+
this.isDebug = false;
|
|
175
|
+
this.pluginInstanceURL = await this.hostedPluginServer.runHostedPluginInstance(this.pluginLocation.toString());
|
|
176
|
+
}
|
|
177
|
+
await this.openPluginWindow();
|
|
178
|
+
|
|
179
|
+
this.messageService.info(`${nls.localize('theia/plugin-dev/running', 'Hosted instance is running at:')} ${this.pluginInstanceURL}`);
|
|
180
|
+
this.stateChanged.fire({ state: HostedInstanceState.RUNNING, pluginLocation: this.pluginLocation });
|
|
181
|
+
} catch (error) {
|
|
182
|
+
this.messageService.error(nls.localize('theia/plugin-dev/failed', 'Failed to run hosted plugin instance: {0}', this.getErrorMessage(error)));
|
|
183
|
+
this.stateChanged.fire({ state: HostedInstanceState.FAILED, pluginLocation: this.pluginLocation });
|
|
184
|
+
this.stop();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async debug(config?: PluginDebugConfiguration): Promise<string | undefined> {
|
|
189
|
+
await this.start(this.setDebugConfig(config));
|
|
190
|
+
await this.startDebugSessionManager();
|
|
191
|
+
|
|
192
|
+
return this.pluginInstanceURL;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async startDebugSessionManager(): Promise<void> {
|
|
196
|
+
let outFiles: string[] | undefined = undefined;
|
|
197
|
+
if (this.pluginLocation && this.hostedPluginPreferences['hosted-plugin.launchOutFiles'].length > 0) {
|
|
198
|
+
const fsPath = await this.fileService.fsPath(this.pluginLocation);
|
|
199
|
+
if (fsPath) {
|
|
200
|
+
outFiles = this.hostedPluginPreferences['hosted-plugin.launchOutFiles'].map(outFile =>
|
|
201
|
+
outFile.replace('${pluginPath}', new Path(fsPath).toString())
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const name = nls.localize('theia/plugin-dev/hostedPlugin', 'Hosted Plugin');
|
|
206
|
+
await this.debugSessionManager.start({
|
|
207
|
+
name,
|
|
208
|
+
configuration: {
|
|
209
|
+
type: 'node',
|
|
210
|
+
request: 'attach',
|
|
211
|
+
timeout: 30000,
|
|
212
|
+
name,
|
|
213
|
+
smartStep: true,
|
|
214
|
+
sourceMaps: !!outFiles,
|
|
215
|
+
outFiles
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async stop(checkRunning: boolean = true): Promise<void> {
|
|
221
|
+
if (checkRunning && !await this.hostedPluginServer.isHostedPluginInstanceRunning()) {
|
|
222
|
+
this.messageService.warn(nls.localize('theia/plugin-dev/notRunning', 'Hosted instance is not running.'));
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
try {
|
|
226
|
+
this.stateChanged.fire({ state: HostedInstanceState.STOPPING, pluginLocation: this.pluginLocation! });
|
|
227
|
+
await this.hostedPluginServer.terminateHostedPluginInstance();
|
|
228
|
+
this.messageService.info((this.pluginInstanceURL
|
|
229
|
+
? nls.localize('theia/plugin-dev/instanceTerminated', '{0} has been terminated', this.pluginInstanceURL)
|
|
230
|
+
: nls.localize('theia/plugin-dev/unknownTerminated', 'The instance has been terminated')));
|
|
231
|
+
this.stateChanged.fire({ state: HostedInstanceState.STOPPED, pluginLocation: this.pluginLocation! });
|
|
232
|
+
} catch (error) {
|
|
233
|
+
this.messageService.error(this.getErrorMessage(error));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async restart(): Promise<void> {
|
|
238
|
+
if (await this.hostedPluginServer.isHostedPluginInstanceRunning()) {
|
|
239
|
+
await this.stop(false);
|
|
240
|
+
|
|
241
|
+
this.messageService.info(nls.localize('theia/plugin-dev/starting', 'Starting hosted instance server ...'));
|
|
242
|
+
|
|
243
|
+
// It takes some time before OS released all resources e.g. port.
|
|
244
|
+
// Keep trying to run hosted instance with delay.
|
|
245
|
+
this.stateChanged.fire({ state: HostedInstanceState.STARTING, pluginLocation: this.pluginLocation! });
|
|
246
|
+
let lastError;
|
|
247
|
+
for (let tries = 0; tries < 15; tries++) {
|
|
248
|
+
try {
|
|
249
|
+
if (this.isDebug) {
|
|
250
|
+
this.pluginInstanceURL = await this.hostedPluginServer.runDebugHostedPluginInstance(this.pluginLocation!.toString(), {
|
|
251
|
+
debugMode: this.hostedPluginPreferences['hosted-plugin.debugMode']
|
|
252
|
+
});
|
|
253
|
+
await this.startDebugSessionManager();
|
|
254
|
+
} else {
|
|
255
|
+
this.pluginInstanceURL = await this.hostedPluginServer.runHostedPluginInstance(this.pluginLocation!.toString());
|
|
256
|
+
}
|
|
257
|
+
await this.openPluginWindow();
|
|
258
|
+
this.messageService.info(`${nls.localize('theia/plugin-dev/running', 'Hosted instance is running at:')} ${this.pluginInstanceURL}`);
|
|
259
|
+
this.stateChanged.fire({
|
|
260
|
+
state: HostedInstanceState.RUNNING,
|
|
261
|
+
pluginLocation: this.pluginLocation!
|
|
262
|
+
});
|
|
263
|
+
return;
|
|
264
|
+
} catch (error) {
|
|
265
|
+
lastError = error;
|
|
266
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
this.messageService.error(nls.localize('theia/plugin-dev/failed', 'Failed to run hosted plugin instance: {0}', this.getErrorMessage(lastError)));
|
|
270
|
+
this.stateChanged.fire({ state: HostedInstanceState.FAILED, pluginLocation: this.pluginLocation! });
|
|
271
|
+
this.stop();
|
|
272
|
+
} else {
|
|
273
|
+
this.messageService.warn(nls.localize('theia/plugin-dev/notRunning', 'Hosted instance is not running.'));
|
|
274
|
+
this.start();
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Creates directory choose dialog and set selected folder into pluginLocation field.
|
|
280
|
+
*/
|
|
281
|
+
async selectPluginPath(): Promise<void> {
|
|
282
|
+
const workspaceFolder = (await this.workspaceService.roots)[0] || await this.fileService.resolve(new URI(await this.environments.getHomeDirUri()));
|
|
283
|
+
if (!workspaceFolder) {
|
|
284
|
+
throw new Error('Unable to find the root');
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const result = await this.fileDialogService.showOpenDialog({
|
|
288
|
+
title: HostedPluginCommands.SELECT_PATH.label!,
|
|
289
|
+
openLabel: nls.localize('theia/plugin-dev/select', 'Select'),
|
|
290
|
+
canSelectFiles: false,
|
|
291
|
+
canSelectFolders: true,
|
|
292
|
+
canSelectMany: false
|
|
293
|
+
}, workspaceFolder);
|
|
294
|
+
|
|
295
|
+
if (result) {
|
|
296
|
+
if (await this.hostedPluginServer.isPluginValid(result.toString())) {
|
|
297
|
+
this.pluginLocation = result;
|
|
298
|
+
this.messageService.info(nls.localize('theia/plugin-dev/pluginFolder', 'Plugin folder is set to: {0}', this.labelProvider.getLongName(result)));
|
|
299
|
+
} else {
|
|
300
|
+
this.messageService.error(nls.localize('theia/plugin-dev/noValidPlugin', 'Specified folder does not contain valid plugin.'));
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
register(configType: string, connection: DebugSessionConnection): void {
|
|
306
|
+
if (configType === 'pwa-extensionHost') {
|
|
307
|
+
this.connection = connection;
|
|
308
|
+
this.connection.onRequest('launchVSCode', (request: LaunchVSCodeRequest) => this.launchVSCode(request));
|
|
309
|
+
|
|
310
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
311
|
+
this.connection.on('exited', async (args: any) => {
|
|
312
|
+
await this.stop();
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Opens window with URL to the running plugin instance.
|
|
319
|
+
*/
|
|
320
|
+
protected async openPluginWindow(): Promise<void> {
|
|
321
|
+
// do nothing for electron browser
|
|
322
|
+
if (isNative) {
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (this.pluginInstanceURL) {
|
|
327
|
+
try {
|
|
328
|
+
this.windowService.openNewWindow(this.pluginInstanceURL);
|
|
329
|
+
} catch (err) {
|
|
330
|
+
// browser blocked opening of a new tab
|
|
331
|
+
this.openNewTabAskDialog.showOpenNewTabAskDialog(this.pluginInstanceURL);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
protected async launchVSCode({ arguments: { args } }: LaunchVSCodeRequest): Promise<LaunchVSCodeResult> {
|
|
337
|
+
let result = {};
|
|
338
|
+
let instanceURI;
|
|
339
|
+
|
|
340
|
+
const sessions = this.debugSessionManager.sessions.filter(session => session.id !== this.connection.sessionId);
|
|
341
|
+
|
|
342
|
+
/* if `launchVSCode` is invoked and sessions do not exist - it means that `start` debug was invoked.
|
|
343
|
+
if `launchVSCode` is invoked and sessions do exist - it means that `restartSessions()` was invoked,
|
|
344
|
+
which invoked `this.sendRequest('restart', {})`, which restarted `vscode-builtin-js-debug` plugin which is
|
|
345
|
+
connected to first session (sessions[0]), which means that other existing (child) sessions need to be terminated
|
|
346
|
+
and new ones will be created by running `startDebugSessionManager()`
|
|
347
|
+
*/
|
|
348
|
+
if (sessions.length > 0) {
|
|
349
|
+
sessions.forEach(session => this.debugSessionManager.terminateSession(session));
|
|
350
|
+
await this.startDebugSessionManager();
|
|
351
|
+
instanceURI = this.pluginInstanceURL;
|
|
352
|
+
} else {
|
|
353
|
+
instanceURI = await this.debug(this.getDebugPluginConfig(args));
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (instanceURI) {
|
|
357
|
+
const instanceURL = new URL(instanceURI);
|
|
358
|
+
if (instanceURL.port) {
|
|
359
|
+
result = Object.assign(result, { rendererDebugPort: instanceURL.port });
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return result;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
366
|
+
protected getErrorMessage(error: any): string {
|
|
367
|
+
return error?.message?.substring(error.message.indexOf(':') + 1) || '';
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
private setDebugConfig(config?: PluginDebugConfiguration): PluginDebugConfiguration {
|
|
371
|
+
config = Object.assign(config || {}, { debugMode: this.hostedPluginPreferences['hosted-plugin.debugMode'] });
|
|
372
|
+
if (config.pluginLocation) {
|
|
373
|
+
this.pluginLocation = new URI((!config.pluginLocation.startsWith('/') ? '/' : '') + config.pluginLocation.replace(/\\/g, '/')).withScheme('file');
|
|
374
|
+
}
|
|
375
|
+
return config;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
private getDebugPluginConfig(args: LaunchVSCodeArgument[]): PluginDebugConfiguration {
|
|
379
|
+
let pluginLocation;
|
|
380
|
+
for (const arg of args) {
|
|
381
|
+
if (arg?.prefix === '--extensionDevelopmentPath=') {
|
|
382
|
+
pluginLocation = arg.path;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return {
|
|
387
|
+
pluginLocation
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
class OpenHostedInstanceLinkDialog extends AbstractDialog<string> {
|
|
393
|
+
protected readonly windowService: WindowService;
|
|
394
|
+
protected readonly openButton: HTMLButtonElement;
|
|
395
|
+
protected readonly messageNode: HTMLDivElement;
|
|
396
|
+
protected readonly linkNode: HTMLAnchorElement;
|
|
397
|
+
value: string;
|
|
398
|
+
|
|
399
|
+
constructor(windowService: WindowService) {
|
|
400
|
+
super({
|
|
401
|
+
title: nls.localize('theia/plugin-dev/preventedNewTab', 'Your browser prevented opening of a new tab')
|
|
402
|
+
});
|
|
403
|
+
this.windowService = windowService;
|
|
404
|
+
|
|
405
|
+
this.linkNode = document.createElement('a');
|
|
406
|
+
this.linkNode.target = '_blank';
|
|
407
|
+
this.linkNode.setAttribute('style', 'color: var(--theia-editorWidget-foreground);');
|
|
408
|
+
this.contentNode.appendChild(this.linkNode);
|
|
409
|
+
|
|
410
|
+
const messageNode = document.createElement('div');
|
|
411
|
+
messageNode.innerText = nls.localize('theia/plugin-dev/running', 'Hosted instance is running at:') + ' ';
|
|
412
|
+
messageNode.appendChild(this.linkNode);
|
|
413
|
+
this.contentNode.appendChild(messageNode);
|
|
414
|
+
|
|
415
|
+
this.appendCloseButton();
|
|
416
|
+
this.openButton = this.appendAcceptButton(nls.localizeByDefault('Open'));
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
showOpenNewTabAskDialog(uri: string): void {
|
|
420
|
+
this.value = uri;
|
|
421
|
+
|
|
422
|
+
this.linkNode.textContent = uri;
|
|
423
|
+
this.linkNode.href = uri;
|
|
424
|
+
this.openButton.onclick = () => {
|
|
425
|
+
this.windowService.openNewWindow(uri);
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
this.open();
|
|
429
|
+
}
|
|
430
|
+
}
|