@theia/core 1.77.0-next.4 → 1.77.0-next.47

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 (66) hide show
  1. package/README.md +5 -5
  2. package/lib/browser/catalog.json +207 -22
  3. package/lib/browser/color-application-contribution.d.ts +6 -0
  4. package/lib/browser/color-application-contribution.d.ts.map +1 -1
  5. package/lib/browser/color-application-contribution.js +15 -4
  6. package/lib/browser/color-application-contribution.js.map +1 -1
  7. package/lib/browser/color-application-contribution.spec.d.ts +2 -0
  8. package/lib/browser/color-application-contribution.spec.d.ts.map +1 -0
  9. package/lib/browser/color-application-contribution.spec.js +96 -0
  10. package/lib/browser/color-application-contribution.spec.js.map +1 -0
  11. package/lib/browser/color-registry.d.ts +6 -0
  12. package/lib/browser/color-registry.d.ts.map +1 -1
  13. package/lib/browser/color-registry.js +12 -0
  14. package/lib/browser/color-registry.js.map +1 -1
  15. package/lib/browser/tree/tree-selection-state.d.ts +23 -1
  16. package/lib/browser/tree/tree-selection-state.d.ts.map +1 -1
  17. package/lib/browser/tree/tree-selection-state.js +23 -1
  18. package/lib/browser/tree/tree-selection-state.js.map +1 -1
  19. package/lib/browser/tree/tree-widget.d.ts +28 -0
  20. package/lib/browser/tree/tree-widget.d.ts.map +1 -1
  21. package/lib/browser/tree/tree-widget.js +59 -3
  22. package/lib/browser/tree/tree-widget.js.map +1 -1
  23. package/lib/browser/tree/tree-widget.spec.js +53 -3
  24. package/lib/browser/tree/tree-widget.spec.js.map +1 -1
  25. package/lib/browser/widgets/select-component.d.ts.map +1 -1
  26. package/lib/browser/widgets/select-component.js +6 -11
  27. package/lib/browser/widgets/select-component.js.map +1 -1
  28. package/lib/browser/widgets/select-component.spec.d.ts +2 -0
  29. package/lib/browser/widgets/select-component.spec.d.ts.map +1 -0
  30. package/lib/browser/widgets/select-component.spec.js +66 -0
  31. package/lib/browser/widgets/select-component.spec.js.map +1 -0
  32. package/lib/common/core-preferences.js +5 -5
  33. package/lib/common/core-preferences.js.map +1 -1
  34. package/lib/electron-browser/electron-clipboard-service.d.ts +1 -1
  35. package/lib/electron-browser/electron-clipboard-service.d.ts.map +1 -1
  36. package/lib/electron-browser/preload.js +1 -1
  37. package/lib/electron-browser/preload.js.map +1 -1
  38. package/lib/electron-common/electron-api.d.ts +1 -1
  39. package/lib/electron-common/electron-api.d.ts.map +1 -1
  40. package/lib/electron-main/electron-api-main.d.ts.map +1 -1
  41. package/lib/electron-main/electron-api-main.js +5 -4
  42. package/lib/electron-main/electron-api-main.js.map +1 -1
  43. package/lib/node/messaging/binary-message-pipe.d.ts.map +1 -1
  44. package/lib/node/messaging/binary-message-pipe.js +5 -3
  45. package/lib/node/messaging/binary-message-pipe.js.map +1 -1
  46. package/lib/node/messaging/binary-message-pipe.spec.d.ts +2 -0
  47. package/lib/node/messaging/binary-message-pipe.spec.d.ts.map +1 -0
  48. package/lib/node/messaging/binary-message-pipe.spec.js +71 -0
  49. package/lib/node/messaging/binary-message-pipe.spec.js.map +1 -0
  50. package/package.json +16 -16
  51. package/src/browser/color-application-contribution.spec.ts +108 -0
  52. package/src/browser/color-application-contribution.ts +16 -4
  53. package/src/browser/color-registry.ts +13 -0
  54. package/src/browser/style/hover-service.css +0 -2
  55. package/src/browser/tree/tree-selection-state.ts +40 -1
  56. package/src/browser/tree/tree-widget.spec.tsx +59 -3
  57. package/src/browser/tree/tree-widget.tsx +64 -2
  58. package/src/browser/widgets/select-component.spec.ts +77 -0
  59. package/src/browser/widgets/select-component.tsx +6 -10
  60. package/src/common/core-preferences.ts +5 -5
  61. package/src/electron-browser/electron-clipboard-service.ts +1 -1
  62. package/src/electron-browser/preload.ts +2 -2
  63. package/src/electron-common/electron-api.ts +1 -1
  64. package/src/electron-main/electron-api-main.ts +5 -4
  65. package/src/node/messaging/binary-message-pipe.spec.ts +81 -0
  66. package/src/node/messaging/binary-message-pipe.ts +5 -4
@@ -1655,13 +1655,73 @@ export namespace TreeWidget {
1655
1655
  }
1656
1656
  };
1657
1657
 
1658
+ /**
1659
+ * `true` once Virtuoso has measured the list. Before that, `scrollIntoView` sees every row as visible and does nothing.
1660
+ */
1661
+ protected measured = false;
1662
+ /**
1663
+ * `true` while a scroll request made before the list was measured waits to be served.
1664
+ */
1665
+ protected scrollHeld = false;
1666
+ /**
1667
+ * The latest list height reported by Virtuoso. It can still change after the first report, as more rows are measured.
1668
+ */
1669
+ protected listHeight = 0;
1670
+ /**
1671
+ * How many frames a held scroll request waits for the list to be rendered at its height, about half a second at 60 Hz.
1672
+ */
1673
+ protected readonly maxRenderWaitFrames = 30;
1674
+ protected scroller: HTMLElement | undefined;
1675
+
1676
+ /**
1677
+ * Scroll now, or hold the request until the list is measured, e.g. for the focus restored when the view mounts.
1678
+ */
1679
+ protected requestScroll(): void {
1680
+ if (this.measured) {
1681
+ this.scrollIntoViewIfNeeded();
1682
+ } else {
1683
+ this.scrollHeld = this.props.scrollToRow !== undefined;
1684
+ }
1685
+ }
1686
+
1687
+ protected readonly onTotalListHeightChanged = (height: number): void => {
1688
+ this.listHeight = height;
1689
+ if (!this.measured && height > 0) {
1690
+ this.measured = true;
1691
+ if (this.scrollHeld) {
1692
+ this.scrollHeld = false;
1693
+ this.scrollWhenRendered();
1694
+ }
1695
+ }
1696
+ this.props.totalListHeightChanged?.(height);
1697
+ };
1698
+
1699
+ protected readonly setScroller = (scroller: HTMLElement | Window | null): void => {
1700
+ this.scroller = scroller instanceof HTMLElement ? scroller : undefined;
1701
+ this.props.scrollerRef?.(scroller);
1702
+ };
1703
+
1704
+ /**
1705
+ * Virtuoso reports the list height before React renders the list at it, and a scroll before that is clamped
1706
+ * to the old height. Wait for the rendered height, at most `maxRenderWaitFrames` frames.
1707
+ */
1708
+ protected scrollWhenRendered(framesLeft = this.maxRenderWaitFrames): void {
1709
+ window.requestAnimationFrame(() => {
1710
+ if (this.scroller && this.scroller.scrollHeight < this.listHeight && framesLeft > 0) {
1711
+ this.scrollWhenRendered(framesLeft - 1);
1712
+ } else {
1713
+ this.scrollIntoViewIfNeeded();
1714
+ }
1715
+ });
1716
+ }
1717
+
1658
1718
  override componentDidMount(): void {
1659
- this.scrollIntoViewIfNeeded();
1719
+ this.requestScroll();
1660
1720
  }
1661
1721
 
1662
1722
  override componentDidUpdate(prevProps: ViewProps): void {
1663
1723
  if (this.props.scrollToRow !== prevProps.scrollToRow || this.props.scrollToRowRequestId !== prevProps.scrollToRowRequestId) {
1664
- this.scrollIntoViewIfNeeded();
1724
+ this.requestScroll();
1665
1725
  }
1666
1726
  }
1667
1727
 
@@ -1681,6 +1741,8 @@ export namespace TreeWidget {
1681
1741
  // Higher value provides smoother scrolling experience especially during inference, but uses more memory
1682
1742
  overscan={800}
1683
1743
  {...other}
1744
+ totalListHeightChanged={this.onTotalListHeightChanged}
1745
+ scrollerRef={this.setScroller}
1684
1746
  />;
1685
1747
  }
1686
1748
  }
@@ -0,0 +1,77 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 EclipseSource GmbH.
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
+ let disableJSDOM = enableJSDOM();
19
+
20
+ import { expect } from 'chai';
21
+ import * as React from 'react';
22
+ import * as sinon from 'sinon';
23
+ import * as browser from '../browser';
24
+ import { SelectComponent, SelectOption } from './select-component';
25
+
26
+ disableJSDOM();
27
+
28
+ class TestSelectComponent extends SelectComponent {
29
+ dropdown(): React.ReactElement<{ style: React.CSSProperties }> {
30
+ this.state = { ...this.state, dimensions: new DOMRect(20, 20, 90, 23) };
31
+ return this.renderDropdown() as React.ReactElement<{ style: React.CSSProperties }>;
32
+ }
33
+ }
34
+
35
+ describe('SelectComponent dropdown width', () => {
36
+ let component: TestSelectComponent;
37
+ let textWidth: sinon.SinonStub;
38
+
39
+ before(() => disableJSDOM = enableJSDOM());
40
+ after(() => disableJSDOM());
41
+
42
+ beforeEach(() => {
43
+ textWidth = sinon.stub(browser, 'measureTextWidth').returns(84);
44
+ sinon.stub(document.documentElement, 'getBoundingClientRect').returns(new DOMRect(0, 0, 800, 600));
45
+ });
46
+
47
+ afterEach(() => {
48
+ component.componentWillUnmount();
49
+ sinon.restore();
50
+ });
51
+
52
+ function dropdown(options: SelectOption[], alignment?: 'left' | 'right'): React.ReactElement<{ style: React.CSSProperties }> {
53
+ component = new TestSelectComponent({ options, alignment });
54
+ return component.dropdown();
55
+ }
56
+
57
+ it('keeps label-based sizing when no option has a description', () => {
58
+ expect(dropdown([{ label: 'Off' }, { label: 'None', description: '' }]).props.style.width).to.equal(100);
59
+ });
60
+
61
+ it('reserves readable space even when only an unselected option has a description', () => {
62
+ expect(dropdown([{ label: 'Auto' }, { label: 'Off', description: 'Uses provider defaults.' }]).props.style.width).to.equal(300);
63
+ });
64
+
65
+ it('keeps enough space for labels wider than the description minimum', () => {
66
+ textWidth.returns(384);
67
+ expect(dropdown([{ label: 'A long label', description: 'Description' }]).props.style.width).to.equal(400);
68
+ });
69
+
70
+ it('caps the description width at the available space for either alignment', () => {
71
+ (document.documentElement.getBoundingClientRect as sinon.SinonStub).returns(new DOMRect(0, 0, 200, 600));
72
+ const options = [{ label: 'Off', description: 'Uses provider defaults.' }];
73
+ expect(dropdown(options).props.style.width).to.equal(180);
74
+ component.componentWillUnmount();
75
+ expect(dropdown(options, 'right').props.style.width).to.equal(110);
76
+ });
77
+ });
@@ -163,7 +163,8 @@ export class SelectComponent extends React.Component<SelectComponentProps, Selec
163
163
 
164
164
  protected getOptimalWidth(): number {
165
165
  const textWidth = measureTextWidth(this.props.options.map(e => e.label || e.value || '' + (e.detail || '')));
166
- return Math.ceil(textWidth + 16);
166
+ const minWidth = this.props.options.some(option => option.description) ? 300 : 0;
167
+ return Math.max(Math.ceil(textWidth + 16), minWidth);
167
168
  }
168
169
 
169
170
  protected getOptimalHeight(maxWidth?: number): number {
@@ -320,15 +321,10 @@ export class SelectComponent extends React.Component<SelectComponentProps, Selec
320
321
  if (!this.fieldRef.current) {
321
322
  return;
322
323
  }
323
- if (ev.key === 'ArrowUp') {
324
- const selected = this.nextNotSeparator('backwards', this.state.hover);
325
- this.setState({
326
- selected,
327
- hover: selected
328
- });
329
- } else if (ev.key === 'ArrowDown') {
324
+ if (ev.key === 'ArrowUp' || ev.key === 'ArrowDown') {
325
+ const direction = ev.key === 'ArrowUp' ? 'backwards' : 'forwards';
330
326
  if (this.state.dimensions) {
331
- const selected = this.nextNotSeparator('forwards', this.state.hover);
327
+ const selected = this.nextNotSeparator(direction, this.state.hover);
332
328
  this.setState({
333
329
  selected,
334
330
  hover: selected
@@ -337,7 +333,7 @@ export class SelectComponent extends React.Component<SelectComponentProps, Selec
337
333
  this.toggleVisibility();
338
334
  // Start on the first selectable option (startFrom -1 evaluates index 0 first), so a
339
335
  // leading separator or disabled option is skipped instead of becoming the active row.
340
- const selected = this.nextNotSeparator('forwards', -1);
336
+ const selected = this.nextNotSeparator(direction, -1);
341
337
  this.setState({
342
338
  selected,
343
339
  hover: selected,
@@ -153,18 +153,18 @@ export const corePreferenceSchema: PreferenceSchema = {
153
153
  'http.proxy': {
154
154
  type: 'string',
155
155
  pattern: '^https?://([^:]*(:[^@]*)?@)?([^:]+|\\[[:0-9a-fA-F]+\\])(:\\d+)?/?$|^$',
156
- markdownDescription: nls.localizeByDefault('The proxy setting to use. If not set, will be inherited from the `http_proxy` and `https_proxy` environment variables. When during [remote development](https://aka.ms/vscode-remote) the {0} setting is disabled this setting can be configured in the local and the remote settings separately.'),
156
+ markdownDescription: nls.localize('theia/core/http/proxy', 'The proxy setting to use. If not set, will be inherited from the `http_proxy` and `https_proxy` environment variables.'),
157
157
  scope: PreferenceScope.User
158
158
  },
159
159
  'http.proxyStrictSSL': {
160
160
  type: 'boolean',
161
161
  default: true,
162
- description: nls.localizeByDefault('Controls whether the proxy server certificate should be verified against the list of supplied CAs. When during [remote development](https://aka.ms/vscode-remote) the {0} setting is disabled this setting can be configured in the local and the remote settings separately.'),
162
+ description: nls.localize('theia/core/http/proxyStrictSSL', 'Controls whether the proxy server certificate should be verified against the list of supplied CAs.'),
163
163
  scope: PreferenceScope.User
164
164
  },
165
165
  'http.proxyAuthorization': {
166
166
  type: 'string',
167
- markdownDescription: nls.localizeByDefault('The value to send as the `Proxy-Authorization` header for every network request. When during [remote development](https://aka.ms/vscode-remote) the {0} setting is disabled this setting can be configured in the local and the remote settings separately.'),
167
+ markdownDescription: nls.localize('theia/core/http/proxyAuthorization', 'The value to send as the `Proxy-Authorization` header for every network request.'),
168
168
  scope: PreferenceScope.User
169
169
  },
170
170
  'http.proxySupport': {
@@ -177,13 +177,13 @@ export const corePreferenceSchema: PreferenceSchema = {
177
177
  nls.localizeByDefault('Enable proxy support for extensions, override request options.'),
178
178
  ],
179
179
  default: 'override',
180
- description: nls.localizeByDefault('Use the proxy support for extensions. When during [remote development](https://aka.ms/vscode-remote) the {0} setting is disabled this setting can be configured in the local and the remote settings separately.'),
180
+ description: nls.localize('theia/core/http/proxySupport', 'Use the proxy support for extensions.'),
181
181
  scope: PreferenceScope.User
182
182
  },
183
183
  'http.systemCertificates': {
184
184
  type: 'boolean',
185
185
  default: true,
186
- description: nls.localizeByDefault('Controls whether CA certificates should be loaded from the OS. On Windows and macOS, a reload of the window is required after turning this off. When during [remote development](https://aka.ms/vscode-remote) the {0} setting is disabled this setting can be configured in the local and the remote settings separately.'),
186
+ description: nls.localize('theia/core/http/systemCertificates', 'Controls whether CA certificates should be loaded from the OS. On Windows and macOS, a reload of the window is required after turning this off.'),
187
187
  scope: PreferenceScope.User
188
188
  },
189
189
  'workbench.list.openMode': {
@@ -25,7 +25,7 @@ export class ElectronClipboardService implements ClipboardService {
25
25
  protected readonly onDidWriteTextEmitter = new Emitter<string>();
26
26
  readonly onDidWriteText: Event<string> = this.onDidWriteTextEmitter.event;
27
27
 
28
- readText(): string {
28
+ readText(): Promise<string> {
29
29
  return window.electronTheiaCore.readClipboard();
30
30
  }
31
31
 
@@ -232,8 +232,8 @@ const api: TheiaCoreAPI = {
232
232
  ipcRenderer.send(CHANNEL_APP_STATE_CHANGED, state);
233
233
  },
234
234
 
235
- readClipboard(): string {
236
- return ipcRenderer.sendSync(CHANNEL_READ_CLIPBOARD);
235
+ readClipboard(): Promise<string> {
236
+ return ipcRenderer.invoke(CHANNEL_READ_CLIPBOARD);
237
237
  },
238
238
 
239
239
  writeClipboard(text): void {
@@ -107,7 +107,7 @@ export interface TheiaCoreAPI {
107
107
 
108
108
  applicationStateChanged(state: FrontendApplicationState): void;
109
109
 
110
- readClipboard(): string;
110
+ readClipboard(): Promise<string>;
111
111
  writeClipboard(text: string): void;
112
112
 
113
113
  onKeyboardLayoutChanged(handler: (newLayout: NativeKeyboardLayout) => void): Disposable;
@@ -274,11 +274,12 @@ export class TheiaMainApi implements ElectronMainApplicationContribution {
274
274
  event.returnValue = BrowserWindow.fromWebContents(event.sender)?.isFullScreen();
275
275
  });
276
276
 
277
- ipcMain.on(CHANNEL_READ_CLIPBOARD, event => {
278
- event.returnValue = clipboard.readText();
279
- });
277
+ ipcMain.handle(CHANNEL_READ_CLIPBOARD, () => clipboard.readText().catch(error => {
278
+ console.warn('Could not read from the clipboard', error);
279
+ return '';
280
+ }));
280
281
  ipcMain.on(CHANNEL_WRITE_CLIPBOARD, (event, text) => {
281
- clipboard.writeText(text);
282
+ clipboard.writeText(text).catch(error => console.warn('Could not write to the clipboard', error));
282
283
  });
283
284
 
284
285
  nativeKeymap.onDidChangeKeyboardLayout(() => {
@@ -0,0 +1,81 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 Ivan Baksheev 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 { PassThrough } from 'stream';
19
+ import { BinaryMessagePipe } from './binary-message-pipe';
20
+
21
+ class TestBinaryMessagePipe extends BinaryMessagePipe {
22
+ override encodeMessageStart(message: Uint8Array): Uint8Array {
23
+ return super.encodeMessageStart(message);
24
+ }
25
+
26
+ override get messageStartByteLength(): number {
27
+ return super.messageStartByteLength;
28
+ }
29
+ }
30
+
31
+ describe('BinaryMessagePipe', () => {
32
+ const payload = Buffer.alloc(64, 0x41);
33
+ let headerLength: number;
34
+ let headerSplit: number;
35
+ let stream: PassThrough;
36
+ let pipe: TestBinaryMessagePipe;
37
+ let messages: Buffer[];
38
+ let frame: Buffer;
39
+
40
+ beforeEach(() => {
41
+ stream = new PassThrough();
42
+ pipe = new TestBinaryMessagePipe(stream);
43
+ headerLength = pipe.messageStartByteLength;
44
+ headerSplit = Math.floor(headerLength / 2);
45
+ messages = [];
46
+ frame = encodeFrame(payload);
47
+ pipe.onMessage(message => messages.push(Buffer.from(message)));
48
+ });
49
+
50
+ afterEach(() => {
51
+ pipe.dispose();
52
+ stream.destroy();
53
+ });
54
+
55
+ it('accumulates consecutive chunks that are shorter than the header', () => {
56
+ stream.emit('data', frame.subarray(0, headerSplit));
57
+ stream.emit('data', frame.subarray(headerSplit, headerLength));
58
+ stream.emit('data', frame.subarray(headerLength));
59
+
60
+ expect(messages).to.deep.equal([payload]);
61
+ });
62
+
63
+ it('receives the original payload when the header is split between chunks', () => {
64
+ stream.emit('data', frame.subarray(0, headerSplit));
65
+ stream.emit('data', frame.subarray(headerSplit));
66
+
67
+ expect(messages).to.deep.equal([payload]);
68
+ });
69
+
70
+ it('preserves the next frame in the same chunk after a partial header', () => {
71
+ const nextPayload = Buffer.from('next');
72
+ stream.emit('data', frame.subarray(0, headerSplit));
73
+ stream.emit('data', Buffer.concat([frame.subarray(headerSplit), encodeFrame(nextPayload)]));
74
+
75
+ expect(messages).to.deep.equal([payload, nextPayload]);
76
+ });
77
+
78
+ function encodeFrame(message: Uint8Array): Buffer {
79
+ return Buffer.concat([Buffer.from(pipe.encodeMessageStart(message)), Buffer.from(message)]);
80
+ }
81
+ });
@@ -57,6 +57,10 @@ export class BinaryMessagePipe implements Disposable {
57
57
  }
58
58
 
59
59
  protected handleNewMessage(chunk: Uint8Array): void {
60
+ if (this.cachedMessageData.partialMessageStart) {
61
+ chunk = Buffer.concat([this.cachedMessageData.partialMessageStart, chunk]);
62
+ this.cachedMessageData.partialMessageStart = undefined;
63
+ }
60
64
  if (chunk.byteLength < this.messageStartByteLength) {
61
65
  // The chunk only contains a part of the encoded message start
62
66
  this.cachedMessageData.partialMessageStart = chunk;
@@ -137,10 +141,7 @@ export class BinaryMessagePipe implements Disposable {
137
141
  * @throws An error if the message start can not be read successfully.
138
142
  */
139
143
  protected readMessageStart(chunk: Uint8Array): number {
140
- const messageData = this.cachedMessageData.partialMessageStart ? Buffer.concat([this.cachedMessageData.partialMessageStart, chunk]) : chunk;
141
- this.cachedMessageData.partialMessageStart = undefined;
142
-
143
- const reader = new Uint8ArrayReadBuffer(messageData);
144
+ const reader = new Uint8ArrayReadBuffer(chunk);
144
145
  const identifier = reader.readString();
145
146
 
146
147
  if (identifier !== BinaryMessagePipe.MESSAGE_START_IDENTIFIER) {