@theia/core 1.76.0-next.7 → 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
@@ -0,0 +1,245 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 Safi Seid-Ahmad, K2view 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 { enableJSDOM } from '../test/jsdom';
18
+
19
+ // The widget module transitively imports `@lumino/widgets`, which touches `document` at load time.
20
+ let disableJSDOM = enableJSDOM();
21
+
22
+ import { expect } from 'chai';
23
+ import { flushSync } from 'react-dom';
24
+ import { createRoot, Root } from 'react-dom/client';
25
+ import { CompositeTreeNode, TreeNode } from './tree';
26
+ import { ExpandableTreeNode } from './tree-expansion';
27
+ import { TreeFocusService } from './tree-focus-service';
28
+ import { TreeModel } from './tree-model';
29
+ import { defaultTreeProps, TreeWidget } from './tree-widget';
30
+ import { ContextMenuRenderer } from '../context-menu-renderer';
31
+
32
+ disableJSDOM();
33
+
34
+ class CountingView extends TreeWidget.View {
35
+ scrollCount = 0;
36
+ protected override readonly scrollIntoViewIfNeeded = (): void => {
37
+ if (this.list && this.props.scrollToRow !== undefined) {
38
+ this.scrollCount++;
39
+ }
40
+ };
41
+ }
42
+
43
+ class TestTreeWidget extends TreeWidget {
44
+ focusedNode: TreeNode | undefined;
45
+ scrollToSelectedCount = 0;
46
+ override update(): void { }
47
+ protected override scrollToSelected(): void {
48
+ this.scrollToSelectedCount++;
49
+ }
50
+ get RowRenderer(): typeof this.ScrollingRowRenderer {
51
+ return this.ScrollingRowRenderer;
52
+ }
53
+ get currentScrollToRow(): number | undefined {
54
+ return this.scrollToRow;
55
+ }
56
+ get currentScrollToRowRequestId(): number {
57
+ return this.scrollToRowRequestId;
58
+ }
59
+ requestScrollToFocus(): void {
60
+ this.updateScrollToRow();
61
+ }
62
+ doUpdateRowsNow(): void {
63
+ this.doUpdateRows();
64
+ }
65
+ }
66
+
67
+ function createTreeRoot(...children: TreeNode[]): CompositeTreeNode {
68
+ const root: CompositeTreeNode = { id: 'root', name: 'root', parent: undefined, visible: false, children: [] };
69
+ return CompositeTreeNode.addChildren(root, children);
70
+ }
71
+
72
+ function leaf(id: string): TreeNode {
73
+ return { id, name: id, parent: undefined, visible: true };
74
+ }
75
+
76
+ describe('TreeWidget', () => {
77
+
78
+ before(() => disableJSDOM = enableJSDOM());
79
+ after(() => disableJSDOM());
80
+
81
+ describe('scroll requests', () => {
82
+
83
+ let widget: TestTreeWidget;
84
+ let model: { root: TreeNode | undefined };
85
+
86
+ beforeEach(() => {
87
+ model = { root: undefined };
88
+ widget = new TestTreeWidget(defaultTreeProps, model as unknown as TreeModel, {} as ContextMenuRenderer);
89
+ Object.assign(widget, {
90
+ focusService: { get focusedNode(): TreeNode | undefined { return widget.focusedNode; } } as TreeFocusService,
91
+ scheduleUpdateScrollToRow: () => widget.requestScrollToFocus()
92
+ });
93
+ });
94
+
95
+ afterEach(() => widget.dispose());
96
+
97
+ it('scrolls to the focused row when a scroll is requested', () => {
98
+ const target = leaf('target');
99
+ model.root = createTreeRoot(leaf('a'), leaf('b'), target);
100
+ widget.doUpdateRowsNow();
101
+ widget.focusedNode = target;
102
+ const requestId = widget.currentScrollToRowRequestId;
103
+
104
+ widget.requestScrollToFocus();
105
+
106
+ expect(widget.currentScrollToRow).to.equal(2);
107
+ expect(widget.currentScrollToRowRequestId).to.not.equal(requestId);
108
+ });
109
+
110
+ it('does not scroll again when the rows update after the request was served', () => {
111
+ const target = leaf('target');
112
+ model.root = createTreeRoot(leaf('a'), target);
113
+ widget.doUpdateRowsNow();
114
+ widget.focusedNode = target;
115
+ widget.requestScrollToFocus();
116
+ const requestId = widget.currentScrollToRowRequestId;
117
+
118
+ // A file created above the focused node shifts its row.
119
+ model.root = createTreeRoot(leaf('a'), leaf('new'), target);
120
+ widget.doUpdateRowsNow();
121
+
122
+ expect(widget.currentScrollToRow).to.equal(1);
123
+ expect(widget.currentScrollToRowRequestId).to.equal(requestId);
124
+ });
125
+
126
+ it('keeps a request pending until the rows contain the focused node', () => {
127
+ const target = leaf('target');
128
+ const folder: ExpandableTreeNode = { id: 'folder', name: 'folder', parent: undefined, visible: true, expanded: false, children: [] };
129
+ CompositeTreeNode.addChild(folder, target);
130
+ model.root = createTreeRoot(leaf('a'), folder);
131
+ widget.doUpdateRowsNow();
132
+ widget.focusedNode = target;
133
+
134
+ widget.requestScrollToFocus();
135
+ expect(widget.currentScrollToRow).to.be.undefined;
136
+ const requestId = widget.currentScrollToRowRequestId;
137
+
138
+ // The reveal expands the collapsed parent.
139
+ folder.expanded = true;
140
+ widget.doUpdateRowsNow();
141
+
142
+ expect(widget.currentScrollToRow).to.equal(2);
143
+ expect(widget.currentScrollToRowRequestId).to.not.equal(requestId);
144
+
145
+ // Once served, a later rows update does not scroll again.
146
+ const servedRequestId = widget.currentScrollToRowRequestId;
147
+ widget.doUpdateRowsNow();
148
+ expect(widget.currentScrollToRowRequestId).to.equal(servedRequestId);
149
+ });
150
+ });
151
+
152
+ describe('non-virtualized rows', () => {
153
+
154
+ let container: HTMLElement;
155
+ let root: Root;
156
+ let widget: TestTreeWidget;
157
+
158
+ const render = (scrollToRowRequestId: number): void => flushSync(() => root.render(
159
+ <widget.RowRenderer rows={[]} scrollToRowRequestId={scrollToRowRequestId} />
160
+ ));
161
+
162
+ beforeEach(() => {
163
+ widget = new TestTreeWidget({ ...defaultTreeProps, virtualized: false }, { root: undefined } as unknown as TreeModel, {} as ContextMenuRenderer);
164
+ container = document.createElement('div');
165
+ document.body.appendChild(container);
166
+ root = createRoot(container);
167
+ });
168
+
169
+ afterEach(() => {
170
+ flushSync(() => root.unmount());
171
+ document.body.removeChild(container);
172
+ widget.dispose();
173
+ });
174
+
175
+ it('scrolls to the selection once per scroll request, not on every render', () => {
176
+ render(1);
177
+ const scrollsAfterMount = widget.scrollToSelectedCount;
178
+
179
+ render(1);
180
+ render(1);
181
+ expect(widget.scrollToSelectedCount).to.equal(scrollsAfterMount);
182
+
183
+ render(2);
184
+ expect(widget.scrollToSelectedCount).to.equal(scrollsAfterMount + 1);
185
+ });
186
+ });
187
+
188
+ describe('View', () => {
189
+
190
+ let container: HTMLElement;
191
+ let root: Root;
192
+ let view: CountingView | undefined;
193
+ const rows: TreeWidget.NodeRow[] = [0, 1, 2].map(index => ({ index, depth: 0, node: leaf(`node-${index}`) }));
194
+
195
+ const render = (props: Partial<TreeWidget.ViewProps>): void => flushSync(() => root.render(
196
+ <CountingView
197
+ ref={instance => { view = instance ?? undefined; }}
198
+ width={100}
199
+ height={100}
200
+ rows={rows}
201
+ renderNodeRow={row => <div>{row.node.id}</div>}
202
+ {...props}
203
+ />
204
+ ));
205
+
206
+ beforeEach(() => {
207
+ container = document.createElement('div');
208
+ document.body.appendChild(container);
209
+ root = createRoot(container);
210
+ });
211
+
212
+ afterEach(() => {
213
+ flushSync(() => root.unmount());
214
+ document.body.removeChild(container);
215
+ });
216
+
217
+ it('does not scroll again when re-rendered without a new scroll request', () => {
218
+ render({ scrollToRow: 2, scrollToRowRequestId: 1 });
219
+ const scrollsAfterMount = view!.scrollCount;
220
+
221
+ render({ scrollToRow: 2, scrollToRowRequestId: 1 });
222
+ render({ scrollToRow: 2, scrollToRowRequestId: 1 });
223
+
224
+ expect(view!.scrollCount).to.equal(scrollsAfterMount);
225
+ });
226
+
227
+ it('scrolls again when a new scroll request arrives for the same row', () => {
228
+ render({ scrollToRow: 2, scrollToRowRequestId: 1 });
229
+ const scrollsAfterMount = view!.scrollCount;
230
+
231
+ render({ scrollToRow: 2, scrollToRowRequestId: 2 });
232
+
233
+ expect(view!.scrollCount).to.equal(scrollsAfterMount + 1);
234
+ });
235
+
236
+ it('scrolls when the row to scroll to changes', () => {
237
+ render({ scrollToRow: 1 });
238
+ const scrollsAfterMount = view!.scrollCount;
239
+
240
+ render({ scrollToRow: 2 });
241
+
242
+ expect(view!.scrollCount).to.equal(scrollsAfterMount + 1);
243
+ });
244
+ });
245
+ });
@@ -373,7 +373,9 @@ export class TreeWidget extends ReactWidget implements StatefulWidget {
373
373
  }
374
374
  this.rows = new Map(rowsToUpdate);
375
375
  this.update();
376
- this.scheduleUpdateScrollToRow();
376
+ if (this.scrollToRowPending) {
377
+ this.scheduleUpdateScrollToRow();
378
+ }
377
379
  }
378
380
 
379
381
  protected getDepthForNode(node: TreeNode, depths: Map<CompositeTreeNode | undefined, number>): number {
@@ -395,11 +397,24 @@ export class TreeWidget extends ReactWidget implements StatefulWidget {
395
397
  */
396
398
  protected scrollToRow: number | undefined;
397
399
  /**
398
- * Update the `scrollToRow`.
399
- * @param updateOptions the tree widget force update options.
400
+ * Identifies the latest scroll request, so that the view scrolls to `scrollToRow` once per request
401
+ * rather than on every render.
402
+ */
403
+ protected scrollToRowRequestId = 0;
404
+ /**
405
+ * `true` while the latest scroll request has no target row yet, for example when a reveal selects a node
406
+ * whose parent is still collapsed. The request is served on the first rows update that contains the target.
407
+ */
408
+ protected scrollToRowPending = false;
409
+ /**
410
+ * Update the `scrollToRow` and request the view to scroll to it.
400
411
  */
401
412
  protected updateScrollToRow(): void {
402
413
  this.scrollToRow = this.getScrollToRow();
414
+ this.scrollToRowPending = this.scrollToRow === undefined;
415
+ if (!this.scrollToRowPending) {
416
+ this.scrollToRowRequestId++;
417
+ }
403
418
  this.update();
404
419
  }
405
420
 
@@ -510,8 +525,8 @@ export class TreeWidget extends ReactWidget implements StatefulWidget {
510
525
  return this.model.root;
511
526
  }
512
527
 
513
- protected ScrollingRowRenderer: React.FC<{ rows: TreeWidget.NodeRow[] }> = ({ rows }) => {
514
- useEffect(() => this.scrollToSelected());
528
+ protected ScrollingRowRenderer: React.FC<{ rows: TreeWidget.NodeRow[], scrollToRowRequestId?: number }> = ({ rows, scrollToRowRequestId }) => {
529
+ useEffect(() => this.scrollToSelected(), [scrollToRowRequestId]);
515
530
  return <>{rows.map(row => <div key={row.index}>{this.renderNodeRow(row)}</div>)}</>;
516
531
  };
517
532
 
@@ -524,7 +539,7 @@ export class TreeWidget extends ReactWidget implements StatefulWidget {
524
539
  if (model.root) {
525
540
  const rows = Array.from(this.rows.values());
526
541
  if (this.props.virtualized === false) {
527
- return <this.ScrollingRowRenderer rows={rows} />;
542
+ return <this.ScrollingRowRenderer rows={rows} scrollToRowRequestId={this.scrollToRowRequestId} />;
528
543
  }
529
544
  return <TreeWidget.View
530
545
  ref={view => { this.view = (view || undefined); }}
@@ -533,6 +548,7 @@ export class TreeWidget extends ReactWidget implements StatefulWidget {
533
548
  rows={rows}
534
549
  renderNodeRow={this.renderNodeRow}
535
550
  scrollToRow={this.scrollToRow}
551
+ scrollToRowRequestId={this.scrollToRowRequestId}
536
552
  onAtBottomStateChangeEmitter={this.onAtBottomStateChangeEmitter}
537
553
  {...this.props.viewProps}
538
554
  />;
@@ -1599,6 +1615,10 @@ export namespace TreeWidget {
1599
1615
  * The scroll to row value.
1600
1616
  */
1601
1617
  scrollToRow?: number
1618
+ /**
1619
+ * Changes with every scroll request, so that the view scrolls to `scrollToRow` again even if the row did not change.
1620
+ */
1621
+ scrollToRowRequestId?: number
1602
1622
  /**
1603
1623
  * The list of node rows.
1604
1624
  */
@@ -1615,6 +1635,13 @@ export namespace TreeWidget {
1615
1635
  export class View extends React.Component<ViewProps> {
1616
1636
  list: VirtuosoHandle | undefined;
1617
1637
 
1638
+ /**
1639
+ * Only stores the handle. A new callback on every render would make React detach and re-attach the ref on every commit.
1640
+ */
1641
+ protected readonly setList = (list: VirtuosoHandle | null): void => {
1642
+ this.list = list || undefined;
1643
+ };
1644
+
1618
1645
  /**
1619
1646
  * Ensure the selected row is scrolled into view when virtualization finishes updating.
1620
1647
  */
@@ -1633,18 +1660,15 @@ export namespace TreeWidget {
1633
1660
  }
1634
1661
 
1635
1662
  override componentDidUpdate(prevProps: ViewProps): void {
1636
- if (this.props.scrollToRow !== prevProps.scrollToRow) {
1663
+ if (this.props.scrollToRow !== prevProps.scrollToRow || this.props.scrollToRowRequestId !== prevProps.scrollToRowRequestId) {
1637
1664
  this.scrollIntoViewIfNeeded();
1638
1665
  }
1639
1666
  }
1640
1667
 
1641
1668
  override render(): React.ReactNode {
1642
- const { rows, width, height, scrollToRow, renderNodeRow, onAtBottomStateChangeEmitter, ...other } = this.props;
1669
+ const { rows, width, height, scrollToRow, scrollToRowRequestId, renderNodeRow, onAtBottomStateChangeEmitter, ...other } = this.props;
1643
1670
  return <Virtuoso
1644
- ref={(list: VirtuosoHandle | null) => {
1645
- this.list = (list || undefined);
1646
- this.scrollIntoViewIfNeeded();
1647
- }}
1671
+ ref={this.setList}
1648
1672
  atBottomStateChange={(atBottom: boolean) => {
1649
1673
  onAtBottomStateChangeEmitter?.fire(atBottom);
1650
1674
  }}
@@ -38,7 +38,7 @@ import { Drag } from '@lumino/dragdrop';
38
38
  import { MimeData } from '@lumino/coreutils';
39
39
  import { ElementExt } from '@lumino/domutils';
40
40
  import { TabBarDecoratorService } from './shell/tab-bar-decorator';
41
- import { ContextKeyService } from './context-key-service';
41
+ import { ContextKeyService, ContextMatcher } from './context-key-service';
42
42
  import { KeybindingRegistry } from './keybinding';
43
43
  import { SubmenuAsToolbarItemWrapper } from './shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters';
44
44
  import { TheiaSplitPanel } from './shell/theia-split-panel';
@@ -109,8 +109,8 @@ class PartsMenuToolbarItem extends SubmenuAsToolbarItemWrapper {
109
109
  super(effectiveMenuPath, commandRegistry, menuRegistry, contextKeyService, contextMenuRenderer, menuNode, group);
110
110
  }
111
111
 
112
- override isVisible(widget: Widget): boolean {
113
- return widget === this.target() && super.isVisible(widget);
112
+ override isVisible(widget: Widget, contextMatcher: ContextMatcher): boolean {
113
+ return widget === this.target() && super.isVisible(widget, contextMatcher);
114
114
  }
115
115
  }
116
116
 
@@ -0,0 +1,34 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 JuliaHub, Inc. and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import { Widget } from '@lumino/widgets';
18
+
19
+ export const WidgetContextKeyContribution = Symbol('WidgetContextKeyContribution');
20
+
21
+ /**
22
+ * Contributes the context key values that describe a particular widget, so that `when` clauses about
23
+ * that widget - such as the ones of its tab bar toolbar items - can be evaluated without consulting the
24
+ * ambient context, which describes whichever widget currently has focus.
25
+ */
26
+ export interface WidgetContextKeyContribution {
27
+ /**
28
+ * @param widget the widget to describe.
29
+ * @returns the values to overlay over the ambient context, or `undefined` if this contribution does not
30
+ * describe the given widget. A contribution owning a key should return an explicit "unset" value for the
31
+ * widgets the key does not apply to, so that the ambient value of another widget cannot leak in.
32
+ */
33
+ getContextKeyValues(widget: Widget): Iterable<[string, unknown]> | undefined;
34
+ }
@@ -25,6 +25,7 @@ import { DefaultSecondaryWindowService } from './default-secondary-window-servic
25
25
  import { bindRootContributionProvider } from '../../common';
26
26
  import { WindowTitleContribution } from './window-title-service';
27
27
  import { WindowFocusService } from './window-focus-service';
28
+ import { DefaultWindowLaunchArgs, WindowLaunchArgs } from './window-launch-args';
28
29
 
29
30
  export default new ContainerModule(bind => {
30
31
  bind(DefaultWindowService).toSelf().inSingletonScope();
@@ -33,5 +34,7 @@ export default new ContainerModule(bind => {
33
34
  bind(ClipboardService).to(BrowserClipboardService).inSingletonScope();
34
35
  bind(SecondaryWindowService).to(DefaultSecondaryWindowService).inSingletonScope();
35
36
  bind(WindowFocusService).toSelf().inSingletonScope();
37
+ bind(DefaultWindowLaunchArgs).toSelf().inSingletonScope();
38
+ bind(WindowLaunchArgs).toService(DefaultWindowLaunchArgs);
36
39
  bindRootContributionProvider(bind, WindowTitleContribution);
37
40
  });
@@ -0,0 +1,53 @@
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
+ export const WindowLaunchArgs = Symbol('WindowLaunchArgs');
21
+
22
+ /**
23
+ * Provides the CLI options a window was launched with, when it was opened by a *forwarded*
24
+ * (second-instance) launch. See the `second-instance` handling in `ElectronMainApplication`, and
25
+ * `LaunchArgsStore` for why the options are provided over a trusted channel rather than the URL.
26
+ *
27
+ * @experimental
28
+ */
29
+ export interface WindowLaunchArgs {
30
+
31
+ /**
32
+ * The parsed launch options of the current window, or `undefined` when the window was not opened
33
+ * by a forwarded launch (a cold-start window, or any browser deployment). Synchronous, so a
34
+ * contribution can act on it from the first paint.
35
+ */
36
+ getLaunchArgs(): LaunchArguments | undefined;
37
+ }
38
+
39
+ /**
40
+ * Default (browser) implementation: there is no trusted per-window launch channel in a plain
41
+ * browser deployment, and the window URL must never be treated as one, so this always reports "no
42
+ * launch options". Electron rebinds {@link WindowLaunchArgs} to an implementation backed by the
43
+ * per-window metadata the preload script provides.
44
+ *
45
+ * @experimental
46
+ */
47
+ @injectable()
48
+ export class DefaultWindowLaunchArgs implements WindowLaunchArgs {
49
+
50
+ getLaunchArgs(): LaunchArguments | undefined {
51
+ return undefined;
52
+ }
53
+ }
@@ -93,8 +93,11 @@ export const frontendOnlyApplicationModule = new ContainerModule((bind, unbind,
93
93
  getExecPath: async (): Promise<string> => '',
94
94
  getVariables: async (): Promise<EnvVariable[]> => [],
95
95
  getValue: async (_key: string): Promise<EnvVariable | undefined> => undefined,
96
- getConfigDirUri: async (): Promise<string> => '',
97
- getHomeDirUri: async (): Promise<string> => '',
96
+ // Matches the backend's default '.theia' configuration folder.
97
+ getConfigDirUri: async (): Promise<string> => 'file:///.theia',
98
+ // Has to be a directory that already exists: consumers such as the file dialog only
99
+ // resolve this, they never create it, and nothing seeds a home folder in OPFS.
100
+ getHomeDirUri: async (): Promise<string> => 'file:///',
98
101
  getDrives: async (): Promise<string[]> => []
99
102
  };
100
103
  if (isBound(EnvVariablesServer)) {
@@ -13,7 +13,9 @@
13
13
  //
14
14
  // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
15
  // *****************************************************************************
16
- import { Event, RpcProxy, Channel, RpcProxyFactory, Emitter } from '../../common';
16
+ import { Emitter, Event } from '../../common/event';
17
+ import { Channel } from '../../common/message-rpc/channel';
18
+ import { RpcProxy, RpcProxyFactory } from '../../common/messaging/proxy-factory';
17
19
  import { injectable, inject, named } from 'inversify';
18
20
  import { ServiceConnectionProvider } from '../../browser/messaging/service-connection-provider';
19
21
  import { ILogger } from '../../common/logger';