@theia/core 1.71.0-next.8 → 1.71.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 (257) hide show
  1. package/README.md +19 -19
  2. package/i18n/nls.cs.json +97 -51
  3. package/i18n/nls.de.json +97 -51
  4. package/i18n/nls.es.json +97 -51
  5. package/i18n/nls.fr.json +97 -51
  6. package/i18n/nls.hu.json +97 -51
  7. package/i18n/nls.it.json +97 -51
  8. package/i18n/nls.ja.json +97 -51
  9. package/i18n/nls.json +101 -55
  10. package/i18n/nls.ko.json +97 -51
  11. package/i18n/nls.pl.json +97 -51
  12. package/i18n/nls.pt-br.json +97 -51
  13. package/i18n/nls.ru.json +97 -51
  14. package/i18n/nls.tr.json +97 -51
  15. package/i18n/nls.zh-cn.json +97 -51
  16. package/i18n/nls.zh-tw.json +97 -51
  17. package/lib/browser/catalog.json +169 -10
  18. package/lib/browser/common-frontend-contribution.js +2 -2
  19. package/lib/browser/common-frontend-contribution.js.map +1 -1
  20. package/lib/browser/components/card.d.ts.map +1 -1
  21. package/lib/browser/components/card.js +11 -3
  22. package/lib/browser/components/card.js.map +1 -1
  23. package/lib/browser/connection-status-service.js +1 -1
  24. package/lib/browser/connection-status-service.js.map +1 -1
  25. package/lib/browser/frontend-application-contribution.d.ts +2 -2
  26. package/lib/browser/frontend-application-contribution.d.ts.map +1 -1
  27. package/lib/browser/frontend-application.d.ts +2 -0
  28. package/lib/browser/frontend-application.d.ts.map +1 -1
  29. package/lib/browser/frontend-application.js +15 -6
  30. package/lib/browser/frontend-application.js.map +1 -1
  31. package/lib/browser/keyboard/index.d.ts +1 -0
  32. package/lib/browser/keyboard/index.d.ts.map +1 -1
  33. package/lib/browser/keyboard/index.js +1 -0
  34. package/lib/browser/keyboard/index.js.map +1 -1
  35. package/lib/browser/keyboard/keyboard-utils.d.ts +17 -0
  36. package/lib/browser/keyboard/keyboard-utils.d.ts.map +1 -0
  37. package/lib/browser/keyboard/keyboard-utils.js +40 -0
  38. package/lib/browser/keyboard/keyboard-utils.js.map +1 -0
  39. package/lib/browser/menu/browser-menu-plugin.d.ts.map +1 -1
  40. package/lib/browser/menu/browser-menu-plugin.js +8 -1
  41. package/lib/browser/menu/browser-menu-plugin.js.map +1 -1
  42. package/lib/browser/messaging/connection-source.d.ts +2 -1
  43. package/lib/browser/messaging/connection-source.d.ts.map +1 -1
  44. package/lib/browser/messaging/connection-source.js.map +1 -1
  45. package/lib/browser/messaging/messaging-frontend-module.d.ts.map +1 -1
  46. package/lib/browser/messaging/messaging-frontend-module.js +3 -0
  47. package/lib/browser/messaging/messaging-frontend-module.js.map +1 -1
  48. package/lib/browser/messaging/service-connection-provider.d.ts +2 -2
  49. package/lib/browser/messaging/service-connection-provider.d.ts.map +1 -1
  50. package/lib/browser/messaging/service-connection-provider.js +2 -2
  51. package/lib/browser/messaging/service-connection-provider.js.map +1 -1
  52. package/lib/browser/messaging/ws-connection-provider.d.ts +1 -1
  53. package/lib/browser/messaging/ws-connection-provider.d.ts.map +1 -1
  54. package/lib/browser/messaging/ws-connection-provider.js +3 -3
  55. package/lib/browser/messaging/ws-connection-provider.js.map +1 -1
  56. package/lib/browser/messaging/ws-connection-source.d.ts +4 -2
  57. package/lib/browser/messaging/ws-connection-source.d.ts.map +1 -1
  58. package/lib/browser/messaging/ws-connection-source.js +15 -10
  59. package/lib/browser/messaging/ws-connection-source.js.map +1 -1
  60. package/lib/browser/performance/frontend-stopwatch.js +1 -1
  61. package/lib/browser/performance/frontend-stopwatch.js.map +1 -1
  62. package/lib/browser/preload/i18n-preload-contribution.d.ts +1 -1
  63. package/lib/browser/preload/i18n-preload-contribution.d.ts.map +1 -1
  64. package/lib/browser/preload/i18n-preload-contribution.js +2 -2
  65. package/lib/browser/preload/i18n-preload-contribution.js.map +1 -1
  66. package/lib/browser/preload/os-preload-contribution.d.ts +1 -1
  67. package/lib/browser/preload/os-preload-contribution.d.ts.map +1 -1
  68. package/lib/browser/preload/os-preload-contribution.js +6 -6
  69. package/lib/browser/preload/os-preload-contribution.js.map +1 -1
  70. package/lib/browser/saveable-service.d.ts +9 -2
  71. package/lib/browser/saveable-service.d.ts.map +1 -1
  72. package/lib/browser/saveable-service.js +34 -25
  73. package/lib/browser/saveable-service.js.map +1 -1
  74. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.d.ts +1 -0
  75. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.d.ts.map +1 -1
  76. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.js +10 -1
  77. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.js.map +1 -1
  78. package/lib/browser/shell/tab-bar-toolbar/tab-toolbar-item.d.ts +2 -0
  79. package/lib/browser/shell/tab-bar-toolbar/tab-toolbar-item.d.ts.map +1 -1
  80. package/lib/browser/shell/tab-bar-toolbar/tab-toolbar-item.js +11 -2
  81. package/lib/browser/shell/tab-bar-toolbar/tab-toolbar-item.js.map +1 -1
  82. package/lib/browser/window/browser-window-module.d.ts.map +1 -1
  83. package/lib/browser/window/browser-window-module.js +2 -0
  84. package/lib/browser/window/browser-window-module.js.map +1 -1
  85. package/lib/browser/window/default-secondary-window-service.d.ts +2 -0
  86. package/lib/browser/window/default-secondary-window-service.d.ts.map +1 -1
  87. package/lib/browser/window/default-secondary-window-service.js +7 -0
  88. package/lib/browser/window/default-secondary-window-service.js.map +1 -1
  89. package/lib/browser/window/window-focus-service.d.ts +71 -0
  90. package/lib/browser/window/window-focus-service.d.ts.map +1 -0
  91. package/lib/browser/window/window-focus-service.js +173 -0
  92. package/lib/browser/window/window-focus-service.js.map +1 -0
  93. package/lib/common/event.d.ts +16 -0
  94. package/lib/common/event.d.ts.map +1 -1
  95. package/lib/common/event.js +20 -2
  96. package/lib/common/event.js.map +1 -1
  97. package/lib/common/event.spec.js +63 -0
  98. package/lib/common/event.spec.js.map +1 -1
  99. package/lib/common/glob.d.ts +2 -0
  100. package/lib/common/glob.d.ts.map +1 -1
  101. package/lib/common/glob.js +8 -7
  102. package/lib/common/glob.js.map +1 -1
  103. package/lib/common/message-rpc/channel.d.ts +1 -1
  104. package/lib/common/message-rpc/channel.d.ts.map +1 -1
  105. package/lib/common/message-rpc/channel.js +20 -12
  106. package/lib/common/message-rpc/channel.js.map +1 -1
  107. package/lib/common/message-rpc/channel.spec.d.ts.map +1 -1
  108. package/lib/common/message-rpc/channel.spec.js +94 -0
  109. package/lib/common/message-rpc/channel.spec.js.map +1 -1
  110. package/lib/common/message-rpc/rpc-protocol.d.ts.map +1 -1
  111. package/lib/common/message-rpc/rpc-protocol.js +13 -3
  112. package/lib/common/message-rpc/rpc-protocol.js.map +1 -1
  113. package/lib/common/message-rpc/uint8-array-message-buffer.d.ts.map +1 -1
  114. package/lib/common/message-rpc/uint8-array-message-buffer.js +1 -1
  115. package/lib/common/message-rpc/uint8-array-message-buffer.js.map +1 -1
  116. package/lib/common/messaging/index.d.ts +1 -0
  117. package/lib/common/messaging/index.d.ts.map +1 -1
  118. package/lib/common/messaging/index.js +1 -0
  119. package/lib/common/messaging/index.js.map +1 -1
  120. package/lib/common/messaging/socket-write-buffer.d.ts +4 -3
  121. package/lib/common/messaging/socket-write-buffer.d.ts.map +1 -1
  122. package/lib/common/messaging/socket-write-buffer.js +14 -4
  123. package/lib/common/messaging/socket-write-buffer.js.map +1 -1
  124. package/lib/common/performance/index.d.ts +1 -0
  125. package/lib/common/performance/index.d.ts.map +1 -1
  126. package/lib/common/performance/index.js +1 -0
  127. package/lib/common/performance/index.js.map +1 -1
  128. package/lib/common/performance/simple-stopwatch.d.ts +18 -0
  129. package/lib/common/performance/simple-stopwatch.d.ts.map +1 -0
  130. package/lib/common/performance/simple-stopwatch.js +80 -0
  131. package/lib/common/performance/simple-stopwatch.js.map +1 -0
  132. package/lib/common/performance/stopwatch.d.ts +41 -0
  133. package/lib/common/performance/stopwatch.d.ts.map +1 -1
  134. package/lib/common/performance/stopwatch.js +89 -3
  135. package/lib/common/performance/stopwatch.js.map +1 -1
  136. package/lib/common/performance/stopwatch.spec.d.ts +2 -0
  137. package/lib/common/performance/stopwatch.spec.d.ts.map +1 -0
  138. package/lib/common/performance/stopwatch.spec.js +256 -0
  139. package/lib/common/performance/stopwatch.spec.js.map +1 -0
  140. package/lib/common/preferences/index.d.ts +1 -0
  141. package/lib/common/preferences/index.d.ts.map +1 -1
  142. package/lib/common/preferences/index.js +1 -0
  143. package/lib/common/preferences/index.js.map +1 -1
  144. package/lib/common/preferences/preference-utils.d.ts +6 -0
  145. package/lib/common/preferences/preference-utils.d.ts.map +1 -0
  146. package/lib/common/preferences/preference-utils.js +29 -0
  147. package/lib/common/preferences/preference-utils.js.map +1 -0
  148. package/lib/common/resource.d.ts +2 -0
  149. package/lib/common/resource.d.ts.map +1 -1
  150. package/lib/common/resource.js +7 -3
  151. package/lib/common/resource.js.map +1 -1
  152. package/lib/electron-browser/menu/electron-main-menu-factory.d.ts.map +1 -1
  153. package/lib/electron-browser/menu/electron-main-menu-factory.js +5 -1
  154. package/lib/electron-browser/menu/electron-main-menu-factory.js.map +1 -1
  155. package/lib/electron-browser/messaging/electron-messaging-frontend-module.d.ts.map +1 -1
  156. package/lib/electron-browser/messaging/electron-messaging-frontend-module.js +3 -0
  157. package/lib/electron-browser/messaging/electron-messaging-frontend-module.js.map +1 -1
  158. package/lib/electron-browser/window/electron-window-module.d.ts.map +1 -1
  159. package/lib/electron-browser/window/electron-window-module.js +2 -0
  160. package/lib/electron-browser/window/electron-window-module.js.map +1 -1
  161. package/lib/electron-main/electron-api-main.d.ts.map +1 -1
  162. package/lib/electron-main/electron-api-main.js +4 -2
  163. package/lib/electron-main/electron-api-main.js.map +1 -1
  164. package/lib/electron-main/electron-main-application-module.d.ts.map +1 -1
  165. package/lib/electron-main/electron-main-application-module.js +3 -0
  166. package/lib/electron-main/electron-main-application-module.js.map +1 -1
  167. package/lib/electron-main/electron-main-application.d.ts +2 -0
  168. package/lib/electron-main/electron-main-application.d.ts.map +1 -1
  169. package/lib/electron-main/electron-main-application.js +14 -5
  170. package/lib/electron-main/electron-main-application.js.map +1 -1
  171. package/lib/electron-main/theia-electron-window.d.ts.map +1 -1
  172. package/lib/electron-main/theia-electron-window.js +3 -0
  173. package/lib/electron-main/theia-electron-window.js.map +1 -1
  174. package/lib/node/backend-application.d.ts +2 -0
  175. package/lib/node/backend-application.d.ts.map +1 -1
  176. package/lib/node/backend-application.js +17 -5
  177. package/lib/node/backend-application.js.map +1 -1
  178. package/lib/node/messaging/default-messaging-service.d.ts.map +1 -1
  179. package/lib/node/messaging/default-messaging-service.js +1 -0
  180. package/lib/node/messaging/default-messaging-service.js.map +1 -1
  181. package/lib/node/messaging/index.d.ts +1 -0
  182. package/lib/node/messaging/index.d.ts.map +1 -1
  183. package/lib/node/messaging/index.js +1 -0
  184. package/lib/node/messaging/index.js.map +1 -1
  185. package/lib/node/messaging/messaging-backend-module.d.ts.map +1 -1
  186. package/lib/node/messaging/messaging-backend-module.js +4 -0
  187. package/lib/node/messaging/messaging-backend-module.js.map +1 -1
  188. package/lib/node/messaging/test/default-messaging-service.spec.d.ts +2 -0
  189. package/lib/node/messaging/test/default-messaging-service.spec.d.ts.map +1 -0
  190. package/lib/node/messaging/test/default-messaging-service.spec.js +81 -0
  191. package/lib/node/messaging/test/default-messaging-service.spec.js.map +1 -0
  192. package/lib/node/messaging/websocket-frontend-connection-service.d.ts +9 -5
  193. package/lib/node/messaging/websocket-frontend-connection-service.d.ts.map +1 -1
  194. package/lib/node/messaging/websocket-frontend-connection-service.js +21 -5
  195. package/lib/node/messaging/websocket-frontend-connection-service.js.map +1 -1
  196. package/lib/node/performance/node-stopwatch.js +1 -1
  197. package/lib/node/performance/node-stopwatch.js.map +1 -1
  198. package/lib/node/process-utils.d.ts.map +1 -1
  199. package/lib/node/process-utils.js +9 -1
  200. package/lib/node/process-utils.js.map +1 -1
  201. package/package.json +34 -34
  202. package/src/browser/common-frontend-contribution.ts +2 -2
  203. package/src/browser/components/card.tsx +13 -2
  204. package/src/browser/connection-status-service.ts +1 -1
  205. package/src/browser/frontend-application-contribution.ts +2 -2
  206. package/src/browser/frontend-application.ts +26 -17
  207. package/src/browser/keyboard/index.ts +1 -0
  208. package/src/browser/keyboard/keyboard-utils.ts +37 -0
  209. package/src/browser/menu/browser-menu-plugin.ts +8 -1
  210. package/src/browser/messaging/connection-source.ts +2 -1
  211. package/src/browser/messaging/messaging-frontend-module.ts +3 -0
  212. package/src/browser/messaging/service-connection-provider.ts +2 -2
  213. package/src/browser/messaging/ws-connection-provider.ts +1 -1
  214. package/src/browser/messaging/ws-connection-source.ts +7 -4
  215. package/src/browser/performance/frontend-stopwatch.ts +1 -1
  216. package/src/browser/preload/i18n-preload-contribution.ts +1 -1
  217. package/src/browser/preload/os-preload-contribution.ts +1 -1
  218. package/src/browser/saveable-service.ts +34 -27
  219. package/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar.tsx +14 -1
  220. package/src/browser/shell/tab-bar-toolbar/tab-toolbar-item.tsx +13 -2
  221. package/src/browser/style/card.css +4 -2
  222. package/src/browser/style/hover-service.css +7 -0
  223. package/src/browser/window/browser-window-module.ts +2 -0
  224. package/src/browser/window/default-secondary-window-service.ts +6 -0
  225. package/src/browser/window/window-focus-service.ts +187 -0
  226. package/src/common/event.spec.ts +80 -0
  227. package/src/common/event.ts +31 -2
  228. package/src/common/glob.ts +2 -2
  229. package/src/common/i18n/nls.metadata.json +4254 -1058
  230. package/src/common/message-rpc/channel.spec.ts +116 -0
  231. package/src/common/message-rpc/channel.ts +15 -11
  232. package/src/common/message-rpc/rpc-protocol.ts +12 -3
  233. package/src/common/message-rpc/uint8-array-message-buffer.ts +1 -1
  234. package/src/common/messaging/index.ts +1 -0
  235. package/src/common/messaging/socket-write-buffer.ts +10 -4
  236. package/src/common/performance/index.ts +1 -0
  237. package/src/common/performance/simple-stopwatch.ts +91 -0
  238. package/src/common/performance/stopwatch.spec.ts +321 -0
  239. package/src/common/performance/stopwatch.ts +103 -2
  240. package/src/common/preferences/index.ts +1 -0
  241. package/src/common/preferences/preference-utils.ts +28 -0
  242. package/src/common/resource.ts +8 -2
  243. package/src/electron-browser/menu/electron-main-menu-factory.ts +5 -1
  244. package/src/electron-browser/messaging/electron-messaging-frontend-module.ts +3 -0
  245. package/src/electron-browser/window/electron-window-module.ts +2 -0
  246. package/src/electron-main/electron-api-main.ts +4 -2
  247. package/src/electron-main/electron-main-application-module.ts +3 -0
  248. package/src/electron-main/electron-main-application.ts +21 -5
  249. package/src/electron-main/theia-electron-window.ts +3 -0
  250. package/src/node/backend-application.ts +27 -10
  251. package/src/node/messaging/default-messaging-service.ts +1 -0
  252. package/src/node/messaging/index.ts +1 -0
  253. package/src/node/messaging/messaging-backend-module.ts +5 -1
  254. package/src/node/messaging/test/default-messaging-service.spec.ts +85 -0
  255. package/src/node/messaging/websocket-frontend-connection-service.ts +20 -7
  256. package/src/node/performance/node-stopwatch.ts +1 -1
  257. package/src/node/process-utils.ts +9 -1
@@ -0,0 +1,321 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 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 { expect } from 'chai';
18
+ import * as sinon from 'sinon';
19
+ import { Deferred } from '../promise-util';
20
+ import { Measurement, MeasurementOptions } from './measurement';
21
+ import { MeasurementContext } from './stopwatch';
22
+ import { SimpleStopwatch } from './simple-stopwatch';
23
+
24
+ /**
25
+ * A fake {@link Measurement} whose log methods are sinon spies, with a
26
+ * configurable duration returned by {@link stop}.
27
+ */
28
+ class FakeMeasurement implements Measurement {
29
+ name: string;
30
+ elapsed?: number;
31
+ duration: number;
32
+
33
+ readonly log = sinon.spy();
34
+ readonly info = sinon.spy();
35
+ readonly debug = sinon.spy();
36
+ readonly warn = sinon.spy();
37
+ readonly error = sinon.spy();
38
+
39
+ readonly stop = sinon.spy((): number => {
40
+ if (this.elapsed === undefined) {
41
+ this.elapsed = this.duration;
42
+ }
43
+ return this.elapsed;
44
+ });
45
+
46
+ constructor(name: string, duration: number = 0) {
47
+ this.name = name;
48
+ this.duration = duration;
49
+ }
50
+ }
51
+
52
+ /**
53
+ * A fake {@link Stopwatch} that creates {@link FakeMeasurement}s with
54
+ * configurable durations and records all invocations of {@link start}.
55
+ */
56
+ class FakeStopwatch extends SimpleStopwatch {
57
+ defaultDuration = 0;
58
+ readonly durationByName = new Map<string, number>();
59
+ readonly created: FakeMeasurement[] = [];
60
+
61
+ override readonly start = sinon.spy((name: string, _options?: MeasurementOptions): Measurement => {
62
+ const duration = this.durationByName.get(name) ?? this.defaultDuration;
63
+ const measurement = new FakeMeasurement(name, duration);
64
+ this.created.push(measurement);
65
+ return measurement;
66
+ });
67
+
68
+ constructor() {
69
+ super('test', () => 0);
70
+ }
71
+
72
+ /** Return the first measurement created with the given name, or `undefined`. */
73
+ measurementFor(name: string): FakeMeasurement | undefined {
74
+ return this.created.find(m => m.name === name);
75
+ }
76
+ }
77
+
78
+ class TestContribution { }
79
+ class OtherTestContribution { }
80
+
81
+ /**
82
+ * Allow any already-queued microtasks (such as `.then` callbacks attached to
83
+ * already-settled promises) to run before assertions.
84
+ */
85
+ async function flushPromises(): Promise<void> {
86
+ for (let i = 0; i < 5; i++) {
87
+ await Promise.resolve();
88
+ }
89
+ }
90
+
91
+ describe('MeasurementContext', () => {
92
+
93
+ let stopwatch: FakeStopwatch;
94
+
95
+ beforeEach(() => {
96
+ stopwatch = new FakeStopwatch();
97
+ });
98
+
99
+ describe('ensureEntry', () => {
100
+
101
+ it('starts a per-contribution measurement', () => {
102
+ const context = new MeasurementContext<TestContribution>(stopwatch, 'Frontend', 250);
103
+
104
+ context.ensureEntry(new TestContribution());
105
+
106
+ sinon.assert.calledWith(stopwatch.start, 'TestContribution.settled', sinon.match({ thresholdMillis: 250 }));
107
+ });
108
+
109
+ it('starts a per-contribution measurement only once per item', () => {
110
+ const context = new MeasurementContext<TestContribution>(stopwatch, 'Frontend', 100);
111
+ const item = new TestContribution();
112
+
113
+ context.ensureEntry(item);
114
+ context.ensureEntry(item);
115
+ context.ensureEntry(item);
116
+
117
+ const started = stopwatch.start.getCalls().filter(c => c.args[0] === 'TestContribution.settled');
118
+ expect(started).to.have.length(1);
119
+ });
120
+
121
+ it('starts independent measurements for distinct items', () => {
122
+ const context = new MeasurementContext<TestContribution>(stopwatch, 'Frontend', 100);
123
+
124
+ context.ensureEntry(new TestContribution());
125
+ context.ensureEntry(new TestContribution());
126
+
127
+ const started = stopwatch.start.getCalls().filter(c => c.args[0] === 'TestContribution.settled');
128
+ expect(started).to.have.length(2);
129
+ });
130
+ });
131
+
132
+ describe('trackSettlement', () => {
133
+
134
+ it('is a no-op for a synchronous result', async () => {
135
+ const context = new MeasurementContext<TestContribution>(stopwatch, 'Frontend', 100);
136
+ const item = new TestContribution();
137
+ context.ensureEntry(item);
138
+
139
+ context.trackSettlement(item, undefined);
140
+ context.armAllSettled();
141
+ await flushPromises();
142
+
143
+ const perContribution = stopwatch.measurementFor('TestContribution.settled')!;
144
+ sinon.assert.notCalled(perContribution.debug);
145
+ sinon.assert.notCalled(perContribution.warn);
146
+ sinon.assert.notCalled(perContribution.info);
147
+
148
+ // Synchronous results do not increment allSettledPending, so arming fires the
149
+ // aggregate message immediately.
150
+ const allSettled = stopwatch.measurementFor('frontend-all-settled')!;
151
+ sinon.assert.calledOnce(allSettled.info);
152
+ });
153
+
154
+ it('does not log a per-contribution settlement when only one promise was tracked', async () => {
155
+ // The single lifecycle measurement already describes the duration of a solo tracked
156
+ // promise, so the per-contribution aggregate must stay silent.
157
+ const context = new MeasurementContext<TestContribution>(stopwatch, 'Frontend', 100);
158
+ const item = new TestContribution();
159
+ context.ensureEntry(item);
160
+
161
+ context.trackSettlement(item, Promise.resolve());
162
+ context.armAllSettled();
163
+ await flushPromises();
164
+
165
+ const perContribution = stopwatch.measurementFor('TestContribution.settled')!;
166
+ sinon.assert.notCalled(perContribution.debug);
167
+ sinon.assert.notCalled(perContribution.warn);
168
+ sinon.assert.notCalled(perContribution.info);
169
+ });
170
+
171
+ it('logs a debug settlement message once multiple tracked promises all resolve under the threshold', async () => {
172
+ stopwatch.durationByName.set('TestContribution.settled', 50);
173
+ const context = new MeasurementContext<TestContribution>(stopwatch, 'Frontend', 100);
174
+ const item = new TestContribution();
175
+ context.ensureEntry(item);
176
+
177
+ const first = new Deferred<void>();
178
+ const second = new Deferred<void>();
179
+ context.trackSettlement(item, first.promise);
180
+ context.trackSettlement(item, second.promise);
181
+
182
+ // Before any promise resolves, nothing has been logged.
183
+ const perContribution = stopwatch.measurementFor('TestContribution.settled')!;
184
+ sinon.assert.notCalled(perContribution.debug);
185
+
186
+ first.resolve();
187
+ await flushPromises();
188
+ sinon.assert.notCalled(perContribution.debug);
189
+
190
+ second.resolve();
191
+ await flushPromises();
192
+ sinon.assert.calledOnceWithExactly(perContribution.debug, 'Frontend TestContribution settled');
193
+ sinon.assert.notCalled(perContribution.warn);
194
+ });
195
+
196
+ it('logs a warn settlement message when multiple tracked promises exceed the threshold', async () => {
197
+ stopwatch.durationByName.set('TestContribution.settled', 500);
198
+ const context = new MeasurementContext<TestContribution>(stopwatch, 'Frontend', 100);
199
+ const item = new TestContribution();
200
+ context.ensureEntry(item);
201
+
202
+ context.trackSettlement(item, Promise.resolve());
203
+ context.trackSettlement(item, Promise.resolve());
204
+ await flushPromises();
205
+
206
+ const perContribution = stopwatch.measurementFor('TestContribution.settled')!;
207
+ sinon.assert.calledOnceWithExactly(perContribution.warn, 'Frontend TestContribution took longer than expected to settle');
208
+ sinon.assert.notCalled(perContribution.debug);
209
+ });
210
+
211
+ it('treats a rejected promise as settled', async () => {
212
+ stopwatch.durationByName.set('TestContribution.settled', 10);
213
+ const context = new MeasurementContext<TestContribution>(stopwatch, 'Frontend', 100);
214
+ const item = new TestContribution();
215
+ context.ensureEntry(item);
216
+
217
+ const rejecting = new Deferred<void>();
218
+ context.trackSettlement(item, Promise.resolve());
219
+ context.trackSettlement(item, rejecting.promise);
220
+ rejecting.reject(new Error('expected failure'));
221
+ await flushPromises();
222
+
223
+ const perContribution = stopwatch.measurementFor('TestContribution.settled')!;
224
+ sinon.assert.calledOnce(perContribution.debug);
225
+ });
226
+
227
+ it('tracks promises independently for each contribution', async () => {
228
+ stopwatch.durationByName.set('TestContribution.settled', 50);
229
+ stopwatch.durationByName.set('OtherTestContribution.settled', 50);
230
+ const context = new MeasurementContext<object>(stopwatch, 'Frontend', 100);
231
+
232
+ const a = new TestContribution();
233
+ const b = new OtherTestContribution();
234
+ context.ensureEntry(a);
235
+ context.ensureEntry(b);
236
+
237
+ context.trackSettlement(a, Promise.resolve());
238
+ context.trackSettlement(a, Promise.resolve());
239
+ context.trackSettlement(b, Promise.resolve());
240
+ await flushPromises();
241
+
242
+ // a had two tracked promises: logs once.
243
+ sinon.assert.calledOnce(stopwatch.measurementFor('TestContribution.settled')!.debug);
244
+ // b had a single tracked promise: logs nothing.
245
+ sinon.assert.notCalled(stopwatch.measurementFor('OtherTestContribution.settled')!.debug);
246
+ });
247
+ });
248
+
249
+ describe('armAllSettled', () => {
250
+
251
+ it('logs the aggregate message immediately when armed with zero pending promises', () => {
252
+ const context = new MeasurementContext(stopwatch, 'Frontend', 100);
253
+
254
+ context.armAllSettled();
255
+
256
+ const allSettled = stopwatch.measurementFor('frontend-all-settled')!;
257
+ sinon.assert.calledOnceWithExactly(allSettled.info, 'All frontend contributions settled');
258
+ });
259
+
260
+ it('defers the aggregate log until the last tracked promise settles', async () => {
261
+ const context = new MeasurementContext<TestContribution>(stopwatch, 'Frontend', 100);
262
+ const item = new TestContribution();
263
+ context.ensureEntry(item);
264
+
265
+ const pending = new Deferred<void>();
266
+ context.trackSettlement(item, pending.promise);
267
+ context.armAllSettled();
268
+
269
+ const allSettled = stopwatch.measurementFor('frontend-all-settled')!;
270
+ sinon.assert.notCalled(allSettled.info);
271
+
272
+ pending.resolve();
273
+ await flushPromises();
274
+
275
+ sinon.assert.calledOnce(allSettled.info);
276
+ });
277
+
278
+ it('does not log the aggregate message when all promises settle before arming', async () => {
279
+ const context = new MeasurementContext<TestContribution>(stopwatch, 'Frontend', 100);
280
+ const item = new TestContribution();
281
+ context.ensureEntry(item);
282
+
283
+ context.trackSettlement(item, Promise.resolve());
284
+ await flushPromises();
285
+
286
+ const allSettled = stopwatch.measurementFor('frontend-all-settled')!;
287
+ sinon.assert.notCalled(allSettled.info);
288
+ });
289
+
290
+ it('logs the aggregate message when arming after all tracked promises have already settled', async () => {
291
+ const context = new MeasurementContext<TestContribution>(stopwatch, 'Frontend', 100);
292
+ const item = new TestContribution();
293
+ context.ensureEntry(item);
294
+
295
+ context.trackSettlement(item, Promise.resolve());
296
+ await flushPromises();
297
+
298
+ const allSettled = stopwatch.measurementFor('frontend-all-settled')!;
299
+ sinon.assert.notCalled(allSettled.info);
300
+
301
+ context.armAllSettled();
302
+ sinon.assert.calledOnce(allSettled.info);
303
+ });
304
+
305
+ it('logs the aggregate message exactly once when multiple contributions finish', async () => {
306
+ const context = new MeasurementContext<object>(stopwatch, 'Frontend', 100);
307
+ const a = new TestContribution();
308
+ const b = new OtherTestContribution();
309
+ context.ensureEntry(a);
310
+ context.ensureEntry(b);
311
+
312
+ context.trackSettlement(a, Promise.resolve());
313
+ context.trackSettlement(b, Promise.resolve());
314
+ context.armAllSettled();
315
+ await flushPromises();
316
+
317
+ const allSettled = stopwatch.measurementFor('frontend-all-settled')!;
318
+ sinon.assert.calledOnce(allSettled.info);
319
+ });
320
+ });
321
+ });
@@ -170,8 +170,8 @@ export abstract class Stopwatch {
170
170
  }
171
171
  }
172
172
 
173
- const start = options.owner ? `${options.owner} start` : 'start';
174
- const timeFromStart = `Finished ${(options.now() / 1000).toFixed(3)} s after ${start}`;
173
+ const origin = options.owner ?? 'application';
174
+ const timeFromStart = `${(options.now() / 1000).toFixed(3)} s since ${origin} start`;
175
175
  const whatWasMeasured = options.context ? `[${options.context}] ${activity}` : activity;
176
176
  this.logger.log(level, `${whatWasMeasured}: ${elapsed.toFixed(1)} ms [${timeFromStart}]`, ...(options.arguments ?? []));
177
177
  }
@@ -181,3 +181,104 @@ export abstract class Stopwatch {
181
181
  }
182
182
 
183
183
  }
184
+
185
+ interface SettlementEntry {
186
+ name: string;
187
+ measurement: Measurement;
188
+ pending: number;
189
+ total: number;
190
+ }
191
+
192
+ /**
193
+ * Tracks the settlement of async work initiated by contributions during application startup.
194
+ *
195
+ * A contribution "settles" when all promises it returned from lifecycle methods (initialize, configure, onStart, etc.)
196
+ * have resolved. Individual settlement is only logged when a contribution returned promises from more than one lifecycle
197
+ * method; otherwise the single lifecycle measurement already describes the work. An aggregate "all settled" message is
198
+ * logged once all tracked promises across all contributions have resolved.
199
+ *
200
+ * Typical usage:
201
+ * 1. Create the context at the start of the application lifecycle.
202
+ * 2. Before each lifecycle call, call {@link ensureEntry} to start the per-contribution clock.
203
+ * 3. After each lifecycle call, call {@link trackSettlement} with the return value.
204
+ * 4. After the startup sequence completes, call {@link armAllSettled} to enable the aggregate message.
205
+ */
206
+ export class MeasurementContext<T extends object = object> {
207
+
208
+ private readonly entries = new Map<T, SettlementEntry>();
209
+ private readonly allSettledMeasurement: Measurement;
210
+ private allSettledPending = 0;
211
+ private allSettledArmed = false;
212
+
213
+ constructor(
214
+ protected readonly stopwatch: Stopwatch,
215
+ protected readonly owner: string,
216
+ protected readonly thresholdMillis: number
217
+ ) {
218
+ this.allSettledMeasurement = this.stopwatch.start(`${owner.toLowerCase()}-all-settled`);
219
+ }
220
+
221
+ /**
222
+ * Ensure that settlement tracking has been started for the given contribution.
223
+ * Starts the per-contribution measurement clock on the first call for each contribution.
224
+ */
225
+ ensureEntry(item: T): void {
226
+ if (!this.entries.has(item)) {
227
+ const name = item.constructor.name;
228
+ this.entries.set(item, {
229
+ name,
230
+ measurement: this.stopwatch.start(`${name}.settled`, { thresholdMillis: this.thresholdMillis }),
231
+ pending: 0,
232
+ total: 0
233
+ });
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Track a promise returned by a contribution's lifecycle method.
239
+ * Must be called after the corresponding {@link Stopwatch.startAsync} has completed so that
240
+ * the settlement log appears after the lifecycle measurement log.
241
+ */
242
+ trackSettlement(item: T, result: MaybePromise<unknown>): void {
243
+ if (result instanceof Promise) {
244
+ const entry = this.entries.get(item)!;
245
+ entry.pending++;
246
+ entry.total++;
247
+ this.allSettledPending++;
248
+ const onSettled = (): void => {
249
+ this.onPromiseSettled(item);
250
+ };
251
+ result.then(onSettled, onSettled);
252
+ }
253
+ }
254
+
255
+ /**
256
+ * Arm the aggregate "all settled" log message. Call this after the startup sequence has finished
257
+ * collecting all promises. If all promises have already settled, the message is logged immediately.
258
+ */
259
+ armAllSettled(): void {
260
+ this.allSettledArmed = true;
261
+ if (this.allSettledPending === 0) {
262
+ this.allSettledMeasurement.info(`All ${this.owner.toLowerCase()} contributions settled`);
263
+ }
264
+ }
265
+
266
+ private onPromiseSettled(item: T): void {
267
+ const entry = this.entries.get(item);
268
+ if (entry && --entry.pending === 0) {
269
+ const { name, measurement, total } = entry;
270
+ this.entries.delete(item);
271
+ if (total > 1) {
272
+ if (measurement.stop() > this.thresholdMillis) {
273
+ measurement.warn(`${this.owner} ${name} took longer than expected to settle`);
274
+ } else {
275
+ measurement.debug(`${this.owner} ${name} settled`);
276
+ }
277
+ }
278
+ }
279
+ if (--this.allSettledPending === 0 && this.allSettledArmed) {
280
+ this.allSettledMeasurement.info(`All ${this.owner.toLowerCase()} contributions settled`);
281
+ }
282
+ }
283
+
284
+ }
@@ -25,3 +25,4 @@ export * from './preference-service';
25
25
  export * from './injectable-preference-proxy';
26
26
  export * from './preference-proxy';
27
27
  export * from './preference-configurations';
28
+ export * from './preference-utils';
@@ -0,0 +1,28 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 EclipseSource 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 { nls } from '../nls';
18
+ import { isOSX, isWindows } from '../os';
19
+
20
+ /**
21
+ * Hint appended to API key preference descriptions on Linux, where environment variables
22
+ * set in `~/.bashrc` are not available to desktop-launched applications.
23
+ */
24
+ export const LINUX_ENV_HINT = !isWindows && !isOSX
25
+ ? ' ' + nls.localize('theia/ai-core/preferences/linuxEnvHint',
26
+ 'On Linux, make sure the variable is defined in `~/.profile` (not just `~/.bashrc`) if you launch the application from a desktop shortcut.' +
27
+ ' See the [documentation](https://theia-ide.org/docs/user_ai/#setting-api-keys) for details.')
28
+ : '';
@@ -311,13 +311,19 @@ export class InMemoryResources implements ResourceResolver {
311
311
  }
312
312
 
313
313
  export const MEMORY_TEXT = 'mem-txt';
314
+ export const MEMORY_TEXT_READONLY = 'mem-txt-readonly';
314
315
 
315
316
  /**
316
317
  * Resource implementation for 'mem-txt' URI scheme where content is saved in URI query.
317
318
  */
318
319
  export class InMemoryTextResource implements Resource {
320
+
319
321
  constructor(readonly uri: URI) { }
320
322
 
323
+ get readOnly(): boolean {
324
+ return this.uri.scheme === MEMORY_TEXT_READONLY;
325
+ }
326
+
321
327
  async readContents(options?: { encoding?: string | undefined; } | undefined): Promise<string> {
322
328
  return this.uri.query;
323
329
  }
@@ -330,8 +336,8 @@ export class InMemoryTextResource implements Resource {
330
336
  @injectable()
331
337
  export class InMemoryTextResourceResolver implements ResourceResolver {
332
338
  resolve(uri: URI): MaybePromise<Resource> {
333
- if (uri.scheme !== MEMORY_TEXT) {
334
- throw new Error(`Expected a URI with ${MEMORY_TEXT} scheme. Was: ${uri}.`);
339
+ if (uri.scheme !== MEMORY_TEXT && uri.scheme !== MEMORY_TEXT_READONLY) {
340
+ throw new Error(`Expected a URI with ${MEMORY_TEXT} or ${MEMORY_TEXT_READONLY} scheme. Was: ${uri}.`);
335
341
  }
336
342
  return new InMemoryTextResource(uri);
337
343
  }
@@ -233,7 +233,11 @@ export class ElectronMainMenuFactory extends BrowserMainMenuFactory {
233
233
  }
234
234
  };
235
235
 
236
- const role = this.roleFor(menu.id);
236
+ // Only assign Electron roles when no custom args are present.
237
+ // Custom args indicate that command handlers have context-dependent
238
+ // behavior (e.g. chat view copying the whole message when there is
239
+ // no DOM selection) that would be bypassed by the native role.
240
+ const role = args.length === 0 ? this.roleFor(menu.id) : undefined;
237
241
  if (role) {
238
242
  menuItem.role = role;
239
243
  delete menuItem.execute;
@@ -26,11 +26,14 @@ import { LocalConnectionProvider, RemoteConnectionProvider, ServiceConnectionPro
26
26
  import { WebSocketConnectionProvider } from '../../browser/messaging/ws-connection-provider';
27
27
  import { ConnectionCloseService, connectionCloseServicePath } from '../../common/messaging/connection-management';
28
28
  import { WebSocketConnectionSource } from '../../browser/messaging/ws-connection-source';
29
+ import { SocketWriteBuffer } from '../../common/messaging/socket-write-buffer';
29
30
 
30
31
  const backendServiceProvider = Symbol('backendServiceProvider2');
31
32
  const localServiceProvider = Symbol('localServiceProvider');
32
33
 
33
34
  export const messagingFrontendModule = new ContainerModule(bind => {
35
+ // Transient: each connection source gets its own private buffer instance.
36
+ bind(SocketWriteBuffer).toSelf();
34
37
  bind(ConnectionCloseService).toDynamicValue(ctx => WebSocketConnectionProvider.createProxy(ctx.container, connectionCloseServicePath)).inSingletonScope();
35
38
  bind(ElectronWebSocketConnectionSource).toSelf().inSingletonScope();
36
39
  bind(WebSocketConnectionSource).toService(ElectronWebSocketConnectionSource);
@@ -32,6 +32,7 @@ import { ExternalAppOpenHandler } from './external-app-open-handler';
32
32
  import { ElectronUriHandlerContribution } from '../electron-uri-handler';
33
33
  import { bindRootContributionProvider } from '../../common';
34
34
  import { WindowTitleContribution } from '../../browser/window/window-title-service';
35
+ import { WindowFocusService } from '../../browser/window/window-focus-service';
35
36
  import { WindowZoomStatusBarItem } from './window-zoom-status-bar-item';
36
37
  import '../../../src/electron-browser/style/window-zoom-action-bar.css';
37
38
 
@@ -50,6 +51,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
50
51
  bind(ExternalAppOpenHandler).toSelf().inSingletonScope();
51
52
  bind(OpenHandler).toService(ExternalAppOpenHandler);
52
53
  bindRootContributionProvider(bind, WindowTitleContribution);
54
+ bind(WindowFocusService).toSelf().inSingletonScope();
53
55
  bind(WindowZoomStatusBarItem).toSelf().inSingletonScope();
54
56
  bind(FrontendApplicationContribution).toService(WindowZoomStatusBarItem);
55
57
  });
@@ -136,6 +136,8 @@ export class TheiaMainApi implements ElectronMainApplicationContribution {
136
136
  }
137
137
  popup.popup({
138
138
  window: electronWindow,
139
+ x,
140
+ y,
139
141
  callback: () => {
140
142
  this.openPopups.delete(menuId);
141
143
  event.sender.send(CHANNEL_ON_CLOSE_POPUP, menuId);
@@ -343,13 +345,13 @@ export namespace TheiaRendererAPI {
343
345
  const disposables = new DisposableCollection();
344
346
 
345
347
  return new Promise<boolean>(resolve => {
346
- wc.send(CHANNEL_REQUEST_CLOSE, stopReason, confirmChannel, cancelChannel);
347
348
  createDisposableListener(ipcMain, confirmChannel, e => {
348
349
  resolve(true);
349
350
  }, disposables);
350
351
  createDisposableListener(ipcMain, cancelChannel, e => {
351
352
  resolve(false);
352
353
  }, disposables);
354
+ wc.send(CHANNEL_REQUEST_CLOSE, stopReason, confirmChannel, cancelChannel);
353
355
  }).finally(() => disposables.dispose());
354
356
  }
355
357
 
@@ -360,13 +362,13 @@ export namespace TheiaRendererAPI {
360
362
  const disposables = new DisposableCollection();
361
363
 
362
364
  return new Promise<boolean>(resolve => {
363
- mainWindow.send(CHANNEL_REQUEST_SECONDARY_CLOSE, secondaryWindow.mainFrame.name, confirmChannel, cancelChannel);
364
365
  createDisposableListener(ipcMain, confirmChannel, e => {
365
366
  resolve(true);
366
367
  }, disposables);
367
368
  createDisposableListener(ipcMain, cancelChannel, e => {
368
369
  resolve(false);
369
370
  }, disposables);
371
+ mainWindow.send(CHANNEL_REQUEST_SECONDARY_CLOSE, secondaryWindow.mainFrame.name, confirmChannel, cancelChannel);
370
372
  }).finally(() => disposables.dispose());
371
373
  }
372
374
 
@@ -15,8 +15,10 @@
15
15
  // *****************************************************************************
16
16
 
17
17
  import { ContainerModule } from 'inversify';
18
+ import { performance } from 'perf_hooks';
18
19
  import { generateUuid } from '../common/uuid';
19
20
  import { bindRootContributionProvider } from '../common/contribution-provider';
21
+ import { Stopwatch, SimpleStopwatch } from '../common/performance';
20
22
  import { RpcConnectionHandler } from '../common/messaging/proxy-factory';
21
23
  import { ElectronSecurityToken } from '../electron-common/electron-token';
22
24
  import { ElectronMainWindowService, electronMainWindowServicePath } from '../electron-common/electron-main-window-service';
@@ -34,6 +36,7 @@ const electronSecurityToken: ElectronSecurityToken = { value: generateUuid() };
34
36
  (global as any)[ElectronSecurityToken] = electronSecurityToken;
35
37
 
36
38
  export default new ContainerModule(bind => {
39
+ bind(Stopwatch).toConstantValue(new SimpleStopwatch('electron main', () => performance.now()));
37
40
  bind(ElectronMainApplication).toSelf().inSingletonScope();
38
41
  bind(ElectronMessagingContribution).toSelf().inSingletonScope();
39
42
  bind(ElectronMainApplicationContribution).toService(ElectronMessagingContribution);