@theia/core 1.53.0-next.6 → 1.53.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.
Files changed (142) hide show
  1. package/README.md +7 -7
  2. package/i18n/nls.cs.json +30 -0
  3. package/i18n/nls.de.json +30 -0
  4. package/i18n/nls.es.json +30 -0
  5. package/i18n/nls.fr.json +30 -0
  6. package/i18n/nls.hu.json +30 -0
  7. package/i18n/nls.it.json +30 -0
  8. package/i18n/nls.ja.json +30 -0
  9. package/i18n/nls.json +31 -1
  10. package/i18n/nls.ko.json +582 -0
  11. package/i18n/nls.pl.json +30 -0
  12. package/i18n/nls.pt-br.json +30 -0
  13. package/i18n/nls.ru.json +30 -0
  14. package/i18n/nls.tr.json +582 -0
  15. package/i18n/nls.zh-cn.json +30 -0
  16. package/i18n/nls.zh-tw.json +582 -0
  17. package/lib/browser/catalog.json +6801 -0
  18. package/lib/browser/context-key-service.d.ts +3 -2
  19. package/lib/browser/context-key-service.d.ts.map +1 -1
  20. package/lib/browser/context-key-service.js.map +1 -1
  21. package/lib/browser/frontend-application-module.d.ts.map +1 -1
  22. package/lib/browser/frontend-application-module.js.map +1 -1
  23. package/lib/browser/json-schema-store.d.ts +0 -3
  24. package/lib/browser/json-schema-store.d.ts.map +1 -1
  25. package/lib/browser/json-schema-store.js +2 -12
  26. package/lib/browser/json-schema-store.js.map +1 -1
  27. package/lib/browser/opener-service.d.ts +5 -0
  28. package/lib/browser/opener-service.d.ts.map +1 -1
  29. package/lib/browser/opener-service.js +5 -2
  30. package/lib/browser/opener-service.js.map +1 -1
  31. package/lib/browser/saveable-service.d.ts.map +1 -1
  32. package/lib/browser/saveable-service.js +6 -2
  33. package/lib/browser/saveable-service.js.map +1 -1
  34. package/lib/browser/saveable.d.ts +17 -1
  35. package/lib/browser/saveable.d.ts.map +1 -1
  36. package/lib/browser/saveable.js +62 -1
  37. package/lib/browser/saveable.js.map +1 -1
  38. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.d.ts +2 -2
  39. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.d.ts.map +1 -1
  40. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.js.map +1 -1
  41. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.d.ts +6 -16
  42. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.d.ts.map +1 -1
  43. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.js +11 -29
  44. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.js.map +1 -1
  45. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-types.d.ts +39 -78
  46. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-types.d.ts.map +1 -1
  47. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-types.js +8 -39
  48. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-types.js.map +1 -1
  49. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.d.ts +10 -10
  50. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.d.ts.map +1 -1
  51. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.js +43 -32
  52. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.js.map +1 -1
  53. package/lib/browser/view-container.d.ts +2 -2
  54. package/lib/browser/view-container.d.ts.map +1 -1
  55. package/lib/browser/view-container.js.map +1 -1
  56. package/lib/browser/widget-open-handler.d.ts +4 -1
  57. package/lib/browser/widget-open-handler.d.ts.map +1 -1
  58. package/lib/browser/widget-open-handler.js.map +1 -1
  59. package/lib/browser/widgets/index.d.ts +1 -0
  60. package/lib/browser/widgets/index.d.ts.map +1 -1
  61. package/lib/browser/widgets/index.js +1 -0
  62. package/lib/browser/widgets/index.js.map +1 -1
  63. package/lib/browser/widgets/split-widget.d.ts +45 -0
  64. package/lib/browser/widgets/split-widget.d.ts.map +1 -0
  65. package/lib/browser/widgets/split-widget.js +126 -0
  66. package/lib/browser/widgets/split-widget.js.map +1 -0
  67. package/lib/common/event.d.ts +2 -0
  68. package/lib/common/event.d.ts.map +1 -1
  69. package/lib/common/event.js +4 -0
  70. package/lib/common/event.js.map +1 -1
  71. package/lib/common/json-schema.d.ts +2 -0
  72. package/lib/common/json-schema.d.ts.map +1 -1
  73. package/lib/common/menu/menu-types.d.ts.map +1 -1
  74. package/lib/common/menu/menu-types.js.map +1 -1
  75. package/lib/electron-browser/electron-uri-handler.d.ts +6 -0
  76. package/lib/electron-browser/electron-uri-handler.d.ts.map +1 -0
  77. package/lib/electron-browser/electron-uri-handler.js +49 -0
  78. package/lib/electron-browser/electron-uri-handler.js.map +1 -0
  79. package/lib/electron-browser/menu/electron-main-menu-factory.d.ts +1 -1
  80. package/lib/electron-browser/menu/electron-main-menu-factory.d.ts.map +1 -1
  81. package/lib/electron-browser/menu/electron-main-menu-factory.js +6 -6
  82. package/lib/electron-browser/menu/electron-main-menu-factory.js.map +1 -1
  83. package/lib/electron-browser/preload.d.ts.map +1 -1
  84. package/lib/electron-browser/preload.js +12 -0
  85. package/lib/electron-browser/preload.js.map +1 -1
  86. package/lib/electron-browser/window/electron-window-module.d.ts.map +1 -1
  87. package/lib/electron-browser/window/electron-window-module.js +3 -0
  88. package/lib/electron-browser/window/electron-window-module.js.map +1 -1
  89. package/lib/electron-browser/window/external-app-open-handler.js +1 -1
  90. package/lib/electron-browser/window/external-app-open-handler.js.map +1 -1
  91. package/lib/electron-common/electron-api.d.ts +2 -0
  92. package/lib/electron-common/electron-api.d.ts.map +1 -1
  93. package/lib/electron-common/electron-api.js +2 -1
  94. package/lib/electron-common/electron-api.js.map +1 -1
  95. package/lib/electron-main/electron-api-main.d.ts +2 -0
  96. package/lib/electron-main/electron-api-main.d.ts.map +1 -1
  97. package/lib/electron-main/electron-api-main.js +27 -3
  98. package/lib/electron-main/electron-api-main.js.map +1 -1
  99. package/lib/electron-main/electron-main-application.d.ts +5 -3
  100. package/lib/electron-main/electron-main-application.d.ts.map +1 -1
  101. package/lib/electron-main/electron-main-application.js +57 -14
  102. package/lib/electron-main/electron-main-application.js.map +1 -1
  103. package/lib/electron-main/theia-electron-window.d.ts +1 -0
  104. package/lib/electron-main/theia-electron-window.d.ts.map +1 -1
  105. package/lib/electron-main/theia-electron-window.js +3 -0
  106. package/lib/electron-main/theia-electron-window.js.map +1 -1
  107. package/lib/node/i18n/theia-localization-contribution.d.ts.map +1 -1
  108. package/lib/node/i18n/theia-localization-contribution.js +12 -8
  109. package/lib/node/i18n/theia-localization-contribution.js.map +1 -1
  110. package/package.json +10 -8
  111. package/src/browser/context-key-service.ts +3 -3
  112. package/src/browser/frontend-application-module.ts +0 -1
  113. package/src/browser/json-schema-store.ts +2 -11
  114. package/src/browser/opener-service.ts +12 -2
  115. package/src/browser/saveable-service.ts +6 -2
  116. package/src/browser/saveable.ts +69 -1
  117. package/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.ts +2 -2
  118. package/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.ts +18 -33
  119. package/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar-types.ts +62 -124
  120. package/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar.tsx +40 -25
  121. package/src/browser/style/index.css +1 -0
  122. package/src/browser/style/split-widget.css +38 -0
  123. package/src/browser/style/tabs.css +13 -24
  124. package/src/browser/style/view-container.css +0 -7
  125. package/src/browser/view-container.ts +2 -2
  126. package/src/browser/widget-open-handler.ts +4 -1
  127. package/src/browser/widgets/index.ts +1 -0
  128. package/src/browser/widgets/split-widget.ts +163 -0
  129. package/src/common/event.ts +6 -0
  130. package/src/common/json-schema.ts +2 -0
  131. package/src/common/menu/menu-types.ts +1 -0
  132. package/src/electron-browser/electron-uri-handler.ts +42 -0
  133. package/src/electron-browser/menu/electron-main-menu-factory.ts +7 -6
  134. package/src/electron-browser/preload.ts +16 -1
  135. package/src/electron-browser/window/electron-window-module.ts +3 -0
  136. package/src/electron-browser/window/external-app-open-handler.ts +1 -1
  137. package/src/electron-common/electron-api.ts +3 -0
  138. package/src/electron-main/electron-api-main.ts +31 -5
  139. package/src/electron-main/electron-main-application.ts +62 -20
  140. package/src/electron-main/theia-electron-window.ts +5 -0
  141. package/src/node/i18n/theia-localization-contribution.ts +12 -8
  142. package/i18n/nls.pt-pt.json +0 -552
@@ -0,0 +1,163 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2024 1C-Soft LLC 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 { Emitter } from 'vscode-languageserver-protocol';
18
+ import { ApplicationShell, StatefulWidget } from '../shell';
19
+ import { BaseWidget, Message, PanelLayout, SplitPanel, Widget } from './widget';
20
+ import { CompositeSaveable, Saveable, SaveableSource } from '../saveable';
21
+ import { Navigatable } from '../navigatable-types';
22
+ import { URI } from '../../common';
23
+
24
+ /**
25
+ * A widget containing a number of panes in a split layout.
26
+ */
27
+ export class SplitWidget extends BaseWidget implements ApplicationShell.TrackableWidgetProvider, SaveableSource, Navigatable, StatefulWidget {
28
+
29
+ protected readonly splitPanel: SplitPanel;
30
+
31
+ protected readonly onDidChangeTrackableWidgetsEmitter = new Emitter<Widget[]>();
32
+ readonly onDidChangeTrackableWidgets = this.onDidChangeTrackableWidgetsEmitter.event;
33
+
34
+ protected readonly compositeSaveable = new CompositeSaveable();
35
+
36
+ protected navigatable?: Navigatable;
37
+
38
+ constructor(options?: SplitPanel.IOptions & { navigatable?: Navigatable }) {
39
+ super();
40
+
41
+ this.toDispose.pushAll([this.onDidChangeTrackableWidgetsEmitter]);
42
+
43
+ this.addClass('theia-split-widget');
44
+
45
+ const layout = new PanelLayout();
46
+ this.layout = layout;
47
+ const that = this;
48
+ this.splitPanel = new class extends SplitPanel {
49
+
50
+ protected override onChildAdded(msg: Widget.ChildMessage): void {
51
+ super.onChildAdded(msg);
52
+ that.onPaneAdded(msg.child);
53
+ }
54
+
55
+ protected override onChildRemoved(msg: Widget.ChildMessage): void {
56
+ super.onChildRemoved(msg);
57
+ that.onPaneRemoved(msg.child);
58
+ }
59
+ }({
60
+ spacing: 1, // --theia-border-width
61
+ ...options
62
+ });
63
+ this.splitPanel.node.tabIndex = -1;
64
+ layout.addWidget(this.splitPanel);
65
+
66
+ this.navigatable = options?.navigatable;
67
+ }
68
+
69
+ get orientation(): SplitPanel.Orientation {
70
+ return this.splitPanel.orientation;
71
+ }
72
+
73
+ set orientation(value: SplitPanel.Orientation) {
74
+ this.splitPanel.orientation = value;
75
+ }
76
+
77
+ relativeSizes(): number[] {
78
+ return this.splitPanel.relativeSizes();
79
+ }
80
+
81
+ setRelativeSizes(sizes: number[]): void {
82
+ this.splitPanel.setRelativeSizes(sizes);
83
+ }
84
+
85
+ get handles(): readonly HTMLDivElement[] {
86
+ return this.splitPanel.handles;
87
+ }
88
+
89
+ get saveable(): Saveable {
90
+ return this.compositeSaveable;
91
+ }
92
+
93
+ getResourceUri(): URI | undefined {
94
+ return this.navigatable?.getResourceUri();
95
+ }
96
+
97
+ createMoveToUri(resourceUri: URI): URI | undefined {
98
+ return this.navigatable?.createMoveToUri(resourceUri);
99
+ }
100
+
101
+ storeState(): SplitWidget.State {
102
+ return { orientation: this.orientation, widgets: this.panes, relativeSizes: this.relativeSizes() };
103
+ }
104
+
105
+ restoreState(oldState: SplitWidget.State): void {
106
+ const { orientation, widgets, relativeSizes } = oldState;
107
+ if (orientation) {
108
+ this.orientation = orientation;
109
+ }
110
+ for (const widget of widgets) {
111
+ this.addPane(widget);
112
+ }
113
+ if (relativeSizes) {
114
+ this.setRelativeSizes(relativeSizes);
115
+ }
116
+ }
117
+
118
+ get panes(): readonly Widget[] {
119
+ return this.splitPanel.widgets;
120
+ }
121
+
122
+ getTrackableWidgets(): Widget[] {
123
+ return [...this.panes];
124
+ }
125
+
126
+ protected fireDidChangeTrackableWidgets(): void {
127
+ this.onDidChangeTrackableWidgetsEmitter.fire(this.getTrackableWidgets());
128
+ }
129
+
130
+ addPane(pane: Widget): void {
131
+ this.splitPanel.addWidget(pane);
132
+ }
133
+
134
+ insertPane(index: number, pane: Widget): void {
135
+ this.splitPanel.insertWidget(index, pane);
136
+ }
137
+
138
+ protected onPaneAdded(pane: Widget): void {
139
+ if (Saveable.isSource(pane)) {
140
+ this.compositeSaveable.add(pane.saveable);
141
+ }
142
+ this.fireDidChangeTrackableWidgets();
143
+ }
144
+
145
+ protected onPaneRemoved(pane: Widget): void {
146
+ if (Saveable.isSource(pane)) {
147
+ this.compositeSaveable.remove(pane.saveable);
148
+ }
149
+ this.fireDidChangeTrackableWidgets();
150
+ }
151
+
152
+ protected override onActivateRequest(msg: Message): void {
153
+ this.splitPanel.node.focus();
154
+ }
155
+ }
156
+
157
+ export namespace SplitWidget {
158
+ export interface State {
159
+ orientation?: SplitPanel.Orientation;
160
+ widgets: readonly Widget[]; // note: don't rename this property; it has special meaning for `ShellLayoutRestorer`
161
+ relativeSizes?: number[];
162
+ }
163
+ }
@@ -89,6 +89,12 @@ export namespace Event {
89
89
  return new Promise(resolve => once(event)(resolve));
90
90
  }
91
91
 
92
+ export function filter<T>(event: Event<T>, predicate: (e: T) => unknown): Event<T>;
93
+ export function filter<T, S extends T>(event: Event<T>, predicate: (e: T) => e is S): Event<S>;
94
+ export function filter<T>(event: Event<T>, predicate: (e: T) => unknown): Event<T> {
95
+ return (listener, thisArg, disposables) => event(e => predicate(e) && listener.call(thisArg, e), undefined, disposables);
96
+ }
97
+
92
98
  /**
93
99
  * Given an event and a `map` function, returns another event which maps each element
94
100
  * through the mapping function.
@@ -36,6 +36,8 @@ export interface IJSONSchema {
36
36
  $id?: string;
37
37
  $schema?: string;
38
38
  type?: JsonType | JsonType[];
39
+ owner?: string;
40
+ group?: string;
39
41
  title?: string;
40
42
  default?: JSONValue;
41
43
  definitions?: IJSONSchemaMap;
@@ -68,6 +68,7 @@ export interface MenuNodeBase extends MenuNodeMetadata, MenuNodeRenderingData {
68
68
  * A menu entry representing an action, e.g. "New File".
69
69
  */
70
70
  export interface MenuAction extends MenuNodeRenderingData, Pick<MenuNodeMetadata, 'when'> {
71
+
71
72
  /**
72
73
  * The command to execute.
73
74
  */
@@ -0,0 +1,42 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2024 STMicroelectronics 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 { FrontendApplicationContribution, OpenerService } from '../browser';
18
+
19
+ import { injectable, inject } from 'inversify';
20
+ import { URI } from '../common';
21
+
22
+ @injectable()
23
+ export class ElectronUriHandlerContribution implements FrontendApplicationContribution {
24
+ @inject(OpenerService)
25
+ protected readonly openenerService: OpenerService;
26
+
27
+ initialize(): void {
28
+ window.electronTheiaCore.setOpenUrlHandler(async url => {
29
+ const uri = new URI(url);
30
+ try {
31
+ const handler = await this.openenerService.getOpener(uri);
32
+ if (handler) {
33
+ await handler.open(uri);
34
+ return true;
35
+ }
36
+ } catch (e) {
37
+ // no handler
38
+ }
39
+ return false;
40
+ });
41
+ }
42
+ }
@@ -117,7 +117,7 @@ export class ElectronMainMenuFactory extends BrowserMainMenuFactory {
117
117
  const maxWidget = document.getElementsByClassName(MAXIMIZED_CLASS);
118
118
  if (preference === 'visible' || (preference === 'classic' && maxWidget.length === 0)) {
119
119
  const menuModel = this.menuProvider.getMenu(MAIN_MENU_BAR);
120
- this._menu = this.fillMenuTemplate([], menuModel, [], { honorDisabled: false, rootMenuPath: MAIN_MENU_BAR });
120
+ this._menu = this.fillMenuTemplate([], menuModel, [], { honorDisabled: false, rootMenuPath: MAIN_MENU_BAR }, false);
121
121
  if (isOSX) {
122
122
  this._menu.unshift(this.createOSXMenu());
123
123
  }
@@ -130,13 +130,14 @@ export class ElectronMainMenuFactory extends BrowserMainMenuFactory {
130
130
 
131
131
  createElectronContextMenu(menuPath: MenuPath, args?: any[], context?: HTMLElement, contextKeyService?: ContextMatcher, skipSingleRootNode?: boolean): MenuDto[] {
132
132
  const menuModel = skipSingleRootNode ? this.menuProvider.removeSingleRootNode(this.menuProvider.getMenu(menuPath), menuPath) : this.menuProvider.getMenu(menuPath);
133
- return this.fillMenuTemplate([], menuModel, args, { showDisabled: true, context, rootMenuPath: menuPath, contextKeyService });
133
+ return this.fillMenuTemplate([], menuModel, args, { showDisabled: true, context, rootMenuPath: menuPath, contextKeyService }, true);
134
134
  }
135
135
 
136
136
  protected fillMenuTemplate(parentItems: MenuDto[],
137
137
  menu: MenuNode,
138
138
  args: unknown[] = [],
139
- options: ElectronMenuOptions
139
+ options: ElectronMenuOptions,
140
+ skipRoot: boolean
140
141
  ): MenuDto[] {
141
142
  const showDisabled = options?.showDisabled !== false;
142
143
  const honorDisabled = options?.honorDisabled !== false;
@@ -148,13 +149,13 @@ export class ElectronMainMenuFactory extends BrowserMainMenuFactory {
148
149
  }
149
150
  const children = CompoundMenuNode.getFlatChildren(menu.children);
150
151
  const myItems: MenuDto[] = [];
151
- children.forEach(child => this.fillMenuTemplate(myItems, child, args, options));
152
+ children.forEach(child => this.fillMenuTemplate(myItems, child, args, options, false));
152
153
  if (myItems.length === 0) {
153
154
  return parentItems;
154
155
  }
155
- if (role === CompoundMenuNodeRole.Submenu) {
156
+ if (!skipRoot && role === CompoundMenuNodeRole.Submenu) {
156
157
  parentItems.push({ label: menu.label, submenu: myItems });
157
- } else if (role === CompoundMenuNodeRole.Group && menu.id !== 'inline') {
158
+ } else {
158
159
  if (parentItems.length && parentItems[parentItems.length - 1].type !== 'separator') {
159
160
  parentItems.push({ type: 'separator' });
160
161
  }
@@ -26,7 +26,8 @@ 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, CHANNEL_OPEN_WITH_SYSTEM_APP
29
+ CHANNEL_WC_METADATA, CHANNEL_ABOUT_TO_CLOSE, CHANNEL_OPEN_WITH_SYSTEM_APP,
30
+ CHANNEL_OPEN_URL
30
31
  } from '../electron-common/electron-api';
31
32
 
32
33
  // eslint-disable-next-line import/no-extraneous-dependencies
@@ -38,6 +39,16 @@ let nextHandlerId = 0;
38
39
  const mainMenuId = 0;
39
40
  let nextMenuId = mainMenuId + 1;
40
41
 
42
+ let openUrlHandler: ((url: string) => Promise<boolean>) | undefined;
43
+
44
+ ipcRenderer.on(CHANNEL_OPEN_URL, async (event: Electron.IpcRendererEvent, url: string, replyChannel: string) => {
45
+ if (openUrlHandler) {
46
+ event.sender.send(replyChannel, await openUrlHandler(url));
47
+ } else {
48
+ event.sender.send(replyChannel, false);
49
+ }
50
+ });
51
+
41
52
  function convertMenu(menu: MenuDto[] | undefined, handlerMap: Map<number, () => void>): InternalMenuDto[] | undefined {
42
53
  if (!menu) {
43
54
  return undefined;
@@ -135,6 +146,10 @@ const api: TheiaCoreAPI = {
135
146
  return Disposable.create(() => ipcRenderer.off(CHANNEL_ABOUT_TO_CLOSE, h));
136
147
  },
137
148
 
149
+ setOpenUrlHandler(handler: (url: string) => Promise<boolean>): void {
150
+ openUrlHandler = handler;
151
+ },
152
+
138
153
  onWindowEvent: function (event: WindowEvent, handler: () => void): Disposable {
139
154
  const h = (_event: unknown, evt: WindowEvent) => {
140
155
  if (event === evt) {
@@ -29,6 +29,7 @@ import { ElectronSecondaryWindowService } from './electron-secondary-window-serv
29
29
  import { bindWindowPreferences } from './electron-window-preferences';
30
30
  import { ElectronWindowService } from './electron-window-service';
31
31
  import { ExternalAppOpenHandler } from './external-app-open-handler';
32
+ import { ElectronUriHandlerContribution } from '../electron-uri-handler';
32
33
 
33
34
  export default new ContainerModule((bind, unbind, isBound, rebind) => {
34
35
  bind(ElectronMainWindowService).toDynamicValue(context =>
@@ -37,6 +38,8 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
37
38
  bindWindowPreferences(bind);
38
39
  bind(WindowService).to(ElectronWindowService).inSingletonScope();
39
40
  bind(FrontendApplicationContribution).toService(WindowService);
41
+ bind(ElectronUriHandlerContribution).toSelf().inSingletonScope();
42
+ bind(FrontendApplicationContribution).toService(ElectronUriHandlerContribution);
40
43
  bind(ClipboardService).to(ElectronClipboardService).inSingletonScope();
41
44
  rebind(FrontendApplicationStateService).to(ElectronFrontendApplicationStateService).inSingletonScope();
42
45
  bind(SecondaryWindowService).to(ElectronSecondaryWindowService).inSingletonScope();
@@ -36,7 +36,7 @@ export class ExternalAppOpenHandler implements OpenHandler {
36
36
  async open(uri: URI): Promise<undefined> {
37
37
  // For files 'file:' scheme, system accepts only the path.
38
38
  // For other protocols e.g. 'vscode:' we use the full URI to propagate target app information.
39
- window.electronTheiaCore.openWithSystemApp(uri.scheme === 'file' ? uri.path.fsPath() : uri.toString(true));
39
+ window.electronTheiaCore.openWithSystemApp(uri.toString(true));
40
40
  return undefined;
41
41
  }
42
42
  }
@@ -74,6 +74,8 @@ export interface TheiaCoreAPI {
74
74
  onAboutToClose(handler: () => void): Disposable;
75
75
  setCloseRequestHandler(handler: (reason: StopReason) => Promise<boolean>): void;
76
76
 
77
+ setOpenUrlHandler(handler: (url: string) => Promise<boolean>): void;
78
+
77
79
  setSecondaryWindowCloseRequestHandler(windowName: string, handler: () => Promise<boolean>): void;
78
80
 
79
81
  toggleDevTools(): void;
@@ -129,6 +131,7 @@ export const CHANNEL_MAXIMIZE = 'Maximize';
129
131
  export const CHANNEL_IS_MAXIMIZED = 'IsMaximized';
130
132
 
131
133
  export const CHANNEL_ABOUT_TO_CLOSE = 'AboutToClose';
134
+ export const CHANNEL_OPEN_URL = 'OpenUrl';
132
135
 
133
136
  export const CHANNEL_UNMAXIMIZE = 'UnMaximize';
134
137
  export const CHANNEL_ON_WINDOW_EVENT = 'OnWindowEvent';
@@ -54,7 +54,8 @@ import {
54
54
  CHANNEL_SET_BACKGROUND_COLOR,
55
55
  CHANNEL_WC_METADATA,
56
56
  CHANNEL_ABOUT_TO_CLOSE,
57
- CHANNEL_OPEN_WITH_SYSTEM_APP
57
+ CHANNEL_OPEN_WITH_SYSTEM_APP,
58
+ CHANNEL_OPEN_URL
58
59
  } from '../electron-common/electron-api';
59
60
  import { ElectronMainApplication, ElectronMainApplicationContribution } from './electron-main-application';
60
61
  import { Disposable, DisposableCollection, isOSX, MaybePromise } from '../common';
@@ -165,8 +166,8 @@ export class TheiaMainApi implements ElectronMainApplicationContribution {
165
166
  shell.showItemInFolder(fsPath);
166
167
  });
167
168
 
168
- ipcMain.on(CHANNEL_OPEN_WITH_SYSTEM_APP, (event, fsPath) => {
169
- shell.openPath(fsPath);
169
+ ipcMain.on(CHANNEL_OPEN_WITH_SYSTEM_APP, (event, uri) => {
170
+ shell.openExternal(uri);
170
171
  });
171
172
 
172
173
  ipcMain.handle(CHANNEL_GET_TITLE_STYLE_AT_STARTUP, event => application.getTitleBarStyleAtStartup(event.sender));
@@ -241,9 +242,20 @@ export class TheiaMainApi implements ElectronMainApplicationContribution {
241
242
  });
242
243
  }
243
244
 
245
+ private isASCI(accelerator: string | undefined): boolean {
246
+ if (typeof accelerator !== 'string') {
247
+ return false;
248
+ }
249
+ for (let i = 0; i < accelerator.length; i++) {
250
+ if (accelerator.charCodeAt(i) > 127) {
251
+ return false;
252
+ }
253
+ }
254
+ return true;
255
+ }
256
+
244
257
  fromMenuDto(sender: WebContents, menuId: number, menuDto: InternalMenuDto[]): MenuItemConstructorOptions[] {
245
258
  return menuDto.map(dto => {
246
-
247
259
  const result: MenuItemConstructorOptions = {
248
260
  id: dto.id,
249
261
  label: dto.label,
@@ -252,7 +264,7 @@ export class TheiaMainApi implements ElectronMainApplicationContribution {
252
264
  enabled: dto.enabled,
253
265
  visible: dto.visible,
254
266
  role: dto.role,
255
- accelerator: dto.accelerator
267
+ accelerator: this.isASCI(dto.accelerator) ? dto.accelerator : undefined
256
268
  };
257
269
  if (dto.submenu) {
258
270
  result.submenu = this.fromMenuDto(sender, menuId, dto.submenu);
@@ -274,6 +286,20 @@ export namespace TheiaRendererAPI {
274
286
  wc.send(CHANNEL_ON_WINDOW_EVENT, event);
275
287
  }
276
288
 
289
+ export function openUrl(wc: WebContents, url: string): Promise<boolean> {
290
+ return new Promise<boolean>(resolve => {
291
+ const channelNr = nextReplyChannel++;
292
+ const replyChannel = `openUrl${channelNr}`;
293
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
294
+ const l = createDisposableListener(ipcMain, replyChannel, (e, args: any[]) => {
295
+ l.dispose();
296
+ resolve(args[0]);
297
+ });
298
+
299
+ wc.send(CHANNEL_OPEN_URL, url, replyChannel);
300
+ });
301
+ }
302
+
277
303
  export function sendAboutToClose(wc: WebContents): Promise<void> {
278
304
  return new Promise<void>(resolve => {
279
305
  const channelNr = nextReplyChannel++;
@@ -117,13 +117,13 @@ export class ElectronMainProcessArgv {
117
117
  return 1;
118
118
  }
119
119
 
120
- protected get isBundledElectronApp(): boolean {
120
+ get isBundledElectronApp(): boolean {
121
121
  // process.defaultApp is either set by electron in an electron unbundled app, or undefined
122
122
  // see https://github.com/electron/electron/blob/master/docs/api/process.md#processdefaultapp-readonly
123
123
  return this.isElectronApp && !(process as ElectronMainProcessArgv.ElectronMainProcess).defaultApp;
124
124
  }
125
125
 
126
- protected get isElectronApp(): boolean {
126
+ get isElectronApp(): boolean {
127
127
  // process.versions.electron is either set by electron, or undefined
128
128
  // see https://github.com/electron/electron/blob/master/docs/api/process.md#processversionselectron-readonly
129
129
  return !!(process as ElectronMainProcessArgv.ElectronMainProcess).versions.electron;
@@ -183,6 +183,7 @@ export class ElectronMainApplication {
183
183
  protected customBackgroundColor?: string;
184
184
  protected didUseNativeWindowFrameOnStart = new Map<number, boolean>();
185
185
  protected windows = new Map<number, TheiaElectronWindow>();
186
+ protected activeWindowStack: number[] = [];
186
187
  protected restarting = false;
187
188
 
188
189
  /** Used to temporarily store the reference to an early created main window */
@@ -229,7 +230,7 @@ export class ElectronMainApplication {
229
230
  this.useNativeWindowFrame = this.getTitleBarStyle(config) === 'native';
230
231
  this._config = config;
231
232
  this.hookApplicationEvents();
232
- this.showInitialWindow();
233
+ this.showInitialWindow(argv.includes('--open-url') ? argv[argv.length - 1] : undefined);
233
234
  const port = await this.startBackend();
234
235
  this._backendPort.resolve(port);
235
236
  await app.whenReady();
@@ -317,7 +318,7 @@ export class ElectronMainApplication {
317
318
  !('THEIA_ELECTRON_NO_EARLY_WINDOW' in process.env && process.env.THEIA_ELECTRON_NO_EARLY_WINDOW === '1');
318
319
  }
319
320
 
320
- protected showInitialWindow(): void {
321
+ protected showInitialWindow(urlToOpen: string | undefined): void {
321
322
  if (this.isShowWindowEarly() || this.isShowSplashScreen()) {
322
323
  app.whenReady().then(async () => {
323
324
  const options = await this.getLastWindowOptions();
@@ -326,7 +327,11 @@ export class ElectronMainApplication {
326
327
  options.preventAutomaticShow = true;
327
328
  }
328
329
  this.initialWindow = await this.createWindow({ ...options });
329
-
330
+ TheiaRendererAPI.onApplicationStateChanged(this.initialWindow.webContents, state => {
331
+ if (state === 'ready' && urlToOpen) {
332
+ this.openUrl(urlToOpen);
333
+ }
334
+ });
330
335
  if (this.isShowSplashScreen()) {
331
336
  console.log('Showing splash screen');
332
337
  this.configureAndShowSplashScreen(this.initialWindow);
@@ -410,11 +415,25 @@ export class ElectronMainApplication {
410
415
  options = this.avoidOverlap(options);
411
416
  const electronWindow = this.windowFactory(options, this.config);
412
417
  const id = electronWindow.window.webContents.id;
418
+ this.activeWindowStack.push(id);
413
419
  this.windows.set(id, electronWindow);
414
- electronWindow.onDidClose(() => this.windows.delete(id));
420
+ electronWindow.onDidClose(() => {
421
+ const stackIndex = this.activeWindowStack.indexOf(id);
422
+ if (stackIndex >= 0) {
423
+ this.activeWindowStack.splice(stackIndex, 1);
424
+ }
425
+ this.windows.delete(id);
426
+ });
415
427
  electronWindow.window.on('maximize', () => TheiaRendererAPI.sendWindowEvent(electronWindow.window.webContents, 'maximize'));
416
428
  electronWindow.window.on('unmaximize', () => TheiaRendererAPI.sendWindowEvent(electronWindow.window.webContents, 'unmaximize'));
417
- electronWindow.window.on('focus', () => TheiaRendererAPI.sendWindowEvent(electronWindow.window.webContents, 'focus'));
429
+ electronWindow.window.on('focus', () => {
430
+ const stackIndex = this.activeWindowStack.indexOf(id);
431
+ if (stackIndex >= 0) {
432
+ this.activeWindowStack.splice(stackIndex, 1);
433
+ }
434
+ this.activeWindowStack.unshift(id);
435
+ TheiaRendererAPI.sendWindowEvent(electronWindow.window.webContents, 'focus');
436
+ });
418
437
  this.attachSaveWindowState(electronWindow.window);
419
438
 
420
439
  return electronWindow.window;
@@ -521,6 +540,15 @@ export class ElectronMainApplication {
521
540
  }
522
541
  }
523
542
 
543
+ async openUrl(url: string): Promise<void> {
544
+ for (const id of this.activeWindowStack) {
545
+ const window = this.windows.get(id);
546
+ if (window && await window.openUrl(url)) {
547
+ break;
548
+ }
549
+ }
550
+ }
551
+
524
552
  protected async createWindowUri(params: WindowSearchParams = {}): Promise<URI> {
525
553
  if (!('port' in params)) {
526
554
  params.port = (await this.backendPort).toString();
@@ -696,6 +724,16 @@ export class ElectronMainApplication {
696
724
  app.on('second-instance', this.onSecondInstance.bind(this));
697
725
  app.on('window-all-closed', this.onWindowAllClosed.bind(this));
698
726
  app.on('web-contents-created', this.onWebContentsCreated.bind(this));
727
+
728
+ if (isWindows) {
729
+ const args = this.processArgv.isBundledElectronApp ? [] : [app.getAppPath()];
730
+ args.push('--open-url');
731
+ app.setAsDefaultProtocolClient(this.config.electron.uriScheme, process.execPath, args);
732
+ } else {
733
+ app.on('open-url', (evt, url) => {
734
+ this.openUrl(url);
735
+ });
736
+ }
699
737
  }
700
738
 
701
739
  protected onWillQuit(event: ElectronEvent): void {
@@ -703,19 +741,23 @@ export class ElectronMainApplication {
703
741
  }
704
742
 
705
743
  protected async onSecondInstance(event: ElectronEvent, argv: string[], cwd: string): Promise<void> {
706
- createYargs(this.processArgv.getProcessArgvWithoutBin(argv), process.cwd())
707
- .help(false)
708
- .command('$0 [file]', false,
709
- cmd => cmd
710
- .positional('file', { type: 'string' }),
711
- async args => {
712
- this.handleMainCommand({
713
- file: args.file,
714
- cwd: process.cwd(),
715
- secondInstance: true
716
- });
717
- },
718
- ).parse();
744
+ if (argv.includes('--open-url')) {
745
+ this.openUrl(argv[argv.length - 1]);
746
+ } else {
747
+ createYargs(this.processArgv.getProcessArgvWithoutBin(argv), process.cwd())
748
+ .help(false)
749
+ .command('$0 [file]', false,
750
+ cmd => cmd
751
+ .positional('file', { type: 'string' }),
752
+ async args => {
753
+ await this.handleMainCommand({
754
+ file: args.file,
755
+ cwd: process.cwd(),
756
+ secondInstance: true
757
+ });
758
+ },
759
+ ).parse();
760
+ }
719
761
  }
720
762
 
721
763
  protected onWebContentsCreated(event: ElectronEvent, webContents: WebContents): void {
@@ -58,6 +58,7 @@ enum ClosingState {
58
58
 
59
59
  @injectable()
60
60
  export class TheiaElectronWindow {
61
+
61
62
  @inject(TheiaBrowserWindowOptions) protected readonly options: TheiaBrowserWindowOptions;
62
63
  @inject(WindowApplicationConfig) protected readonly config: WindowApplicationConfig;
63
64
  @inject(ElectronMainApplicationGlobals) protected readonly globals: ElectronMainApplicationGlobals;
@@ -202,6 +203,10 @@ export class TheiaElectronWindow {
202
203
  this.toDispose.push(TheiaRendererAPI.onRequestReload(this.window.webContents, (newUrl?: string) => this.reload(newUrl)));
203
204
  }
204
205
 
206
+ openUrl(url: string): Promise<boolean> {
207
+ return TheiaRendererAPI.openUrl(this.window.webContents, url);
208
+ }
209
+
205
210
  dispose(): void {
206
211
  this.toDispose.dispose();
207
212
  }
@@ -20,17 +20,21 @@ import { LocalizationContribution, LocalizationRegistry } from './localization-c
20
20
  @injectable()
21
21
  export class TheiaLocalizationContribution implements LocalizationContribution {
22
22
  async registerLocalizations(registry: LocalizationRegistry): Promise<void> {
23
- registry.registerLocalizationFromRequire('cs', require('../../../i18n/nls.cs.json'));
24
- registry.registerLocalizationFromRequire('de', require('../../../i18n/nls.de.json'));
25
- registry.registerLocalizationFromRequire('es', require('../../../i18n/nls.es.json'));
23
+ // Attempt to use the same languages as VS Code
24
+ // See https://code.visualstudio.com/docs/getstarted/locales#_available-locales
25
+ registry.registerLocalizationFromRequire('zh-cn', require('../../../i18n/nls.zh-cn.json'));
26
+ registry.registerLocalizationFromRequire('zh-tw', require('../../../i18n/nls.zh-tw.json'));
26
27
  registry.registerLocalizationFromRequire('fr', require('../../../i18n/nls.fr.json'));
27
- registry.registerLocalizationFromRequire('hu', require('../../../i18n/nls.hu.json'));
28
+ registry.registerLocalizationFromRequire('de', require('../../../i18n/nls.de.json'));
28
29
  registry.registerLocalizationFromRequire('it', require('../../../i18n/nls.it.json'));
30
+ registry.registerLocalizationFromRequire('es', require('../../../i18n/nls.es.json'));
29
31
  registry.registerLocalizationFromRequire('ja', require('../../../i18n/nls.ja.json'));
30
- registry.registerLocalizationFromRequire('pl', require('../../../i18n/nls.pl.json'));
31
- registry.registerLocalizationFromRequire('pt-br', require('../../../i18n/nls.pt-br.json'));
32
- registry.registerLocalizationFromRequire('pt-pt', require('../../../i18n/nls.pt-pt.json'));
32
+ registry.registerLocalizationFromRequire('ko', require('../../../i18n/nls.ko.json'));
33
33
  registry.registerLocalizationFromRequire('ru', require('../../../i18n/nls.ru.json'));
34
- registry.registerLocalizationFromRequire('zh-cn', require('../../../i18n/nls.zh-cn.json'));
34
+ registry.registerLocalizationFromRequire('pt-br', require('../../../i18n/nls.pt-br.json'));
35
+ registry.registerLocalizationFromRequire('tr', require('../../../i18n/nls.tr.json'));
36
+ registry.registerLocalizationFromRequire('pl', require('../../../i18n/nls.pl.json'));
37
+ registry.registerLocalizationFromRequire('cs', require('../../../i18n/nls.cs.json'));
38
+ registry.registerLocalizationFromRequire('hu', require('../../../i18n/nls.hu.json'));
35
39
  }
36
40
  }