@theia/core 1.48.2 → 1.49.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/README.md +6 -6
- package/i18n/nls.cs.json +5 -2
- package/i18n/nls.de.json +5 -2
- package/i18n/nls.es.json +5 -2
- package/i18n/nls.fr.json +5 -2
- package/i18n/nls.hu.json +5 -2
- package/i18n/nls.it.json +5 -2
- package/i18n/nls.ja.json +5 -2
- package/i18n/nls.json +5 -2
- package/i18n/nls.pl.json +5 -2
- package/i18n/nls.pt-br.json +5 -2
- package/i18n/nls.pt-pt.json +5 -2
- package/i18n/nls.ru.json +5 -2
- package/i18n/nls.zh-cn.json +5 -2
- package/lib/browser/command-open-handler.js +1 -1
- package/lib/browser/command-open-handler.js.map +1 -1
- package/lib/browser/frontend-application-module.d.ts.map +1 -1
- package/lib/browser/frontend-application-module.js +2 -0
- package/lib/browser/frontend-application-module.js.map +1 -1
- package/lib/browser/index.d.ts +1 -0
- package/lib/browser/index.d.ts.map +1 -1
- package/lib/browser/index.js +1 -0
- package/lib/browser/index.js.map +1 -1
- package/lib/browser/keybinding.js +1 -1
- package/lib/browser/keybinding.js.map +1 -1
- package/lib/browser/open-with-service.d.ts +44 -0
- package/lib/browser/open-with-service.d.ts.map +1 -0
- package/lib/browser/open-with-service.js +66 -0
- package/lib/browser/open-with-service.js.map +1 -0
- package/lib/browser/shell/view-contribution.d.ts.map +1 -1
- package/lib/browser/shell/view-contribution.js +2 -1
- package/lib/browser/shell/view-contribution.js.map +1 -1
- package/lib/common/command.d.ts +4 -0
- package/lib/common/command.d.ts.map +1 -1
- package/lib/common/command.js.map +1 -1
- package/lib/common/event.d.ts +5 -0
- package/lib/common/event.d.ts.map +1 -1
- package/lib/common/event.js +14 -1
- package/lib/common/event.js.map +1 -1
- package/lib/electron-browser/preload.d.ts.map +1 -1
- package/lib/electron-browser/preload.js +3 -0
- package/lib/electron-browser/preload.js.map +1 -1
- package/lib/electron-browser/window/electron-window-service.js +1 -1
- package/lib/electron-browser/window/electron-window-service.js.map +1 -1
- package/lib/electron-common/electron-api.d.ts +3 -1
- package/lib/electron-common/electron-api.d.ts.map +1 -1
- package/lib/electron-common/electron-api.js +2 -1
- package/lib/electron-common/electron-api.js.map +1 -1
- package/lib/electron-main/electron-api-main.d.ts.map +1 -1
- package/lib/electron-main/electron-api-main.js +6 -1
- package/lib/electron-main/electron-api-main.js.map +1 -1
- package/lib/electron-main/electron-main-application.d.ts +17 -1
- package/lib/electron-main/electron-main-application.d.ts.map +1 -1
- package/lib/electron-main/electron-main-application.js +83 -8
- package/lib/electron-main/electron-main-application.js.map +1 -1
- package/lib/electron-main/theia-electron-window.d.ts +6 -0
- package/lib/electron-main/theia-electron-window.d.ts.map +1 -1
- package/lib/electron-main/theia-electron-window.js +3 -1
- package/lib/electron-main/theia-electron-window.js.map +1 -1
- package/lib/node/application-server.js +3 -3
- package/lib/node/application-server.js.map +1 -1
- package/lib/node/backend-application-module.d.ts.map +1 -1
- package/lib/node/backend-application-module.js +1 -4
- package/lib/node/backend-application-module.js.map +1 -1
- package/lib/node/backend-application.d.ts +6 -3
- package/lib/node/backend-application.d.ts.map +1 -1
- package/lib/node/backend-application.js +10 -18
- package/lib/node/backend-application.js.map +1 -1
- package/lib/node/env-variables/env-variables-server.d.ts.map +1 -1
- package/lib/node/env-variables/env-variables-server.js +2 -4
- package/lib/node/env-variables/env-variables-server.js.map +1 -1
- package/package.json +6 -6
- package/src/browser/command-open-handler.ts +1 -1
- package/src/browser/frontend-application-module.ts +3 -0
- package/src/browser/index.ts +1 -0
- package/src/browser/keybinding.ts +1 -1
- package/src/browser/open-with-service.ts +93 -0
- package/src/browser/shell/view-contribution.ts +3 -2
- package/src/common/command.ts +4 -0
- package/src/common/event.ts +18 -0
- package/src/electron-browser/preload.ts +5 -2
- package/src/electron-browser/window/electron-window-service.ts +1 -1
- package/src/electron-common/electron-api.ts +3 -1
- package/src/electron-main/electron-api-main.ts +9 -2
- package/src/electron-main/electron-main-application.ts +101 -11
- package/src/electron-main/theia-electron-window.ts +9 -1
- package/src/node/application-server.ts +3 -3
- package/src/node/backend-application-module.ts +2 -5
- package/src/node/backend-application.ts +10 -18
- package/src/node/env-variables/env-variables-server.ts +2 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theia/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.49.0",
|
|
4
4
|
"description": "Theia is a cloud & desktop IDE framework implemented in TypeScript.",
|
|
5
5
|
"main": "lib/common/index.js",
|
|
6
6
|
"typings": "lib/common/index.d.ts",
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
"@phosphor/signaling": "1",
|
|
17
17
|
"@phosphor/virtualdom": "1",
|
|
18
18
|
"@phosphor/widgets": "1",
|
|
19
|
-
"@theia/application-package": "1.
|
|
20
|
-
"@theia/request": "1.
|
|
19
|
+
"@theia/application-package": "1.49.0",
|
|
20
|
+
"@theia/request": "1.49.0",
|
|
21
21
|
"@types/body-parser": "^1.16.4",
|
|
22
22
|
"@types/cookie": "^0.3.3",
|
|
23
23
|
"@types/dompurify": "^2.2.2",
|
|
@@ -206,12 +206,12 @@
|
|
|
206
206
|
"watch": "theiaext watch"
|
|
207
207
|
},
|
|
208
208
|
"devDependencies": {
|
|
209
|
-
"@theia/ext-scripts": "1.
|
|
210
|
-
"@theia/re-exports": "1.
|
|
209
|
+
"@theia/ext-scripts": "1.49.0",
|
|
210
|
+
"@theia/re-exports": "1.49.0",
|
|
211
211
|
"minimist": "^1.2.0"
|
|
212
212
|
},
|
|
213
213
|
"nyc": {
|
|
214
214
|
"extends": "../../configs/nyc.json"
|
|
215
215
|
},
|
|
216
|
-
"gitHead": "
|
|
216
|
+
"gitHead": "d413dadd87a70fce1c80df7eb22d9d2529cc129f"
|
|
217
217
|
}
|
|
@@ -140,6 +140,7 @@ import { HoverService } from './hover-service';
|
|
|
140
140
|
import { AdditionalViewsMenuWidget, AdditionalViewsMenuWidgetFactory } from './shell/additional-views-menu-widget';
|
|
141
141
|
import { LanguageIconLabelProvider } from './language-icon-provider';
|
|
142
142
|
import { bindTreePreferences } from './tree';
|
|
143
|
+
import { OpenWithService } from './open-with-service';
|
|
143
144
|
|
|
144
145
|
export { bindResourceProvider, bindMessageService, bindPreferenceService };
|
|
145
146
|
|
|
@@ -224,6 +225,8 @@ export const frontendApplicationModule = new ContainerModule((bind, _unbind, _is
|
|
|
224
225
|
bind(CommandOpenHandler).toSelf().inSingletonScope();
|
|
225
226
|
bind(OpenHandler).toService(CommandOpenHandler);
|
|
226
227
|
|
|
228
|
+
bind(OpenWithService).toSelf().inSingletonScope();
|
|
229
|
+
|
|
227
230
|
bind(TooltipServiceImpl).toSelf().inSingletonScope();
|
|
228
231
|
bind(TooltipService).toService(TooltipServiceImpl);
|
|
229
232
|
|
package/src/browser/index.ts
CHANGED
|
@@ -19,6 +19,7 @@ export * from './frontend-application';
|
|
|
19
19
|
export * from './frontend-application-contribution';
|
|
20
20
|
export * from './keyboard';
|
|
21
21
|
export * from './opener-service';
|
|
22
|
+
export * from './open-with-service';
|
|
22
23
|
export * from './browser';
|
|
23
24
|
export * from './context-menu-renderer';
|
|
24
25
|
export * from './widgets';
|
|
@@ -497,7 +497,7 @@ export class KeybindingRegistry {
|
|
|
497
497
|
|
|
498
498
|
isEnabledInScope(binding: common.Keybinding, target: HTMLElement | undefined): boolean {
|
|
499
499
|
const context = binding.context && this.contexts[binding.context];
|
|
500
|
-
if (binding.command && !this.commandRegistry.isEnabled(binding.command, binding.args)) {
|
|
500
|
+
if (binding.command && (!this.isPseudoCommand(binding.command) && !this.commandRegistry.isEnabled(binding.command, binding.args))) {
|
|
501
501
|
return false;
|
|
502
502
|
}
|
|
503
503
|
if (context && !context.isEnabled(binding)) {
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2024 TypeFox 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 { inject, injectable } from 'inversify';
|
|
18
|
+
import { Disposable } from '../common/disposable';
|
|
19
|
+
import { nls } from '../common/nls';
|
|
20
|
+
import { MaybePromise } from '../common/types';
|
|
21
|
+
import { URI } from '../common/uri';
|
|
22
|
+
import { QuickInputService } from './quick-input';
|
|
23
|
+
|
|
24
|
+
export interface OpenWithHandler {
|
|
25
|
+
/**
|
|
26
|
+
* A unique id of this handler.
|
|
27
|
+
*/
|
|
28
|
+
readonly id: string;
|
|
29
|
+
/**
|
|
30
|
+
* A human-readable name of this handler.
|
|
31
|
+
*/
|
|
32
|
+
readonly label?: string;
|
|
33
|
+
/**
|
|
34
|
+
* A human-readable provider name of this handler.
|
|
35
|
+
*/
|
|
36
|
+
readonly providerName?: string;
|
|
37
|
+
/**
|
|
38
|
+
* A css icon class of this handler.
|
|
39
|
+
*/
|
|
40
|
+
readonly iconClass?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Test whether this handler can open the given URI for given options.
|
|
43
|
+
* Return a nonzero number if this handler can open; otherwise it cannot.
|
|
44
|
+
* Never reject.
|
|
45
|
+
*
|
|
46
|
+
* A returned value indicating a priority of this handler.
|
|
47
|
+
*/
|
|
48
|
+
canHandle(uri: URI): number;
|
|
49
|
+
/**
|
|
50
|
+
* Open a widget for the given URI and options.
|
|
51
|
+
* Resolve to an opened widget or undefined, e.g. if a page is opened.
|
|
52
|
+
* Never reject if `canHandle` return a positive number; otherwise should reject.
|
|
53
|
+
*/
|
|
54
|
+
open(uri: URI): MaybePromise<object | undefined>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@injectable()
|
|
58
|
+
export class OpenWithService {
|
|
59
|
+
|
|
60
|
+
@inject(QuickInputService)
|
|
61
|
+
protected readonly quickInputService: QuickInputService;
|
|
62
|
+
|
|
63
|
+
protected readonly handlers: OpenWithHandler[] = [];
|
|
64
|
+
|
|
65
|
+
registerHandler(handler: OpenWithHandler): Disposable {
|
|
66
|
+
this.handlers.push(handler);
|
|
67
|
+
return Disposable.create(() => {
|
|
68
|
+
const index = this.handlers.indexOf(handler);
|
|
69
|
+
if (index !== -1) {
|
|
70
|
+
this.handlers.splice(index, 1);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async openWith(uri: URI): Promise<object | undefined> {
|
|
76
|
+
const handlers = this.getHandlers(uri);
|
|
77
|
+
const result = await this.quickInputService.pick(handlers.map(handler => ({
|
|
78
|
+
handler: handler,
|
|
79
|
+
label: handler.label ?? handler.id,
|
|
80
|
+
detail: handler.providerName
|
|
81
|
+
})), {
|
|
82
|
+
placeHolder: nls.localizeByDefault("Select editor for '{0}'", uri.path.base)
|
|
83
|
+
});
|
|
84
|
+
if (result) {
|
|
85
|
+
return result.handler.open(uri);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
getHandlers(uri: URI): OpenWithHandler[] {
|
|
90
|
+
const map = new Map<OpenWithHandler, number>(this.handlers.map(handler => [handler, handler.canHandle(uri)]));
|
|
91
|
+
return this.handlers.filter(handler => map.get(handler)! > 0).sort((a, b) => map.get(b)! - map.get(a)!);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -18,7 +18,7 @@ import { injectable, inject, interfaces, optional } from 'inversify';
|
|
|
18
18
|
import { Widget } from '@phosphor/widgets';
|
|
19
19
|
import {
|
|
20
20
|
MenuModelRegistry, Command, CommandContribution,
|
|
21
|
-
MenuContribution, CommandRegistry
|
|
21
|
+
MenuContribution, CommandRegistry, nls
|
|
22
22
|
} from '../../common';
|
|
23
23
|
import { KeybindingContribution, KeybindingRegistry } from '../keybinding';
|
|
24
24
|
import { WidgetManager } from '../widget-manager';
|
|
@@ -69,7 +69,8 @@ export abstract class AbstractViewContribution<T extends Widget> implements Comm
|
|
|
69
69
|
if (options.toggleCommandId) {
|
|
70
70
|
this.toggleCommand = {
|
|
71
71
|
id: options.toggleCommandId,
|
|
72
|
-
|
|
72
|
+
category: nls.localizeByDefault('View'),
|
|
73
|
+
label: nls.localizeByDefault('Toggle {0}', this.viewLabel)
|
|
73
74
|
};
|
|
74
75
|
}
|
|
75
76
|
}
|
package/src/common/command.ts
CHANGED
package/src/common/event.ts
CHANGED
|
@@ -467,3 +467,21 @@ export class AsyncEmitter<T extends WaitUntilEvent> extends Emitter<T> {
|
|
|
467
467
|
}
|
|
468
468
|
|
|
469
469
|
}
|
|
470
|
+
|
|
471
|
+
export class QueueableEmitter<T> extends Emitter<T[]> {
|
|
472
|
+
|
|
473
|
+
currentQueue?: T[];
|
|
474
|
+
|
|
475
|
+
queue(...arg: T[]): void {
|
|
476
|
+
if (!this.currentQueue) {
|
|
477
|
+
this.currentQueue = [];
|
|
478
|
+
}
|
|
479
|
+
this.currentQueue.push(...arg);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
override fire(): void {
|
|
483
|
+
super.fire(this.currentQueue || []);
|
|
484
|
+
this.currentQueue = undefined;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
}
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
CHANNEL_IS_FULL_SCREEN, CHANNEL_SET_MENU_BAR_VISIBLE, CHANNEL_REQUEST_CLOSE, CHANNEL_SET_TITLE_STYLE, CHANNEL_RESTART,
|
|
27
27
|
CHANNEL_REQUEST_RELOAD, CHANNEL_APP_STATE_CHANGED, CHANNEL_SHOW_ITEM_IN_FOLDER, CHANNEL_READ_CLIPBOARD, CHANNEL_WRITE_CLIPBOARD,
|
|
28
28
|
CHANNEL_KEYBOARD_LAYOUT_CHANGED, CHANNEL_IPC_CONNECTION, InternalMenuDto, CHANNEL_REQUEST_SECONDARY_CLOSE, CHANNEL_SET_BACKGROUND_COLOR,
|
|
29
|
-
CHANNEL_WC_METADATA, CHANNEL_ABOUT_TO_CLOSE
|
|
29
|
+
CHANNEL_WC_METADATA, CHANNEL_ABOUT_TO_CLOSE, CHANNEL_OPEN_WITH_SYSTEM_APP
|
|
30
30
|
} from '../electron-common/electron-api';
|
|
31
31
|
|
|
32
32
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
@@ -75,10 +75,13 @@ const api: TheiaCoreAPI = {
|
|
|
75
75
|
ipcRenderer.send(CHANNEL_SET_MENU, mainMenuId, convertMenu(menu, handlers));
|
|
76
76
|
},
|
|
77
77
|
getSecurityToken: () => ipcRenderer.sendSync(CHANNEL_GET_SECURITY_TOKEN),
|
|
78
|
-
focusWindow: (name
|
|
78
|
+
focusWindow: (name?: string) => ipcRenderer.send(CHANNEL_FOCUS_WINDOW, name),
|
|
79
79
|
showItemInFolder: fsPath => {
|
|
80
80
|
ipcRenderer.send(CHANNEL_SHOW_ITEM_IN_FOLDER, fsPath);
|
|
81
81
|
},
|
|
82
|
+
openWithSystemApp: fsPath => {
|
|
83
|
+
ipcRenderer.send(CHANNEL_OPEN_WITH_SYSTEM_APP, fsPath);
|
|
84
|
+
},
|
|
82
85
|
attachSecurityToken: (endpoint: string) => ipcRenderer.invoke(CHANNEL_ATTACH_SECURITY_TOKEN, endpoint),
|
|
83
86
|
|
|
84
87
|
popup: async function (menu: MenuDto[], x: number, y: number, onClosed: () => void, windowName?: string): Promise<number> {
|
|
@@ -53,9 +53,10 @@ export interface TheiaCoreAPI {
|
|
|
53
53
|
popup(menu: MenuDto[], x: number, y: number, onClosed: () => void, windowName?: string): Promise<number>;
|
|
54
54
|
closePopup(handle: number): void;
|
|
55
55
|
|
|
56
|
-
focusWindow(name
|
|
56
|
+
focusWindow(name?: string): void;
|
|
57
57
|
|
|
58
58
|
showItemInFolder(fsPath: string): void;
|
|
59
|
+
openWithSystemApp(fsPath: string): void;
|
|
59
60
|
|
|
60
61
|
getTitleBarStyleAtStartup(): Promise<string>;
|
|
61
62
|
setTitleBarStyle(style: string): void;
|
|
@@ -112,6 +113,7 @@ export const CHANNEL_FOCUS_WINDOW = 'FocusWindow';
|
|
|
112
113
|
export const CHANNEL_SHOW_OPEN = 'ShowOpenDialog';
|
|
113
114
|
export const CHANNEL_SHOW_SAVE = 'ShowSaveDialog';
|
|
114
115
|
export const CHANNEL_SHOW_ITEM_IN_FOLDER = 'ShowItemInFolder';
|
|
116
|
+
export const CHANNEL_OPEN_WITH_SYSTEM_APP = 'OpenWithSystemApp';
|
|
115
117
|
export const CHANNEL_ATTACH_SECURITY_TOKEN = 'AttachSecurityToken';
|
|
116
118
|
|
|
117
119
|
export const CHANNEL_GET_TITLE_STYLE_AT_STARTUP = 'GetTitleStyleAtStartup';
|
|
@@ -53,7 +53,8 @@ import {
|
|
|
53
53
|
CHANNEL_REQUEST_SECONDARY_CLOSE,
|
|
54
54
|
CHANNEL_SET_BACKGROUND_COLOR,
|
|
55
55
|
CHANNEL_WC_METADATA,
|
|
56
|
-
CHANNEL_ABOUT_TO_CLOSE
|
|
56
|
+
CHANNEL_ABOUT_TO_CLOSE,
|
|
57
|
+
CHANNEL_OPEN_WITH_SYSTEM_APP
|
|
57
58
|
} from '../electron-common/electron-api';
|
|
58
59
|
import { ElectronMainApplication, ElectronMainApplicationContribution } from './electron-main-application';
|
|
59
60
|
import { Disposable, DisposableCollection, isOSX, MaybePromise } from '../common';
|
|
@@ -147,7 +148,9 @@ export class TheiaMainApi implements ElectronMainApplicationContribution {
|
|
|
147
148
|
|
|
148
149
|
// focus windows for secondary window support
|
|
149
150
|
ipcMain.on(CHANNEL_FOCUS_WINDOW, (event, windowName) => {
|
|
150
|
-
const electronWindow =
|
|
151
|
+
const electronWindow = windowName
|
|
152
|
+
? BrowserWindow.getAllWindows().find(win => win.webContents.mainFrame.name === windowName)
|
|
153
|
+
: BrowserWindow.fromWebContents(event.sender);
|
|
151
154
|
if (electronWindow) {
|
|
152
155
|
if (electronWindow.isMinimized()) {
|
|
153
156
|
electronWindow.restore();
|
|
@@ -162,6 +165,10 @@ export class TheiaMainApi implements ElectronMainApplicationContribution {
|
|
|
162
165
|
shell.showItemInFolder(fsPath);
|
|
163
166
|
});
|
|
164
167
|
|
|
168
|
+
ipcMain.on(CHANNEL_OPEN_WITH_SYSTEM_APP, (event, fsPath) => {
|
|
169
|
+
shell.openPath(fsPath);
|
|
170
|
+
});
|
|
171
|
+
|
|
165
172
|
ipcMain.handle(CHANNEL_GET_TITLE_STYLE_AT_STARTUP, event => application.getTitleBarStyleAtStartup(event.sender));
|
|
166
173
|
|
|
167
174
|
ipcMain.on(CHANNEL_SET_TITLE_STYLE, (event, style) => application.setTitleBarStyle(event.sender, style));
|
|
@@ -22,16 +22,16 @@ import { AddressInfo } from 'net';
|
|
|
22
22
|
import { promises as fs } from 'fs';
|
|
23
23
|
import { existsSync, mkdirSync } from 'fs-extra';
|
|
24
24
|
import { fork, ForkOptions } from 'child_process';
|
|
25
|
-
import { DefaultTheme, FrontendApplicationConfig } from '@theia/application-package/lib/application-props';
|
|
25
|
+
import { DefaultTheme, ElectronFrontendApplicationConfig, FrontendApplicationConfig } from '@theia/application-package/lib/application-props';
|
|
26
26
|
import URI from '../common/uri';
|
|
27
27
|
import { FileUri } from '../common/file-uri';
|
|
28
|
-
import { Deferred } from '../common/promise-util';
|
|
28
|
+
import { Deferred, timeout } from '../common/promise-util';
|
|
29
29
|
import { MaybePromise } from '../common/types';
|
|
30
30
|
import { ContributionProvider } from '../common/contribution-provider';
|
|
31
31
|
import { ElectronSecurityTokenService } from './electron-security-token-service';
|
|
32
32
|
import { ElectronSecurityToken } from '../electron-common/electron-token';
|
|
33
33
|
import Storage = require('electron-store');
|
|
34
|
-
import { Disposable, DisposableCollection, isOSX, isWindows } from '../common';
|
|
34
|
+
import { CancellationTokenSource, Disposable, DisposableCollection, isOSX, isWindows } from '../common';
|
|
35
35
|
import { DEFAULT_WINDOW_HASH, WindowSearchParams } from '../common/window';
|
|
36
36
|
import { TheiaBrowserWindowOptions, TheiaElectronWindow, TheiaElectronWindowFactory } from './theia-electron-window';
|
|
37
37
|
import { ElectronMainApplicationGlobals } from './electron-main-constants';
|
|
@@ -182,6 +182,7 @@ export class ElectronMainApplication {
|
|
|
182
182
|
protected windows = new Map<number, TheiaElectronWindow>();
|
|
183
183
|
protected restarting = false;
|
|
184
184
|
|
|
185
|
+
/** Used to temporarily store the reference to an early created main window */
|
|
185
186
|
protected initialWindow?: BrowserWindow;
|
|
186
187
|
|
|
187
188
|
get config(): FrontendApplicationConfig {
|
|
@@ -287,18 +288,110 @@ export class ElectronMainApplication {
|
|
|
287
288
|
return this.didUseNativeWindowFrameOnStart.get(webContents.id) ? 'native' : 'custom';
|
|
288
289
|
}
|
|
289
290
|
|
|
291
|
+
protected async determineSplashScreenBounds(initialWindowBounds: { x: number, y: number, width: number, height: number }):
|
|
292
|
+
Promise<{ x: number, y: number, width: number, height: number }> {
|
|
293
|
+
const splashScreenOptions = this.getSplashScreenOptions();
|
|
294
|
+
const width = splashScreenOptions?.width ?? 640;
|
|
295
|
+
const height = splashScreenOptions?.height ?? 480;
|
|
296
|
+
|
|
297
|
+
// determine the screen on which to show the splash screen via the center of the window to show
|
|
298
|
+
const windowCenterPoint = { x: initialWindowBounds.x + (initialWindowBounds.width / 2), y: initialWindowBounds.y + (initialWindowBounds.height / 2) };
|
|
299
|
+
const { bounds } = screen.getDisplayNearestPoint(windowCenterPoint);
|
|
300
|
+
|
|
301
|
+
// place splash screen center of screen
|
|
302
|
+
const screenCenterPoint = { x: bounds.x + (bounds.width / 2), y: bounds.y + (bounds.height / 2) };
|
|
303
|
+
const x = screenCenterPoint.x - (width / 2);
|
|
304
|
+
const y = screenCenterPoint.y - (height / 2);
|
|
305
|
+
|
|
306
|
+
return {
|
|
307
|
+
x, y, width, height
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
protected isShowWindowEarly(): boolean {
|
|
312
|
+
return !!this.config.electron.showWindowEarly &&
|
|
313
|
+
!('THEIA_ELECTRON_NO_EARLY_WINDOW' in process.env && process.env.THEIA_ELECTRON_NO_EARLY_WINDOW === '1');
|
|
314
|
+
}
|
|
315
|
+
|
|
290
316
|
protected showInitialWindow(): void {
|
|
291
|
-
if (this.
|
|
292
|
-
!('THEIA_ELECTRON_NO_EARLY_WINDOW' in process.env && process.env.THEIA_ELECTRON_NO_EARLY_WINDOW === '1')) {
|
|
293
|
-
console.log('Showing main window early');
|
|
317
|
+
if (this.isShowWindowEarly() || this.isShowSplashScreen()) {
|
|
294
318
|
app.whenReady().then(async () => {
|
|
295
319
|
const options = await this.getLastWindowOptions();
|
|
320
|
+
// If we want to show a splash screen, don't auto open the main window
|
|
321
|
+
if (this.isShowSplashScreen()) {
|
|
322
|
+
options.preventAutomaticShow = true;
|
|
323
|
+
}
|
|
296
324
|
this.initialWindow = await this.createWindow({ ...options });
|
|
297
|
-
|
|
325
|
+
|
|
326
|
+
if (this.isShowSplashScreen()) {
|
|
327
|
+
console.log('Showing splash screen');
|
|
328
|
+
this.configureAndShowSplashScreen(this.initialWindow);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Show main window early if windows shall be shown early and splash screen is not configured
|
|
332
|
+
if (this.isShowWindowEarly() && !this.isShowSplashScreen()) {
|
|
333
|
+
console.log('Showing main window early');
|
|
334
|
+
this.initialWindow.show();
|
|
335
|
+
}
|
|
298
336
|
});
|
|
299
337
|
}
|
|
300
338
|
}
|
|
301
339
|
|
|
340
|
+
protected async configureAndShowSplashScreen(mainWindow: BrowserWindow): Promise<BrowserWindow> {
|
|
341
|
+
const splashScreenOptions = this.getSplashScreenOptions()!;
|
|
342
|
+
console.debug('SplashScreen options', splashScreenOptions);
|
|
343
|
+
|
|
344
|
+
const splashScreenBounds = await this.determineSplashScreenBounds(mainWindow.getBounds());
|
|
345
|
+
const splashScreenWindow = new BrowserWindow({
|
|
346
|
+
...splashScreenBounds,
|
|
347
|
+
frame: false,
|
|
348
|
+
alwaysOnTop: true,
|
|
349
|
+
show: false
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
if (this.isShowWindowEarly()) {
|
|
353
|
+
console.log('Showing splash screen early');
|
|
354
|
+
splashScreenWindow.show();
|
|
355
|
+
} else {
|
|
356
|
+
splashScreenWindow.on('ready-to-show', () => {
|
|
357
|
+
splashScreenWindow.show();
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
splashScreenWindow.loadFile(path.resolve(this.globals.THEIA_APP_PROJECT_PATH, splashScreenOptions.content!).toString());
|
|
362
|
+
|
|
363
|
+
// close splash screen and show main window once frontend is ready or a timeout is hit
|
|
364
|
+
const cancelTokenSource = new CancellationTokenSource();
|
|
365
|
+
const minTime = timeout(splashScreenOptions.minDuration ?? 0, cancelTokenSource.token);
|
|
366
|
+
const maxTime = timeout(splashScreenOptions.maxDuration ?? 30000, cancelTokenSource.token);
|
|
367
|
+
|
|
368
|
+
const showWindowAndCloseSplashScreen = () => {
|
|
369
|
+
cancelTokenSource.cancel();
|
|
370
|
+
if (!mainWindow.isVisible()) {
|
|
371
|
+
mainWindow.show();
|
|
372
|
+
}
|
|
373
|
+
splashScreenWindow.close();
|
|
374
|
+
};
|
|
375
|
+
TheiaRendererAPI.onApplicationStateChanged(mainWindow.webContents, state => {
|
|
376
|
+
if (state === 'ready') {
|
|
377
|
+
minTime.then(() => showWindowAndCloseSplashScreen());
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
maxTime.then(() => showWindowAndCloseSplashScreen());
|
|
381
|
+
return splashScreenWindow;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
protected isShowSplashScreen(): boolean {
|
|
385
|
+
return typeof this.config.electron.splashScreenOptions === 'object' && !!this.config.electron.splashScreenOptions.content;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
protected getSplashScreenOptions(): ElectronFrontendApplicationConfig.SplashScreenOptions | undefined {
|
|
389
|
+
if (this.isShowSplashScreen()) {
|
|
390
|
+
return this.config.electron.splashScreenOptions;
|
|
391
|
+
}
|
|
392
|
+
return undefined;
|
|
393
|
+
}
|
|
394
|
+
|
|
302
395
|
/**
|
|
303
396
|
* Use this rather than creating `BrowserWindow` instances from scratch, since some security parameters need to be set, this method will do it.
|
|
304
397
|
*
|
|
@@ -316,6 +409,7 @@ export class ElectronMainApplication {
|
|
|
316
409
|
electronWindow.window.on('focus', () => TheiaRendererAPI.sendWindowEvent(electronWindow.window.webContents, 'focus'));
|
|
317
410
|
this.attachSaveWindowState(electronWindow.window);
|
|
318
411
|
this.configureNativeSecondaryWindowCreation(electronWindow.window);
|
|
412
|
+
|
|
319
413
|
return electronWindow.window;
|
|
320
414
|
}
|
|
321
415
|
|
|
@@ -548,10 +642,6 @@ export class ElectronMainApplication {
|
|
|
548
642
|
protected async startBackend(): Promise<number> {
|
|
549
643
|
// Check if we should run everything as one process.
|
|
550
644
|
const noBackendFork = process.argv.indexOf('--no-cluster') !== -1;
|
|
551
|
-
// We cannot use the `process.cwd()` as the application project path (the location of the `package.json` in other words)
|
|
552
|
-
// in a bundled electron application because it depends on the way we start it. For instance, on OS X, these are a differences:
|
|
553
|
-
// https://github.com/eclipse-theia/theia/issues/3297#issuecomment-439172274
|
|
554
|
-
process.env.THEIA_APP_PROJECT_PATH = this.globals.THEIA_APP_PROJECT_PATH;
|
|
555
645
|
// Set the electron version for both the dev and the production mode. (https://github.com/eclipse-theia/theia/issues/3254)
|
|
556
646
|
// Otherwise, the forked backend processes will not know that they're serving the electron frontend.
|
|
557
647
|
process.env.THEIA_ELECTRON_VERSION = process.versions.electron;
|
|
@@ -37,6 +37,12 @@ export interface TheiaBrowserWindowOptions extends BrowserWindowConstructorOptio
|
|
|
37
37
|
* in which case we want to invalidate the stored options and use the default options instead.
|
|
38
38
|
*/
|
|
39
39
|
screenLayout?: string;
|
|
40
|
+
/**
|
|
41
|
+
* By default, the window will be shown as soon as the content is ready to render.
|
|
42
|
+
* This can be prevented by handing over preventAutomaticShow: `true`.
|
|
43
|
+
* Use this for fine-grained control over when to show the window, e.g. to coordinate with a splash screen.
|
|
44
|
+
*/
|
|
45
|
+
preventAutomaticShow?: boolean;
|
|
40
46
|
}
|
|
41
47
|
|
|
42
48
|
export const TheiaBrowserWindowOptions = Symbol('TheiaBrowserWindowOptions');
|
|
@@ -76,7 +82,9 @@ export class TheiaElectronWindow {
|
|
|
76
82
|
protected init(): void {
|
|
77
83
|
this._window = new BrowserWindow(this.options);
|
|
78
84
|
this._window.setMenuBarVisibility(false);
|
|
79
|
-
this.
|
|
85
|
+
if (!this.options.preventAutomaticShow) {
|
|
86
|
+
this.attachReadyToShow();
|
|
87
|
+
}
|
|
80
88
|
this.restoreMaximizedState();
|
|
81
89
|
this.attachCloseListeners();
|
|
82
90
|
this.trackApplicationState();
|
|
@@ -26,9 +26,9 @@ export class ApplicationServerImpl implements ApplicationServer {
|
|
|
26
26
|
protected readonly applicationPackage: ApplicationPackage;
|
|
27
27
|
|
|
28
28
|
getExtensionsInfos(): Promise<ExtensionInfo[]> {
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
return Promise.resolve(
|
|
29
|
+
// @ts-expect-error
|
|
30
|
+
const appInfo: ExtensionInfo[] = globalThis.extensionInfo;
|
|
31
|
+
return Promise.resolve(appInfo);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
getApplicationInfo(): Promise<ApplicationInfo | undefined> {
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
bindContributionProvider, MessageService, MessageClient, ConnectionHandler, RpcConnectionHandler,
|
|
22
22
|
CommandService, commandServicePath, messageServicePath, OSBackendProvider, OSBackendProviderPath
|
|
23
23
|
} from '../common';
|
|
24
|
-
import { BackendApplication, BackendApplicationContribution, BackendApplicationCliContribution, BackendApplicationServer } from './backend-application';
|
|
24
|
+
import { BackendApplication, BackendApplicationContribution, BackendApplicationCliContribution, BackendApplicationServer, BackendApplicationPath } from './backend-application';
|
|
25
25
|
import { CliManager, CliContribution } from './cli';
|
|
26
26
|
import { IPCConnectionProvider } from './messaging';
|
|
27
27
|
import { ApplicationServerImpl } from './application-server';
|
|
@@ -101,10 +101,7 @@ export const backendApplicationModule = new ContainerModule(bind => {
|
|
|
101
101
|
})
|
|
102
102
|
).inSingletonScope();
|
|
103
103
|
|
|
104
|
-
bind(ApplicationPackage).
|
|
105
|
-
const { projectPath } = container.get(BackendApplicationCliContribution);
|
|
106
|
-
return new ApplicationPackage({ projectPath });
|
|
107
|
-
}).inSingletonScope();
|
|
104
|
+
bind(ApplicationPackage).toConstantValue(new ApplicationPackage({ projectPath: BackendApplicationPath }));
|
|
108
105
|
|
|
109
106
|
bind(WsRequestValidator).toSelf().inSingletonScope();
|
|
110
107
|
bindContributionProvider(bind, WsRequestValidatorContribution);
|
|
@@ -27,9 +27,14 @@ import { CliContribution } from './cli';
|
|
|
27
27
|
import { Deferred } from '../common/promise-util';
|
|
28
28
|
import { environment } from '../common/index';
|
|
29
29
|
import { AddressInfo } from 'net';
|
|
30
|
-
import { ApplicationPackage } from '@theia/application-package';
|
|
31
30
|
import { ProcessUtils } from './process-utils';
|
|
32
31
|
|
|
32
|
+
/**
|
|
33
|
+
* The path to the application project directory. This is the directory where the application code is located.
|
|
34
|
+
* Mostly contains the `package.json` file and the `lib` directory.
|
|
35
|
+
*/
|
|
36
|
+
export const BackendApplicationPath = process.env.THEIA_APP_PROJECT_PATH || process.cwd();
|
|
37
|
+
|
|
33
38
|
export type DnsResultOrder = 'ipv4first' | 'verbatim' | 'nodeDefault';
|
|
34
39
|
|
|
35
40
|
const APP_PROJECT_PATH = 'app-project-path';
|
|
@@ -115,7 +120,8 @@ export class BackendApplicationCliContribution implements CliContribution {
|
|
|
115
120
|
ssl: boolean | undefined;
|
|
116
121
|
cert: string | undefined;
|
|
117
122
|
certkey: string | undefined;
|
|
118
|
-
|
|
123
|
+
/** @deprecated Use the `BackendApplicationPath` constant or `process.env.THEIA_APP_PROJECT_PATH` environment variable instead */
|
|
124
|
+
projectPath = BackendApplicationPath;
|
|
119
125
|
|
|
120
126
|
configure(conf: yargs.Argv): void {
|
|
121
127
|
conf.option('port', { alias: 'p', description: 'The port the backend server listens on.', type: 'number', default: DEFAULT_PORT });
|
|
@@ -123,7 +129,7 @@ export class BackendApplicationCliContribution implements CliContribution {
|
|
|
123
129
|
conf.option('ssl', { description: 'Use SSL (HTTPS), cert and certkey must also be set', type: 'boolean', default: DEFAULT_SSL });
|
|
124
130
|
conf.option('cert', { description: 'Path to SSL certificate.', type: 'string' });
|
|
125
131
|
conf.option('certkey', { description: 'Path to SSL certificate key.', type: 'string' });
|
|
126
|
-
conf.option(APP_PROJECT_PATH, { description: 'Sets the application project directory',
|
|
132
|
+
conf.option(APP_PROJECT_PATH, { description: 'Sets the application project directory', deprecated: true });
|
|
127
133
|
conf.option('dnsDefaultResultOrder', {
|
|
128
134
|
type: 'string',
|
|
129
135
|
description: 'Configure Node\'s DNS resolver default behavior, see https://nodejs.org/docs/latest-v18.x/api/dns.html#dnssetdefaultresultorderorder',
|
|
@@ -138,19 +144,8 @@ export class BackendApplicationCliContribution implements CliContribution {
|
|
|
138
144
|
this.ssl = args.ssl as boolean;
|
|
139
145
|
this.cert = args.cert as string;
|
|
140
146
|
this.certkey = args.certkey as string;
|
|
141
|
-
this.projectPath = args[APP_PROJECT_PATH] as string;
|
|
142
147
|
this.dnsDefaultResultOrder = args.dnsDefaultResultOrder as DnsResultOrder;
|
|
143
148
|
}
|
|
144
|
-
|
|
145
|
-
protected appProjectPath(): string {
|
|
146
|
-
if (environment.electron.is()) {
|
|
147
|
-
if (process.env.THEIA_APP_PROJECT_PATH) {
|
|
148
|
-
return process.env.THEIA_APP_PROJECT_PATH;
|
|
149
|
-
}
|
|
150
|
-
throw new Error('The \'THEIA_APP_PROJECT_PATH\' environment variable must be set when running in electron.');
|
|
151
|
-
}
|
|
152
|
-
return process.cwd();
|
|
153
|
-
}
|
|
154
149
|
}
|
|
155
150
|
|
|
156
151
|
/**
|
|
@@ -161,9 +156,6 @@ export class BackendApplication {
|
|
|
161
156
|
|
|
162
157
|
protected readonly app: express.Application = express();
|
|
163
158
|
|
|
164
|
-
@inject(ApplicationPackage)
|
|
165
|
-
protected readonly applicationPackage: ApplicationPackage;
|
|
166
|
-
|
|
167
159
|
@inject(ProcessUtils)
|
|
168
160
|
protected readonly processUtils: ProcessUtils;
|
|
169
161
|
|
|
@@ -352,7 +344,7 @@ export class BackendApplication {
|
|
|
352
344
|
const acceptedEncodings = req.acceptsEncodings();
|
|
353
345
|
|
|
354
346
|
const gzUrl = `${req.url}.gz`;
|
|
355
|
-
const gzPath = path.join(
|
|
347
|
+
const gzPath = path.join(BackendApplicationPath, 'lib', 'frontend', gzUrl);
|
|
356
348
|
if (acceptedEncodings.indexOf('gzip') === -1 || !(await fs.pathExists(gzPath))) {
|
|
357
349
|
next();
|
|
358
350
|
return;
|
|
@@ -22,6 +22,7 @@ import { pathExists, mkdir } from 'fs-extra';
|
|
|
22
22
|
import { EnvVariable, EnvVariablesServer } from '../../common/env-variables';
|
|
23
23
|
import { isWindows } from '../../common/os';
|
|
24
24
|
import { FileUri } from '../../common/file-uri';
|
|
25
|
+
import { BackendApplicationPath } from '../backend-application';
|
|
25
26
|
|
|
26
27
|
@injectable()
|
|
27
28
|
export class EnvVariablesServerImpl implements EnvVariablesServer {
|
|
@@ -45,10 +46,7 @@ export class EnvVariablesServerImpl implements EnvVariablesServer {
|
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
protected async createConfigDirUri(): Promise<string> {
|
|
48
|
-
|
|
49
|
-
if (process.env.THEIA_APP_PROJECT_PATH) {
|
|
50
|
-
dataFolderPath = join(process.env.THEIA_APP_PROJECT_PATH, 'data');
|
|
51
|
-
}
|
|
49
|
+
const dataFolderPath = join(BackendApplicationPath, 'data');
|
|
52
50
|
const userDataPath = join(dataFolderPath, 'user-data');
|
|
53
51
|
const dataFolderExists = this.pathExistenceCache[dataFolderPath] ??= await pathExists(dataFolderPath);
|
|
54
52
|
if (dataFolderExists) {
|