@theia/core 1.76.0-next.6 → 1.76.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 (179) hide show
  1. package/README.md +8 -8
  2. package/i18n/nls.cs.json +187 -49
  3. package/i18n/nls.de.json +187 -49
  4. package/i18n/nls.es.json +187 -49
  5. package/i18n/nls.fr.json +187 -49
  6. package/i18n/nls.hu.json +187 -49
  7. package/i18n/nls.it.json +187 -49
  8. package/i18n/nls.ja.json +187 -49
  9. package/i18n/nls.json +187 -49
  10. package/i18n/nls.ko.json +187 -49
  11. package/i18n/nls.pl.json +187 -49
  12. package/i18n/nls.pt-br.json +187 -49
  13. package/i18n/nls.ru.json +187 -49
  14. package/i18n/nls.tr.json +187 -49
  15. package/i18n/nls.zh-cn.json +187 -49
  16. package/i18n/nls.zh-tw.json +187 -49
  17. package/lib/browser/catalog.json +175 -95
  18. package/lib/browser/dialogs.d.ts +2 -0
  19. package/lib/browser/dialogs.d.ts.map +1 -1
  20. package/lib/browser/dialogs.js +9 -5
  21. package/lib/browser/dialogs.js.map +1 -1
  22. package/lib/browser/frontend-application-module.d.ts.map +1 -1
  23. package/lib/browser/frontend-application-module.js +3 -0
  24. package/lib/browser/frontend-application-module.js.map +1 -1
  25. package/lib/browser/index.d.ts +1 -0
  26. package/lib/browser/index.d.ts.map +1 -1
  27. package/lib/browser/index.js +1 -0
  28. package/lib/browser/index.js.map +1 -1
  29. package/lib/browser/resource-context-key.d.ts +25 -0
  30. package/lib/browser/resource-context-key.d.ts.map +1 -1
  31. package/lib/browser/resource-context-key.js +41 -16
  32. package/lib/browser/resource-context-key.js.map +1 -1
  33. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.d.ts +7 -5
  34. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.d.ts.map +1 -1
  35. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.js +13 -12
  36. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.js.map +1 -1
  37. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.d.ts +11 -1
  38. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.d.ts.map +1 -1
  39. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.js +38 -3
  40. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.js.map +1 -1
  41. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.d.ts +2 -1
  42. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.d.ts.map +1 -1
  43. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.js +4 -2
  44. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.js.map +1 -1
  45. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.spec.js +123 -2
  46. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.spec.js.map +1 -1
  47. package/lib/browser/shell/tab-bar-toolbar/tab-toolbar-item.d.ts +12 -4
  48. package/lib/browser/shell/tab-bar-toolbar/tab-toolbar-item.d.ts.map +1 -1
  49. package/lib/browser/shell/tab-bar-toolbar/tab-toolbar-item.js +2 -2
  50. package/lib/browser/shell/tab-bar-toolbar/tab-toolbar-item.js.map +1 -1
  51. package/lib/browser/tree/tree-widget.d.ts +20 -2
  52. package/lib/browser/tree/tree-widget.d.ts.map +1 -1
  53. package/lib/browser/tree/tree-widget.js +31 -13
  54. package/lib/browser/tree/tree-widget.js.map +1 -1
  55. package/lib/browser/tree/tree-widget.spec.d.ts +2 -0
  56. package/lib/browser/tree/tree-widget.spec.d.ts.map +1 -0
  57. package/lib/browser/tree/tree-widget.spec.js +192 -0
  58. package/lib/browser/tree/tree-widget.spec.js.map +1 -0
  59. package/lib/browser/view-container.d.ts.map +1 -1
  60. package/lib/browser/view-container.js +2 -2
  61. package/lib/browser/view-container.js.map +1 -1
  62. package/lib/browser/widget-context-key-contribution.d.ts +17 -0
  63. package/lib/browser/widget-context-key-contribution.d.ts.map +1 -0
  64. package/lib/browser/widget-context-key-contribution.js +20 -0
  65. package/lib/browser/widget-context-key-contribution.js.map +1 -0
  66. package/lib/browser/window/browser-window-module.d.ts.map +1 -1
  67. package/lib/browser/window/browser-window-module.js +3 -0
  68. package/lib/browser/window/browser-window-module.js.map +1 -1
  69. package/lib/browser/window/window-launch-args.d.ts +29 -0
  70. package/lib/browser/window/window-launch-args.d.ts.map +1 -0
  71. package/lib/browser/window/window-launch-args.js +39 -0
  72. package/lib/browser/window/window-launch-args.js.map +1 -0
  73. package/lib/browser-only/frontend-only-application-module.d.ts.map +1 -1
  74. package/lib/browser-only/frontend-only-application-module.js +5 -2
  75. package/lib/browser-only/frontend-only-application-module.js.map +1 -1
  76. package/lib/browser-only/messaging/frontend-only-service-connection-provider.d.ts +3 -1
  77. package/lib/browser-only/messaging/frontend-only-service-connection-provider.d.ts.map +1 -1
  78. package/lib/browser-only/messaging/frontend-only-service-connection-provider.js +8 -7
  79. package/lib/browser-only/messaging/frontend-only-service-connection-provider.js.map +1 -1
  80. package/lib/common/index.d.ts +2 -0
  81. package/lib/common/index.d.ts.map +1 -1
  82. package/lib/common/index.js +2 -0
  83. package/lib/common/index.js.map +1 -1
  84. package/lib/common/launch-arguments.d.ts +32 -0
  85. package/lib/common/launch-arguments.d.ts.map +1 -0
  86. package/lib/common/launch-arguments.js +44 -0
  87. package/lib/common/launch-arguments.js.map +1 -0
  88. package/lib/common/remote-cli-args-contribution.d.ts +22 -0
  89. package/lib/common/remote-cli-args-contribution.d.ts.map +1 -0
  90. package/lib/common/remote-cli-args-contribution.js +20 -0
  91. package/lib/common/remote-cli-args-contribution.js.map +1 -0
  92. package/lib/electron-browser/messaging/electron-ipc-connection-source.d.ts +6 -3
  93. package/lib/electron-browser/messaging/electron-ipc-connection-source.d.ts.map +1 -1
  94. package/lib/electron-browser/messaging/electron-ipc-connection-source.js +4 -3
  95. package/lib/electron-browser/messaging/electron-ipc-connection-source.js.map +1 -1
  96. package/lib/electron-browser/preload.d.ts.map +1 -1
  97. package/lib/electron-browser/preload.js +3 -1
  98. package/lib/electron-browser/preload.js.map +1 -1
  99. package/lib/electron-browser/window/electron-window-launch-args.d.ts +14 -0
  100. package/lib/electron-browser/window/electron-window-launch-args.d.ts.map +1 -0
  101. package/lib/electron-browser/window/electron-window-launch-args.js +41 -0
  102. package/lib/electron-browser/window/electron-window-launch-args.js.map +1 -0
  103. package/lib/electron-browser/window/electron-window-launch-args.spec.d.ts +2 -0
  104. package/lib/electron-browser/window/electron-window-launch-args.spec.d.ts.map +1 -0
  105. package/lib/electron-browser/window/electron-window-launch-args.spec.js +43 -0
  106. package/lib/electron-browser/window/electron-window-launch-args.spec.js.map +1 -0
  107. package/lib/electron-browser/window/electron-window-module.d.ts.map +1 -1
  108. package/lib/electron-browser/window/electron-window-module.js +7 -0
  109. package/lib/electron-browser/window/electron-window-module.js.map +1 -1
  110. package/lib/electron-common/electron-api.d.ts +10 -0
  111. package/lib/electron-common/electron-api.d.ts.map +1 -1
  112. package/lib/electron-common/electron-api.js.map +1 -1
  113. package/lib/electron-main/electron-api-main.d.ts.map +1 -1
  114. package/lib/electron-main/electron-api-main.js +7 -1
  115. package/lib/electron-main/electron-api-main.js.map +1 -1
  116. package/lib/electron-main/electron-main-application-module.d.ts.map +1 -1
  117. package/lib/electron-main/electron-main-application-module.js +2 -0
  118. package/lib/electron-main/electron-main-application-module.js.map +1 -1
  119. package/lib/electron-main/electron-main-application.d.ts +57 -2
  120. package/lib/electron-main/electron-main-application.d.ts.map +1 -1
  121. package/lib/electron-main/electron-main-application.js +68 -8
  122. package/lib/electron-main/electron-main-application.js.map +1 -1
  123. package/lib/electron-main/launch-args-store.d.ts +45 -0
  124. package/lib/electron-main/launch-args-store.d.ts.map +1 -0
  125. package/lib/electron-main/launch-args-store.js +76 -0
  126. package/lib/electron-main/launch-args-store.js.map +1 -0
  127. package/lib/electron-main/launch-args-store.spec.d.ts +2 -0
  128. package/lib/electron-main/launch-args-store.spec.d.ts.map +1 -0
  129. package/lib/electron-main/launch-args-store.spec.js +53 -0
  130. package/lib/electron-main/launch-args-store.spec.js.map +1 -0
  131. package/lib/electron-main/launch-argv-parser.d.ts +19 -0
  132. package/lib/electron-main/launch-argv-parser.d.ts.map +1 -0
  133. package/lib/electron-main/launch-argv-parser.js +71 -0
  134. package/lib/electron-main/launch-argv-parser.js.map +1 -0
  135. package/lib/electron-main/launch-argv-parser.spec.d.ts +2 -0
  136. package/lib/electron-main/launch-argv-parser.spec.d.ts.map +1 -0
  137. package/lib/electron-main/launch-argv-parser.spec.js +62 -0
  138. package/lib/electron-main/launch-argv-parser.spec.js.map +1 -0
  139. package/lib/electron-main/theia-electron-window.d.ts +2 -0
  140. package/lib/electron-main/theia-electron-window.d.ts.map +1 -1
  141. package/lib/electron-main/theia-electron-window.js +12 -0
  142. package/lib/electron-main/theia-electron-window.js.map +1 -1
  143. package/package.json +11 -11
  144. package/src/browser/dialogs.ts +10 -5
  145. package/src/browser/frontend-application-module.ts +5 -1
  146. package/src/browser/index.ts +1 -0
  147. package/src/browser/resource-context-key.ts +61 -16
  148. package/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.tsx +13 -12
  149. package/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.ts +36 -4
  150. package/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar.spec.ts +150 -3
  151. package/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar.tsx +6 -3
  152. package/src/browser/shell/tab-bar-toolbar/tab-toolbar-item.tsx +13 -5
  153. package/src/browser/style/tree.css +7 -0
  154. package/src/browser/tree/tree-widget.spec.tsx +245 -0
  155. package/src/browser/tree/tree-widget.tsx +36 -12
  156. package/src/browser/view-container.ts +3 -3
  157. package/src/browser/widget-context-key-contribution.ts +34 -0
  158. package/src/browser/window/browser-window-module.ts +3 -0
  159. package/src/browser/window/window-launch-args.ts +53 -0
  160. package/src/browser-only/frontend-only-application-module.ts +5 -2
  161. package/src/browser-only/messaging/frontend-only-service-connection-provider.ts +3 -1
  162. package/src/common/i18n/nls.metadata.json +5246 -1106
  163. package/src/common/index.ts +2 -0
  164. package/src/common/launch-arguments.ts +61 -0
  165. package/src/common/remote-cli-args-contribution.ts +39 -0
  166. package/src/electron-browser/messaging/electron-ipc-connection-source.ts +6 -3
  167. package/src/electron-browser/preload.ts +3 -1
  168. package/src/electron-browser/window/electron-window-launch-args.spec.ts +45 -0
  169. package/src/electron-browser/window/electron-window-launch-args.ts +38 -0
  170. package/src/electron-browser/window/electron-window-module.ts +7 -0
  171. package/src/electron-common/electron-api.ts +10 -0
  172. package/src/electron-main/electron-api-main.ts +7 -1
  173. package/src/electron-main/electron-main-application-module.ts +2 -0
  174. package/src/electron-main/electron-main-application.ts +101 -8
  175. package/src/electron-main/launch-args-store.spec.ts +59 -0
  176. package/src/electron-main/launch-args-store.ts +73 -0
  177. package/src/electron-main/launch-argv-parser.spec.ts +70 -0
  178. package/src/electron-main/launch-argv-parser.ts +66 -0
  179. package/src/electron-main/theia-electron-window.ts +9 -0
@@ -24,6 +24,7 @@ export * from './core-preferences';
24
24
  export * from './disposable';
25
25
  export * from './event';
26
26
  export * from './inversify-utils';
27
+ export * from './launch-arguments';
27
28
  export * from './listener';
28
29
  export * from './logger';
29
30
  export * from './logger-sanitizer';
@@ -44,6 +45,7 @@ export * from './progress-service';
44
45
  export * from './progress-service-protocol';
45
46
  export * from './quick-pick-service';
46
47
  export * from './reference';
48
+ export * from './remote-cli-args-contribution';
47
49
  export * from './resource';
48
50
  export * from './selection';
49
51
  export * from './selection-service';
@@ -0,0 +1,61 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 EclipseSource GmbH 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
+ /**
18
+ * The CLI options of a launch, already parsed. A window opened for a *forwarded* launch (see the
19
+ * `second-instance` handling in `ElectronMainApplication`) receives this rather than a raw `argv`:
20
+ * the command line is parsed once, in the Electron main process, so that no consumer has to
21
+ * re-implement the argument grammar and the frontend never sees an unparsed command line.
22
+ *
23
+ * @experimental
24
+ */
25
+ export interface LaunchArguments {
26
+
27
+ /**
28
+ * The values of each (possibly repeated) `--name value` / `--name=value` option, in the order
29
+ * they appeared on the command line.
30
+ */
31
+ readonly values: Readonly<Record<string, readonly string[]>>;
32
+
33
+ /** The options that were explicitly negated, i.e. `--no-name` or `--name=false`. */
34
+ readonly negated: readonly string[];
35
+ }
36
+
37
+ /** @experimental */
38
+ export namespace LaunchArguments {
39
+
40
+ /** A launch that carried no options. */
41
+ export const EMPTY: LaunchArguments = { values: {}, negated: [] };
42
+
43
+ /** Every value of a (possibly repeated) option, or an empty array when the option is absent. */
44
+ export function values(args: LaunchArguments, name: string): readonly string[] {
45
+ return args.values[name] ?? [];
46
+ }
47
+
48
+ /**
49
+ * The last value of an option, or `undefined` when the option is absent. Last wins, so that a
50
+ * repeated option behaves like it does on a normal command line.
51
+ */
52
+ export function lastValue(args: LaunchArguments, name: string): string | undefined {
53
+ const all = values(args, name);
54
+ return all.length > 0 ? all[all.length - 1] : undefined;
55
+ }
56
+
57
+ /** Whether a boolean option was explicitly disabled, i.e. `--no-name` or `--name=false`. */
58
+ export function isNegated(args: LaunchArguments, name: string): boolean {
59
+ return args.negated.includes(name);
60
+ }
61
+ }
@@ -0,0 +1,39 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 EclipseSource GmbH 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 { MaybePromise } from './types';
18
+
19
+ export const RemoteCliArgsContribution = Symbol('RemoteCliArgsContribution');
20
+
21
+ /**
22
+ * Frontend contribution that provides additional CLI arguments to pass to a remote backend
23
+ * started for the *current* window (e.g. when attaching to a dev container).
24
+ *
25
+ * This lets per-window CLI options (such as `--session-preference`) that were forwarded to a
26
+ * window be re-applied on the remote backend. Unlike the node-side `RemoteCliContribution#enhanceArgs`,
27
+ * which reads process-global state on the shared backend, this contribution runs in the renderer and
28
+ * therefore has access to the current window's context.
29
+ *
30
+ * @experimental
31
+ */
32
+ export interface RemoteCliArgsContribution {
33
+ /**
34
+ * Returns the extra CLI arguments to append when starting a remote backend for the current
35
+ * window. Implementations should return an empty array when they have nothing to contribute
36
+ * (e.g. for a regular cold-start window whose args are already applied by the shared backend).
37
+ */
38
+ getRemoteCliArgs(): MaybePromise<string[]>;
39
+ }
@@ -15,12 +15,15 @@
15
15
  // *****************************************************************************
16
16
 
17
17
  import { injectable, interfaces } from 'inversify';
18
- import { RpcProxy } from '../../common/messaging';
19
- import { AbstractChannel, Channel, Emitter, Event, MaybePromise, WriteBuffer } from '../../common';
18
+ import { RpcProxy } from '../../common/messaging/proxy-factory';
19
+ import { AbstractChannel, Channel } from '../../common/message-rpc/channel';
20
+ import { WriteBuffer } from '../../common/message-rpc/message-buffer';
21
+ import { Emitter, Event } from '../../common/event';
22
+ import { MaybePromise } from '../../common/types';
20
23
  import { Uint8ArrayReadBuffer, Uint8ArrayWriteBuffer } from '../../common/message-rpc/uint8-array-message-buffer';
21
24
  import { ServiceConnectionProvider } from '../../browser/messaging/service-connection-provider';
22
25
  import { ConnectionSource } from '../../browser/messaging/connection-source';
23
- import { FrontendApplicationContribution } from '../../browser';
26
+ import { FrontendApplicationContribution } from '../../browser/frontend-application-contribution';
24
27
 
25
28
  export interface ElectronIpcOptions {
26
29
  }
@@ -269,7 +269,9 @@ export function preload(): void {
269
269
  }
270
270
  }
271
271
  });
272
- api.WindowMetadata.webcontentId = ipcRenderer.sendSync(CHANNEL_WC_METADATA);
272
+ // Synchronous, so the metadata (including the parsed options of a forwarded launch) is in place
273
+ // before any frontend code runs.
274
+ api.WindowMetadata = ipcRenderer.sendSync(CHANNEL_WC_METADATA);
273
275
 
274
276
  contextBridge.exposeInMainWorld('electronTheiaCore', api);
275
277
  }
@@ -0,0 +1,45 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 EclipseSource GmbH 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 { expect } from 'chai';
18
+ import { LaunchArguments } from '../../common/launch-arguments';
19
+ import { ElectronWindowLaunchArgs } from './electron-window-launch-args';
20
+
21
+ class TestElectronWindowLaunchArgs extends ElectronWindowLaunchArgs {
22
+ constructor(protected readonly metadata: { webcontentId: string, launchArgs?: LaunchArguments }) {
23
+ super();
24
+ }
25
+
26
+ override getLaunchArgs(): LaunchArguments | undefined {
27
+ return this.metadata.launchArgs;
28
+ }
29
+ }
30
+
31
+ describe('ElectronWindowLaunchArgs', () => {
32
+
33
+ it('returns undefined for a cold-start window (main attached no options)', () => {
34
+ const service = new TestElectronWindowLaunchArgs({ webcontentId: '1' });
35
+ expect(service.getLaunchArgs()).to.equal(undefined);
36
+ });
37
+
38
+ it('returns the options attached to the window metadata', () => {
39
+ const launchArgs: LaunchArguments = { values: { 'attach-container': ['B'], 'session-preference': ['foo=1'] }, negated: [] };
40
+ const service = new TestElectronWindowLaunchArgs({ webcontentId: '1', launchArgs });
41
+ const args = service.getLaunchArgs()!;
42
+ expect(LaunchArguments.lastValue(args, 'attach-container')).to.equal('B');
43
+ expect(LaunchArguments.values(args, 'session-preference')).to.deep.equal(['foo=1']);
44
+ });
45
+ });
@@ -0,0 +1,38 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 EclipseSource GmbH 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 } from 'inversify';
18
+ import { LaunchArguments } from '../../common/launch-arguments';
19
+ import { WindowLaunchArgs } from '../../browser/window/window-launch-args';
20
+
21
+ /**
22
+ * Electron implementation of {@link WindowLaunchArgs}. Reads the options the main process attached
23
+ * to this window's metadata, which the preload script fetches synchronously before any frontend code
24
+ * runs, and which main keys by the IPC sender rather than by any value from the URL (see
25
+ * `LaunchArgsStore`).
26
+ *
27
+ * @experimental
28
+ */
29
+ @injectable()
30
+ export class ElectronWindowLaunchArgs implements WindowLaunchArgs {
31
+
32
+ getLaunchArgs(): LaunchArguments | undefined {
33
+ // `undefined` for a cold-start window, letting callers fall back to the shared backend, and
34
+ // the stored options for a forwarded window (kept for as long as the window shows the
35
+ // frontend the launch opened, so a plain reload still sees them).
36
+ return window.electronTheiaCore.WindowMetadata.launchArgs;
37
+ }
38
+ }
@@ -33,6 +33,8 @@ import { ElectronUriHandlerContribution } from '../electron-uri-handler';
33
33
  import { bindRootContributionProvider } from '../../common';
34
34
  import { WindowTitleContribution } from '../../browser/window/window-title-service';
35
35
  import { WindowFocusService } from '../../browser/window/window-focus-service';
36
+ import { WindowLaunchArgs } from '../../browser/window/window-launch-args';
37
+ import { ElectronWindowLaunchArgs } from './electron-window-launch-args';
36
38
  import { WindowZoomStatusBarItem } from './window-zoom-status-bar-item';
37
39
  import '../../../src/electron-browser/style/window-zoom-action-bar.css';
38
40
 
@@ -47,6 +49,11 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
47
49
  bind(FrontendApplicationContribution).toService(ElectronUriHandlerContribution);
48
50
  bind(ClipboardService).to(ElectronClipboardService).inSingletonScope();
49
51
  rebind(FrontendApplicationStateService).to(ElectronFrontendApplicationStateService).inSingletonScope();
52
+ // `browser-window-module` (which binds `WindowLaunchArgs`) is not loaded in Electron, since the
53
+ // `window` extension provides `electron-window-module` as its `frontendElectron` replacement.
54
+ // Bind here directly, mirroring how `WindowService` is bound in each of the two modules.
55
+ bind(ElectronWindowLaunchArgs).toSelf().inSingletonScope();
56
+ bind(WindowLaunchArgs).toService(ElectronWindowLaunchArgs);
50
57
  bind(SecondaryWindowService).to(ElectronSecondaryWindowService).inSingletonScope();
51
58
  bind(ExternalAppOpenHandler).toSelf().inSingletonScope();
52
59
  bind(OpenHandler).toService(ExternalAppOpenHandler);
@@ -18,6 +18,7 @@ import { NativeKeyboardLayout } from '../common/keyboard/keyboard-layout-provide
18
18
  import { Disposable } from '../common';
19
19
  import { FrontendApplicationState, StopReason } from '../common/frontend-application-state';
20
20
  import { ThemeMode } from '../common/theme';
21
+ import { LaunchArguments } from '../common/launch-arguments';
21
22
 
22
23
  export type MenuRole = ('undo' | 'redo' | 'cut' | 'copy' | 'paste' | 'selectAll' | 'about' | 'services' | 'hide' | 'hideOthers' | 'unhide' | 'quit');
23
24
 
@@ -44,6 +45,15 @@ export type WindowEvent = 'maximize' | 'unmaximize' | 'focus';
44
45
  export interface TheiaCoreAPI {
45
46
  WindowMetadata: {
46
47
  webcontentId: string;
48
+ /**
49
+ * The parsed CLI options of a *forwarded* launch (see the `second-instance` handling in
50
+ * `ElectronMainApplication`), or `undefined` for a cold-start window. Provided synchronously
51
+ * by the preload script, so a frontend contribution can act on it from the first paint.
52
+ * The options never ride on the window URL; see `LaunchArgsStore` for the rationale.
53
+ *
54
+ * @experimental
55
+ */
56
+ launchArgs?: LaunchArguments;
47
57
  }
48
58
  getSecurityToken: () => string;
49
59
  attachSecurityToken: (endpoint: string) => Promise<void>;
@@ -76,7 +76,13 @@ export class TheiaMainApi implements ElectronMainApplicationContribution {
76
76
 
77
77
  onStart(application: ElectronMainApplication): MaybePromise<void> {
78
78
  ipcMain.on(CHANNEL_WC_METADATA, event => {
79
- event.returnValue = event.sender.id.toString();
79
+ // Answered synchronously, so the window has its metadata — including the parsed options
80
+ // of a forwarded launch — before any frontend code runs. The window is identified by the
81
+ // IPC sender, so it can only ever read its own.
82
+ event.returnValue = {
83
+ webcontentId: event.sender.id.toString(),
84
+ launchArgs: application.getLaunchArgs(event.sender.id)
85
+ };
80
86
  });
81
87
 
82
88
  // electron security token
@@ -23,6 +23,7 @@ import { RpcConnectionHandler } from '../common/messaging/proxy-factory';
23
23
  import { ElectronSecurityToken } from '../electron-common/electron-token';
24
24
  import { ElectronMainWindowService, electronMainWindowServicePath } from '../electron-common/electron-main-window-service';
25
25
  import { ElectronMainApplication, ElectronMainApplicationContribution, ElectronMainProcessArgv } from './electron-main-application';
26
+ import { LaunchArgsStore } from './launch-args-store';
26
27
  import { ElectronMainWindowServiceImpl } from './electron-main-window-service-impl';
27
28
  import { TheiaBrowserWindowOptions, TheiaElectronWindow, TheiaElectronWindowFactory, WindowApplicationConfig } from './theia-electron-window';
28
29
  import { TheiaMainApi } from './electron-api-main';
@@ -37,6 +38,7 @@ const electronSecurityToken: ElectronSecurityToken = { value: generateUuid() };
37
38
 
38
39
  export default new ContainerModule(bind => {
39
40
  bind(Stopwatch).toConstantValue(new SimpleStopwatch('electron main', () => performance.now()));
41
+ bind(LaunchArgsStore).toSelf().inSingletonScope();
40
42
  bind(ElectronMainApplication).toSelf().inSingletonScope();
41
43
  bind(ElectronMessagingContribution).toSelf().inSingletonScope();
42
44
  bind(ElectronMainApplicationContribution).toService(ElectronMessagingContribution);
@@ -37,6 +37,9 @@ import { ElectronSecurityToken } from '../electron-common/electron-token';
37
37
  import Storage = require('electron-store');
38
38
  import { CancellationTokenSource, Disposable, DisposableCollection, Path, isOSX, isWindows } from '../common';
39
39
  import { DEFAULT_WINDOW_HASH, WindowSearchParams } from '../common/window';
40
+ import { LaunchArguments } from '../common/launch-arguments';
41
+ import { LaunchArgvParser } from './launch-argv-parser';
42
+ import { LaunchArgsStore } from './launch-args-store';
40
43
  import { TheiaBrowserWindowOptions, TheiaElectronWindow, TheiaElectronWindowFactory } from './theia-electron-window';
41
44
  import { ElectronMainApplicationGlobals } from './electron-main-constants';
42
45
  import { createDisposableListener } from './event-utils';
@@ -69,6 +72,25 @@ export interface ElectronMainCommandOptions {
69
72
  * If the app is already running but user relaunches it, `secondInstance` is true.
70
73
  */
71
74
  readonly secondInstance: boolean;
75
+
76
+ /**
77
+ * The parsed CLI options of a forwarded launch. Set for `second-instance` launches so that
78
+ * per-window options (e.g. `--attach-container`, `--session-preference`) are carried to the
79
+ * newly created window rather than being dropped.
80
+ *
81
+ * @experimental
82
+ */
83
+ readonly launchArgs?: LaunchArguments;
84
+
85
+ /**
86
+ * Whether an {@link ElectronMainApplicationContribution} has *claimed* this launch via its
87
+ * `claimsWindow` hook (e.g. a `--attach-container` launch claimed by `@theia/dev-container`).
88
+ * When set, an empty window is opened instead of restoring the last workspace, since the window
89
+ * is about to be replaced by whatever the claiming contribution attaches to.
90
+ *
91
+ * @experimental
92
+ */
93
+ readonly claimed?: boolean;
72
94
  }
73
95
 
74
96
  /**
@@ -103,6 +125,17 @@ export interface ElectronMainApplicationContribution {
103
125
  * The application is stopping. Contributions must perform only synchronous operations.
104
126
  */
105
127
  onStop?(application: ElectronMainApplication): void;
128
+ /**
129
+ * Whether this contribution *claims* responsibility for a window opened for the given launch,
130
+ * e.g. a `--attach-container` launch claimed by `@theia/dev-container`. A claimed launch opens
131
+ * an empty window rather than restoring the last workspace, since the window is about to be
132
+ * replaced by whatever the claiming contribution attaches to. Core itself stays agnostic of the
133
+ * concrete CLI options. The frontend receives the same options (see `LaunchArgsStore`), so the
134
+ * corresponding frontend contribution can show a placeholder from the first paint.
135
+ *
136
+ * @experimental
137
+ */
138
+ claimsWindow?(args: LaunchArguments): MaybePromise<boolean>;
106
139
  }
107
140
 
108
141
  // Extracted and modified the functionality from `yargs@15.4.0-beta.0`.
@@ -177,6 +210,9 @@ export class ElectronMainApplication {
177
210
  @inject(Stopwatch)
178
211
  protected readonly stopwatch: Stopwatch;
179
212
 
213
+ @inject(LaunchArgsStore)
214
+ protected readonly launchArgsStore: LaunchArgsStore;
215
+
180
216
  protected isPortable = this.makePortable();
181
217
 
182
218
  protected readonly electronStore = new Storage<{
@@ -252,10 +288,12 @@ export class ElectronMainApplication {
252
288
  () => this.startContributions());
253
289
  startupMeasurement.info('Startup sequence completed');
254
290
 
291
+ const launchArgs = LaunchArgvParser.parse(argv);
255
292
  this.handleMainCommand({
256
293
  file: args.file,
257
294
  cwd: process.cwd(),
258
- secondInstance: false
295
+ secondInstance: false,
296
+ claimed: await this.isWindowClaimed(launchArgs)
259
297
  });
260
298
  },
261
299
  ).parse();
@@ -445,6 +483,7 @@ export class ElectronMainApplication {
445
483
  this.activeWindowStack.splice(stackIndex, 1);
446
484
  }
447
485
  this.windows.delete(id);
486
+ this.launchArgsStore.delete(id);
448
487
  });
449
488
  electronWindow.window.on('maximize', () => TheiaRendererAPI.sendWindowEvent(electronWindow.window.webContents, 'maximize'));
450
489
  electronWindow.window.on('unmaximize', () => TheiaRendererAPI.sendWindowEvent(electronWindow.window.webContents, 'unmaximize'));
@@ -524,20 +563,35 @@ export class ElectronMainApplication {
524
563
  }
525
564
  }
526
565
 
527
- async openDefaultWindow(params?: WindowSearchParams): Promise<BrowserWindow> {
566
+ async openDefaultWindow(params?: WindowSearchParams, launchArgs?: LaunchArguments): Promise<BrowserWindow> {
528
567
  const options = this.getDefaultTheiaWindowOptions();
529
568
  const [uri, electronWindow] = await Promise.all([this.createWindowUri(params), this.reuseOrCreateWindow(options)]);
569
+ this.stashLaunchArgs(electronWindow, launchArgs);
530
570
  electronWindow.loadURL(uri.withFragment(DEFAULT_WINDOW_HASH).toString(true));
531
571
  return electronWindow;
532
572
  }
533
573
 
534
- protected async openWindowWithWorkspace(workspacePath: string): Promise<BrowserWindow> {
574
+ protected async openWindowWithWorkspace(workspacePath: string, launchArgs?: LaunchArguments): Promise<BrowserWindow> {
535
575
  const options = await this.getLastWindowOptions();
536
576
  const [uri, electronWindow] = await Promise.all([this.createWindowUri(), this.reuseOrCreateWindow(options)]);
577
+ this.stashLaunchArgs(electronWindow, launchArgs);
537
578
  electronWindow.loadURL(uri.withFragment(encodeURI(workspacePath)).toString(true));
538
579
  return electronWindow;
539
580
  }
540
581
 
582
+ /**
583
+ * Associates the parsed options of a forwarded launch with the target window before it loads its
584
+ * URL, so that the preload script cannot ask for them before they are stored. No-op for a
585
+ * cold-start launch, which carries none.
586
+ *
587
+ * @experimental
588
+ */
589
+ protected stashLaunchArgs(window: BrowserWindow, launchArgs?: LaunchArguments): void {
590
+ if (launchArgs) {
591
+ this.launchArgsStore.store(window.webContents.id, launchArgs);
592
+ }
593
+ }
594
+
541
595
  protected async reuseOrCreateWindow(asyncOptions: MaybePromise<TheiaBrowserWindowOptions>): Promise<BrowserWindow> {
542
596
  if (!this.initialWindow) {
543
597
  return this.createWindow(asyncOptions);
@@ -556,6 +610,14 @@ export class ElectronMainApplication {
556
610
  }
557
611
 
558
612
  protected async handleMainCommand(options: ElectronMainCommandOptions): Promise<void> {
613
+ // A claimed launch (e.g. a CLI attach) opens an empty window rather than restoring the last
614
+ // workspace: the local workbench would only be discarded when the window reloads into the
615
+ // claimed target, so loading a real workspace behind the placeholder wastes work and can
616
+ // trigger side effects.
617
+ if (options.claimed) {
618
+ await this.openDefaultWindow(undefined, options.launchArgs);
619
+ return;
620
+ }
559
621
  let workspacePath: string | undefined;
560
622
  if (options.file) {
561
623
  try {
@@ -565,14 +627,41 @@ export class ElectronMainApplication {
565
627
  }
566
628
  }
567
629
  if (workspacePath !== undefined) {
568
- await this.openWindowWithWorkspace(workspacePath);
630
+ await this.openWindowWithWorkspace(workspacePath, options.launchArgs);
569
631
  } else {
570
632
  if (options.secondInstance === false) {
571
- await this.openWindowWithWorkspace(''); // restore previous workspace.
633
+ await this.openWindowWithWorkspace('', options.launchArgs); // restore previous workspace.
572
634
  } else if (options.file === undefined) {
573
- await this.openDefaultWindow();
635
+ await this.openDefaultWindow(undefined, options.launchArgs);
636
+ }
637
+ }
638
+ }
639
+
640
+ /**
641
+ * Returns the parsed launch options stored for the window with the given `webContents` id, or
642
+ * `undefined` for a cold-start window. Read from the per-window metadata channel, where the
643
+ * caller is identified by the IPC sender, so a window can only ever see its own options.
644
+ *
645
+ * @experimental
646
+ */
647
+ getLaunchArgs(windowId: number): LaunchArguments | undefined {
648
+ return this.launchArgsStore.get(windowId);
649
+ }
650
+
651
+ /**
652
+ * Whether any {@link ElectronMainApplicationContribution} claims responsibility for a window
653
+ * opened for the given launch (see its `claimsWindow` hook). Core stays agnostic of the concrete
654
+ * CLI options; e.g. `@theia/dev-container` claims `--attach-container` launches.
655
+ *
656
+ * @experimental
657
+ */
658
+ protected async isWindowClaimed(args: LaunchArguments): Promise<boolean> {
659
+ for (const contribution of this.contributions.getContributions()) {
660
+ if (await contribution.claimsWindow?.(args)) {
661
+ return true;
574
662
  }
575
663
  }
664
+ return false;
576
665
  }
577
666
 
578
667
  async openUrl(url: string): Promise<void> {
@@ -833,16 +922,20 @@ export class ElectronMainApplication {
833
922
  if (originalArgv.includes('--open-url')) {
834
923
  this.openUrl(originalArgv[originalArgv.length - 1]);
835
924
  } else {
836
- createYargs(this.processArgv.getProcessArgvWithoutBin(originalArgv), cwd)
925
+ const argv = this.processArgv.getProcessArgvWithoutBin(originalArgv);
926
+ createYargs(argv, cwd)
837
927
  .help(false)
838
928
  .command('$0 [file]', false,
839
929
  cmd => cmd
840
930
  .positional('file', { type: 'string' }),
841
931
  async args => {
932
+ const launchArgs = LaunchArgvParser.parse(argv);
842
933
  await this.handleMainCommand({
843
934
  file: args.file,
844
935
  cwd: cwd,
845
- secondInstance: true
936
+ secondInstance: true,
937
+ launchArgs,
938
+ claimed: await this.isWindowClaimed(launchArgs)
846
939
  });
847
940
  },
848
941
  ).parse();
@@ -0,0 +1,59 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 EclipseSource GmbH 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 { expect } from 'chai';
18
+ import { LaunchArguments } from '../common/launch-arguments';
19
+ import { LaunchArgsStore } from './launch-args-store';
20
+
21
+ const args = (name: string, value: string): LaunchArguments => ({ values: { [name]: [value] }, negated: [] });
22
+
23
+ describe('LaunchArgsStore', () => {
24
+
25
+ it('returns the stored argv for its window id', () => {
26
+ const store = new LaunchArgsStore();
27
+ const launchArgs: LaunchArguments = { values: { 'attach-container': ['B'], 'session-preference': ['foo=1'] }, negated: [] };
28
+ store.store(1, launchArgs);
29
+ expect(store.get(1)).to.deep.equal(launchArgs);
30
+ });
31
+
32
+ it('keeps the argv for the window lifetime, so a reload still sees it', () => {
33
+ const store = new LaunchArgsStore();
34
+ store.store(1, args('attach-container', 'B'));
35
+ expect(store.get(1)).to.deep.equal(args('attach-container', 'B'));
36
+ // a second read (e.g. after a reload) still returns the options
37
+ expect(store.get(1)).to.deep.equal(args('attach-container', 'B'));
38
+ });
39
+
40
+ it('returns undefined for an unknown (cold-start) window id', () => {
41
+ const store = new LaunchArgsStore();
42
+ expect(store.get(42)).to.equal(undefined);
43
+ });
44
+
45
+ it('drops the argv once the window is deleted', () => {
46
+ const store = new LaunchArgsStore();
47
+ store.store(1, args('attach-container', 'A'));
48
+ store.delete(1);
49
+ expect(store.get(1)).to.equal(undefined);
50
+ });
51
+
52
+ it('keeps entries independent per window id', () => {
53
+ const store = new LaunchArgsStore();
54
+ store.store(1, args('attach-container', 'A'));
55
+ store.store(2, args('attach-container', 'B'));
56
+ expect(store.get(2)).to.deep.equal(args('attach-container', 'B'));
57
+ expect(store.get(1)).to.deep.equal(args('attach-container', 'A'));
58
+ });
59
+ });
@@ -0,0 +1,73 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 EclipseSource GmbH 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 } from 'inversify';
18
+ import { LaunchArguments } from '../common/launch-arguments';
19
+
20
+ /**
21
+ * Holds the parsed CLI options of *forwarded* launches (see the `second-instance` handling in
22
+ * `ElectronMainApplication`) in the trusted Electron main process, keyed by the `webContents` id of
23
+ * the window that was opened for them.
24
+ *
25
+ * ### Why the arguments never travel on the window URL
26
+ *
27
+ * The URL is the one channel in the system that carries no trust: in a browser deployment it is
28
+ * attacker-writable, so putting per-window options such as `--session-preference` there would let a
29
+ * crafted link inject settings (some of which persist to the User scope). The arguments therefore
30
+ * stay here in the main process and are handed to the window over the per-window metadata channel
31
+ * (`CHANNEL_WC_METADATA`), where main identifies the calling window by `event.sender.id` rather than
32
+ * by any value from the URL. This is the single place that documents that rationale; other files
33
+ * point here.
34
+ *
35
+ * The entry is not consumed on first read, so that a plain `Reload Window`, which re-runs the
36
+ * renderer against the same window, still observes the arguments. It is removed when the window is
37
+ * closed, and when the window is redirected to a *different* frontend (e.g. into a dev container
38
+ * after a CLI attach), since the arguments describe the launch rather than whatever the window is
39
+ * navigated to afterwards. See `TheiaElectronWindow.reload`.
40
+ *
41
+ * @experimental
42
+ */
43
+ @injectable()
44
+ export class LaunchArgsStore {
45
+
46
+ protected readonly argsByWindowId = new Map<number, LaunchArguments>();
47
+
48
+ /**
49
+ * Associates the parsed options of a forwarded launch with the window identified by `windowId` (its
50
+ * `webContents` id). Must be called before the window loads its URL, so that the preload script
51
+ * cannot ask for the arguments before they are stored.
52
+ */
53
+ store(windowId: number, args: LaunchArguments): void {
54
+ this.argsByWindowId.set(windowId, args);
55
+ }
56
+
57
+ /**
58
+ * Returns the parsed launch options for the window identified by `windowId`, or `undefined`
59
+ * for a window that was not opened by a forwarded launch (a cold-start window), or one whose
60
+ * arguments were dropped. Repeatable, so a plain reload still sees the arguments.
61
+ */
62
+ get(windowId: number): LaunchArguments | undefined {
63
+ return this.argsByWindowId.get(windowId);
64
+ }
65
+
66
+ /**
67
+ * Drops the arguments stored for `windowId`. Called when the window is closed, and when it is
68
+ * redirected to a different frontend.
69
+ */
70
+ delete(windowId: number): void {
71
+ this.argsByWindowId.delete(windowId);
72
+ }
73
+ }