@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.
- package/README.md +5 -5
- package/lib/browser/catalog.json +207 -22
- package/lib/browser/color-application-contribution.d.ts +6 -0
- package/lib/browser/color-application-contribution.d.ts.map +1 -1
- package/lib/browser/color-application-contribution.js +15 -4
- package/lib/browser/color-application-contribution.js.map +1 -1
- package/lib/browser/color-application-contribution.spec.d.ts +2 -0
- package/lib/browser/color-application-contribution.spec.d.ts.map +1 -0
- package/lib/browser/color-application-contribution.spec.js +96 -0
- package/lib/browser/color-application-contribution.spec.js.map +1 -0
- package/lib/browser/color-registry.d.ts +6 -0
- package/lib/browser/color-registry.d.ts.map +1 -1
- package/lib/browser/color-registry.js +12 -0
- package/lib/browser/color-registry.js.map +1 -1
- package/lib/browser/tree/tree-selection-state.d.ts +23 -1
- package/lib/browser/tree/tree-selection-state.d.ts.map +1 -1
- package/lib/browser/tree/tree-selection-state.js +23 -1
- package/lib/browser/tree/tree-selection-state.js.map +1 -1
- package/lib/browser/tree/tree-widget.d.ts +28 -0
- package/lib/browser/tree/tree-widget.d.ts.map +1 -1
- package/lib/browser/tree/tree-widget.js +59 -3
- package/lib/browser/tree/tree-widget.js.map +1 -1
- package/lib/browser/tree/tree-widget.spec.js +53 -3
- package/lib/browser/tree/tree-widget.spec.js.map +1 -1
- package/lib/browser/widgets/select-component.d.ts.map +1 -1
- package/lib/browser/widgets/select-component.js +6 -11
- package/lib/browser/widgets/select-component.js.map +1 -1
- package/lib/browser/widgets/select-component.spec.d.ts +2 -0
- package/lib/browser/widgets/select-component.spec.d.ts.map +1 -0
- package/lib/browser/widgets/select-component.spec.js +66 -0
- package/lib/browser/widgets/select-component.spec.js.map +1 -0
- package/lib/common/core-preferences.js +5 -5
- package/lib/common/core-preferences.js.map +1 -1
- package/lib/electron-browser/electron-clipboard-service.d.ts +1 -1
- package/lib/electron-browser/electron-clipboard-service.d.ts.map +1 -1
- package/lib/electron-browser/preload.js +1 -1
- package/lib/electron-browser/preload.js.map +1 -1
- package/lib/electron-common/electron-api.d.ts +1 -1
- package/lib/electron-common/electron-api.d.ts.map +1 -1
- package/lib/electron-main/electron-api-main.d.ts.map +1 -1
- package/lib/electron-main/electron-api-main.js +5 -4
- package/lib/electron-main/electron-api-main.js.map +1 -1
- package/lib/node/messaging/binary-message-pipe.d.ts.map +1 -1
- package/lib/node/messaging/binary-message-pipe.js +5 -3
- package/lib/node/messaging/binary-message-pipe.js.map +1 -1
- package/lib/node/messaging/binary-message-pipe.spec.d.ts +2 -0
- package/lib/node/messaging/binary-message-pipe.spec.d.ts.map +1 -0
- package/lib/node/messaging/binary-message-pipe.spec.js +71 -0
- package/lib/node/messaging/binary-message-pipe.spec.js.map +1 -0
- package/package.json +16 -16
- package/src/browser/color-application-contribution.spec.ts +108 -0
- package/src/browser/color-application-contribution.ts +16 -4
- package/src/browser/color-registry.ts +13 -0
- package/src/browser/style/hover-service.css +0 -2
- package/src/browser/tree/tree-selection-state.ts +40 -1
- package/src/browser/tree/tree-widget.spec.tsx +59 -3
- package/src/browser/tree/tree-widget.tsx +64 -2
- package/src/browser/widgets/select-component.spec.ts +77 -0
- package/src/browser/widgets/select-component.tsx +6 -10
- package/src/common/core-preferences.ts +5 -5
- package/src/electron-browser/electron-clipboard-service.ts +1 -1
- package/src/electron-browser/preload.ts +2 -2
- package/src/electron-common/electron-api.ts +1 -1
- package/src/electron-main/electron-api-main.ts +5 -4
- package/src/node/messaging/binary-message-pipe.spec.ts +81 -0
- 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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
278
|
-
|
|
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
|
|
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) {
|