@theia/core 1.71.0-next.4 → 1.71.0-next.41
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 +13 -13
- package/i18n/nls.cs.json +4 -4
- package/i18n/nls.de.json +4 -4
- package/i18n/nls.es.json +4 -4
- package/i18n/nls.fr.json +4 -4
- package/i18n/nls.hu.json +4 -4
- package/i18n/nls.it.json +4 -4
- package/i18n/nls.ja.json +4 -4
- package/i18n/nls.ko.json +4 -4
- package/i18n/nls.pl.json +4 -4
- package/i18n/nls.pt-br.json +4 -4
- package/i18n/nls.ru.json +4 -4
- package/i18n/nls.tr.json +4 -4
- package/i18n/nls.zh-cn.json +4 -4
- package/i18n/nls.zh-tw.json +4 -4
- package/lib/browser/catalog.json +93 -5
- package/lib/browser/components/card.d.ts.map +1 -1
- package/lib/browser/components/card.js +11 -3
- package/lib/browser/components/card.js.map +1 -1
- package/lib/browser/keyboard/index.d.ts +1 -0
- package/lib/browser/keyboard/index.d.ts.map +1 -1
- package/lib/browser/keyboard/index.js +1 -0
- package/lib/browser/keyboard/index.js.map +1 -1
- package/lib/browser/keyboard/keyboard-utils.d.ts +17 -0
- package/lib/browser/keyboard/keyboard-utils.d.ts.map +1 -0
- package/lib/browser/keyboard/keyboard-utils.js +40 -0
- package/lib/browser/keyboard/keyboard-utils.js.map +1 -0
- package/lib/browser/messaging/messaging-frontend-module.d.ts.map +1 -1
- package/lib/browser/messaging/messaging-frontend-module.js +3 -0
- package/lib/browser/messaging/messaging-frontend-module.js.map +1 -1
- package/lib/browser/messaging/ws-connection-source.d.ts +2 -1
- package/lib/browser/messaging/ws-connection-source.d.ts.map +1 -1
- package/lib/browser/messaging/ws-connection-source.js +5 -2
- package/lib/browser/messaging/ws-connection-source.js.map +1 -1
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.d.ts +1 -0
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.d.ts.map +1 -1
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.js +10 -1
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.js.map +1 -1
- package/lib/browser/shell/tab-bar-toolbar/tab-toolbar-item.d.ts +2 -0
- package/lib/browser/shell/tab-bar-toolbar/tab-toolbar-item.d.ts.map +1 -1
- package/lib/browser/shell/tab-bar-toolbar/tab-toolbar-item.js +11 -2
- package/lib/browser/shell/tab-bar-toolbar/tab-toolbar-item.js.map +1 -1
- package/lib/common/event.d.ts +16 -0
- package/lib/common/event.d.ts.map +1 -1
- package/lib/common/event.js +20 -2
- package/lib/common/event.js.map +1 -1
- package/lib/common/event.spec.js +63 -0
- package/lib/common/event.spec.js.map +1 -1
- package/lib/common/glob.d.ts +2 -0
- package/lib/common/glob.d.ts.map +1 -1
- package/lib/common/glob.js +8 -7
- package/lib/common/glob.js.map +1 -1
- package/lib/common/message-rpc/channel.d.ts +1 -1
- package/lib/common/message-rpc/channel.d.ts.map +1 -1
- package/lib/common/message-rpc/channel.js +20 -12
- package/lib/common/message-rpc/channel.js.map +1 -1
- package/lib/common/message-rpc/channel.spec.d.ts.map +1 -1
- package/lib/common/message-rpc/channel.spec.js +94 -0
- package/lib/common/message-rpc/channel.spec.js.map +1 -1
- package/lib/common/message-rpc/rpc-protocol.d.ts.map +1 -1
- package/lib/common/message-rpc/rpc-protocol.js +13 -3
- package/lib/common/message-rpc/rpc-protocol.js.map +1 -1
- package/lib/common/message-rpc/uint8-array-message-buffer.d.ts.map +1 -1
- package/lib/common/message-rpc/uint8-array-message-buffer.js +1 -1
- package/lib/common/message-rpc/uint8-array-message-buffer.js.map +1 -1
- package/lib/common/messaging/index.d.ts +1 -0
- package/lib/common/messaging/index.d.ts.map +1 -1
- package/lib/common/messaging/index.js +1 -0
- package/lib/common/messaging/index.js.map +1 -1
- package/lib/common/messaging/socket-write-buffer.d.ts +4 -3
- package/lib/common/messaging/socket-write-buffer.d.ts.map +1 -1
- package/lib/common/messaging/socket-write-buffer.js +14 -4
- package/lib/common/messaging/socket-write-buffer.js.map +1 -1
- package/lib/common/preferences/index.d.ts +1 -0
- package/lib/common/preferences/index.d.ts.map +1 -1
- package/lib/common/preferences/index.js +1 -0
- package/lib/common/preferences/index.js.map +1 -1
- package/lib/common/preferences/preference-utils.d.ts +6 -0
- package/lib/common/preferences/preference-utils.d.ts.map +1 -0
- package/lib/common/preferences/preference-utils.js +29 -0
- package/lib/common/preferences/preference-utils.js.map +1 -0
- package/lib/common/resource.d.ts +2 -0
- package/lib/common/resource.d.ts.map +1 -1
- package/lib/common/resource.js +7 -3
- package/lib/common/resource.js.map +1 -1
- package/lib/electron-browser/menu/electron-main-menu-factory.d.ts.map +1 -1
- package/lib/electron-browser/menu/electron-main-menu-factory.js +5 -1
- package/lib/electron-browser/menu/electron-main-menu-factory.js.map +1 -1
- package/lib/electron-browser/messaging/electron-messaging-frontend-module.d.ts.map +1 -1
- package/lib/electron-browser/messaging/electron-messaging-frontend-module.js +3 -0
- package/lib/electron-browser/messaging/electron-messaging-frontend-module.js.map +1 -1
- package/lib/electron-main/electron-api-main.d.ts.map +1 -1
- package/lib/electron-main/electron-api-main.js +4 -2
- package/lib/electron-main/electron-api-main.js.map +1 -1
- package/lib/electron-main/theia-electron-window.d.ts.map +1 -1
- package/lib/electron-main/theia-electron-window.js +3 -0
- package/lib/electron-main/theia-electron-window.js.map +1 -1
- package/lib/node/messaging/index.d.ts +1 -0
- package/lib/node/messaging/index.d.ts.map +1 -1
- package/lib/node/messaging/index.js +1 -0
- package/lib/node/messaging/index.js.map +1 -1
- package/lib/node/messaging/messaging-backend-module.d.ts.map +1 -1
- package/lib/node/messaging/messaging-backend-module.js +4 -0
- package/lib/node/messaging/messaging-backend-module.js.map +1 -1
- package/lib/node/messaging/websocket-frontend-connection-service.d.ts +9 -5
- package/lib/node/messaging/websocket-frontend-connection-service.d.ts.map +1 -1
- package/lib/node/messaging/websocket-frontend-connection-service.js +21 -5
- package/lib/node/messaging/websocket-frontend-connection-service.js.map +1 -1
- package/lib/node/process-utils.d.ts.map +1 -1
- package/lib/node/process-utils.js +9 -1
- package/lib/node/process-utils.js.map +1 -1
- package/package.json +32 -32
- package/src/browser/components/card.tsx +13 -2
- package/src/browser/keyboard/index.ts +1 -0
- package/src/browser/keyboard/keyboard-utils.ts +37 -0
- package/src/browser/messaging/messaging-frontend-module.ts +3 -0
- package/src/browser/messaging/ws-connection-source.ts +3 -2
- package/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar.tsx +14 -1
- package/src/browser/shell/tab-bar-toolbar/tab-toolbar-item.tsx +13 -2
- package/src/browser/style/card.css +4 -2
- package/src/common/event.spec.ts +80 -0
- package/src/common/event.ts +31 -2
- package/src/common/glob.ts +2 -2
- package/src/common/message-rpc/channel.spec.ts +116 -0
- package/src/common/message-rpc/channel.ts +15 -11
- package/src/common/message-rpc/rpc-protocol.ts +12 -3
- package/src/common/message-rpc/uint8-array-message-buffer.ts +1 -1
- package/src/common/messaging/index.ts +1 -0
- package/src/common/messaging/socket-write-buffer.ts +10 -4
- package/src/common/preferences/index.ts +1 -0
- package/src/common/preferences/preference-utils.ts +28 -0
- package/src/common/resource.ts +8 -2
- package/src/electron-browser/menu/electron-main-menu-factory.ts +5 -1
- package/src/electron-browser/messaging/electron-messaging-frontend-module.ts +3 -0
- package/src/electron-main/electron-api-main.ts +4 -2
- package/src/electron-main/theia-electron-window.ts +3 -0
- package/src/node/messaging/index.ts +1 -0
- package/src/node/messaging/messaging-backend-module.ts +5 -1
- package/src/node/messaging/websocket-frontend-connection-service.ts +20 -7
- package/src/node/process-utils.ts +9 -1
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
15
|
// *****************************************************************************
|
|
16
16
|
|
|
17
|
+
import { injectable } from 'inversify';
|
|
17
18
|
import { Disposable, DisposableCollection } from '../disposable';
|
|
18
19
|
import { Emitter, Event } from '../event';
|
|
19
20
|
import { ReadBuffer, WriteBuffer } from './message-buffer';
|
|
@@ -71,6 +72,7 @@ export type MessageProvider = () => ReadBuffer;
|
|
|
71
72
|
* Reusable abstract {@link Channel} implementation that sets up
|
|
72
73
|
* the basic channel event listeners and offers a generic close method.
|
|
73
74
|
*/
|
|
75
|
+
@injectable()
|
|
74
76
|
export abstract class AbstractChannel implements Channel {
|
|
75
77
|
|
|
76
78
|
onCloseEmitter: Emitter<ChannelCloseEvent> = new Emitter();
|
|
@@ -152,7 +154,7 @@ export enum MessageTypes {
|
|
|
152
154
|
* messages and always in one go.
|
|
153
155
|
*/
|
|
154
156
|
export class ChannelMultiplexer implements Disposable {
|
|
155
|
-
|
|
157
|
+
private pendingOpen: Map<string, { resolve: (channel: ForwardingChannel) => void, reject: (err: Error) => void }> = new Map();
|
|
156
158
|
protected openChannels: Map<string, ForwardingChannel> = new Map();
|
|
157
159
|
|
|
158
160
|
protected readonly onOpenChannelEmitter = new Emitter<{ id: string, channel: Channel }>();
|
|
@@ -180,9 +182,11 @@ export class ChannelMultiplexer implements Disposable {
|
|
|
180
182
|
onUnderlyingChannelClose(event?: ChannelCloseEvent): void {
|
|
181
183
|
if (!this.toDispose.disposed) {
|
|
182
184
|
this.toDispose.push(Disposable.create(() => {
|
|
185
|
+
const reason = event?.reason ?? 'Multiplexer main channel has been closed from the remote side!';
|
|
186
|
+
this.pendingOpen.forEach(pending => pending.reject(new Error(reason)));
|
|
183
187
|
this.pendingOpen.clear();
|
|
184
188
|
this.openChannels.forEach(channel => {
|
|
185
|
-
channel.onCloseEmitter.fire(event ?? { reason
|
|
189
|
+
channel.onCloseEmitter.fire(event ?? { reason });
|
|
186
190
|
});
|
|
187
191
|
|
|
188
192
|
this.openChannels.clear();
|
|
@@ -212,13 +216,13 @@ export class ChannelMultiplexer implements Disposable {
|
|
|
212
216
|
}
|
|
213
217
|
|
|
214
218
|
protected handleAckOpen(id: string): void {
|
|
215
|
-
// edge case: both
|
|
216
|
-
const
|
|
217
|
-
if (
|
|
219
|
+
// edge case: both sides try to open a channel at the same time.
|
|
220
|
+
const pending = this.pendingOpen.get(id);
|
|
221
|
+
if (pending) {
|
|
218
222
|
const channel = this.createChannel(id);
|
|
219
223
|
this.pendingOpen.delete(id);
|
|
220
224
|
this.openChannels.set(id, channel);
|
|
221
|
-
resolve(channel);
|
|
225
|
+
pending.resolve(channel);
|
|
222
226
|
this.onOpenChannelEmitter.fire({ id, channel });
|
|
223
227
|
} else {
|
|
224
228
|
console.error(`not expecting ack-open on for ${id}`);
|
|
@@ -229,10 +233,10 @@ export class ChannelMultiplexer implements Disposable {
|
|
|
229
233
|
if (!this.openChannels.has(id)) {
|
|
230
234
|
const channel = this.createChannel(id);
|
|
231
235
|
this.openChannels.set(id, channel);
|
|
232
|
-
const
|
|
233
|
-
if (
|
|
234
|
-
// edge case: both
|
|
235
|
-
resolve(channel);
|
|
236
|
+
const pending = this.pendingOpen.get(id);
|
|
237
|
+
if (pending) {
|
|
238
|
+
// edge case: both sides try to open a channel at the same time.
|
|
239
|
+
pending.resolve(channel);
|
|
236
240
|
}
|
|
237
241
|
this.underlyingChannel.getWriteBuffer().writeUint8(MessageTypes.AckOpen).writeString(id).commit();
|
|
238
242
|
this.onOpenChannelEmitter.fire({ id, channel });
|
|
@@ -283,7 +287,7 @@ export class ChannelMultiplexer implements Disposable {
|
|
|
283
287
|
throw new Error(`Another channel with the id '${id}' is already open.`);
|
|
284
288
|
}
|
|
285
289
|
const result = new Promise<Channel>((resolve, reject) => {
|
|
286
|
-
this.pendingOpen.set(id, resolve);
|
|
290
|
+
this.pendingOpen.set(id, { resolve, reject });
|
|
287
291
|
});
|
|
288
292
|
this.underlyingChannel.getWriteBuffer().writeUint8(MessageTypes.Open).writeString(id).commit();
|
|
289
293
|
return result;
|
|
@@ -178,9 +178,18 @@ export class RpcProtocol {
|
|
|
178
178
|
const disposableWrapper = new DisposableWrapper();
|
|
179
179
|
this.pendingRequestCancellationEventListeners.set(id, disposableWrapper);
|
|
180
180
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
181
|
+
try {
|
|
182
|
+
const output = this.channel.getWriteBuffer();
|
|
183
|
+
this.encoder.request(output, id, method, args);
|
|
184
|
+
output.commit();
|
|
185
|
+
} catch (err) {
|
|
186
|
+
// The message could not be sent (e.g. write buffer overflow).
|
|
187
|
+
// Clean up the pending request and reject the promise.
|
|
188
|
+
this.pendingRequests.delete(id);
|
|
189
|
+
this.disposeCancellationEventListener(id);
|
|
190
|
+
reply.reject(err);
|
|
191
|
+
return reply.promise;
|
|
192
|
+
}
|
|
184
193
|
|
|
185
194
|
if (cancellationToken?.isCancellationRequested) {
|
|
186
195
|
this.sendCancel(id);
|
|
@@ -113,7 +113,7 @@ export class Uint8ArrayWriteBuffer implements WriteBuffer, Disposable {
|
|
|
113
113
|
return this;
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
private onCommitEmitter = new Emitter<Uint8Array>();
|
|
116
|
+
private onCommitEmitter = new Emitter<Uint8Array>({ errorHandling: 'propagate' });
|
|
117
117
|
get onCommit(): Event<Uint8Array> {
|
|
118
118
|
return this.onCommitEmitter.event;
|
|
119
119
|
}
|
|
@@ -14,13 +14,19 @@
|
|
|
14
14
|
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
15
|
// *****************************************************************************
|
|
16
16
|
|
|
17
|
+
import { injectable } from 'inversify';
|
|
17
18
|
import { WebSocket } from './web-socket-channel';
|
|
18
19
|
|
|
20
|
+
@injectable()
|
|
19
21
|
export class SocketWriteBuffer {
|
|
20
|
-
private static
|
|
22
|
+
private static readonly DEFAULT_DISCONNECTED_BUFFER_SIZE = 100 * 1024;
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
protected disconnectedBuffer: Uint8Array | undefined;
|
|
25
|
+
protected bufferWritePosition = 0;
|
|
26
|
+
|
|
27
|
+
protected get maxBufferSize(): number {
|
|
28
|
+
return SocketWriteBuffer.DEFAULT_DISCONNECTED_BUFFER_SIZE;
|
|
29
|
+
}
|
|
24
30
|
|
|
25
31
|
buffer(data: Uint8Array): void {
|
|
26
32
|
this.ensureWriteBuffer(data.byteLength);
|
|
@@ -30,7 +36,7 @@ export class SocketWriteBuffer {
|
|
|
30
36
|
|
|
31
37
|
protected ensureWriteBuffer(byteLength: number): void {
|
|
32
38
|
if (!this.disconnectedBuffer) {
|
|
33
|
-
this.disconnectedBuffer = new Uint8Array(
|
|
39
|
+
this.disconnectedBuffer = new Uint8Array(this.maxBufferSize);
|
|
34
40
|
this.bufferWritePosition = 0;
|
|
35
41
|
}
|
|
36
42
|
|
|
@@ -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
|
+
: '';
|
package/src/common/resource.ts
CHANGED
|
@@ -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
|
-
|
|
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);
|
|
@@ -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
|
|
|
@@ -140,6 +140,9 @@ export class TheiaElectronWindow {
|
|
|
140
140
|
|
|
141
141
|
protected async doCloseWindow(): Promise<void> {
|
|
142
142
|
this.closeIsConfirmed = true;
|
|
143
|
+
// Hide the window immediately so the user perceives an instant close.
|
|
144
|
+
// This is done after veto checks have passed to ensure save dialogs remain visible.
|
|
145
|
+
this._window.hide();
|
|
143
146
|
await TheiaRendererAPI.sendAboutToClose(this._window.webContents);
|
|
144
147
|
this._window.close();
|
|
145
148
|
}
|
|
@@ -24,8 +24,9 @@ import { MessagingListener, MessagingListenerContribution } from './messaging-li
|
|
|
24
24
|
import { FrontendConnectionService } from './frontend-connection-service';
|
|
25
25
|
import { BackendApplicationContribution } from '../backend-application';
|
|
26
26
|
import { connectionCloseServicePath } from '../../common/messaging/connection-management';
|
|
27
|
-
import { WebsocketFrontendConnectionService } from './websocket-frontend-connection-service';
|
|
27
|
+
import { ReconnectableSocketChannel, WebsocketFrontendConnectionService } from './websocket-frontend-connection-service';
|
|
28
28
|
import { WebsocketEndpoint } from './websocket-endpoint';
|
|
29
|
+
import { SocketWriteBuffer } from '../../common/messaging/socket-write-buffer';
|
|
29
30
|
|
|
30
31
|
export const messagingBackendModule = new ContainerModule(bind => {
|
|
31
32
|
bindRootContributionProvider(bind, ConnectionContainerModule);
|
|
@@ -36,6 +37,9 @@ export const messagingBackendModule = new ContainerModule(bind => {
|
|
|
36
37
|
bind(MessagingContainer).toDynamicValue(({ container }) => container).inSingletonScope();
|
|
37
38
|
bind(WebsocketEndpoint).toSelf().inSingletonScope();
|
|
38
39
|
bind(BackendApplicationContribution).toService(WebsocketEndpoint);
|
|
40
|
+
// Transient: each connection gets its own private buffer and channel instances.
|
|
41
|
+
bind(SocketWriteBuffer).toSelf();
|
|
42
|
+
bind(ReconnectableSocketChannel).toSelf();
|
|
39
43
|
bind(WebsocketFrontendConnectionService).toSelf().inSingletonScope();
|
|
40
44
|
bind(FrontendConnectionService).toService(WebsocketFrontendConnectionService);
|
|
41
45
|
bind(MessagingListener).toSelf().inSingletonScope();
|
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
|
|
16
16
|
import { Channel, WriteBuffer } from '../../common/message-rpc';
|
|
17
17
|
import { MessagingService } from './messaging-service';
|
|
18
|
-
import { inject, injectable } from 'inversify';
|
|
18
|
+
import { inject, injectable, interfaces } from 'inversify';
|
|
19
19
|
import { Socket } from 'socket.io';
|
|
20
|
-
import { ConnectionHandlers } from './default-messaging-service';
|
|
20
|
+
import { ConnectionHandlers, MessagingContainer } from './default-messaging-service';
|
|
21
21
|
import { SocketWriteBuffer } from '../../common/messaging/socket-write-buffer';
|
|
22
22
|
import { FrontendConnectionService } from './frontend-connection-service';
|
|
23
23
|
import { AbstractChannel } from '../../common/message-rpc/channel';
|
|
@@ -33,6 +33,9 @@ export class WebsocketFrontendConnectionService implements FrontendConnectionSer
|
|
|
33
33
|
@inject(WebsocketEndpoint)
|
|
34
34
|
protected readonly websocketServer: WebsocketEndpoint;
|
|
35
35
|
|
|
36
|
+
@inject(MessagingContainer)
|
|
37
|
+
protected readonly container: interfaces.Container;
|
|
38
|
+
|
|
36
39
|
protected readonly wsHandlers = new ConnectionHandlers();
|
|
37
40
|
protected readonly connectionsByFrontend = new Map<string, ReconnectableSocketChannel>();
|
|
38
41
|
protected readonly closeTimeouts = new Map<string, NodeJS.Timeout>();
|
|
@@ -89,12 +92,13 @@ export class WebsocketFrontendConnectionService implements FrontendConnectionSer
|
|
|
89
92
|
this.closeTimeouts.delete(frontEndId);
|
|
90
93
|
|
|
91
94
|
connection.onCloseEmitter.fire({ reason });
|
|
95
|
+
connection.drainBuffer();
|
|
92
96
|
connection.close();
|
|
93
97
|
}
|
|
94
98
|
|
|
95
99
|
protected createConnection(socket: Socket, frontEndId: string): ReconnectableSocketChannel {
|
|
96
100
|
console.info(`creating connection for ${frontEndId}`);
|
|
97
|
-
const channel =
|
|
101
|
+
const channel = this.container.get(ReconnectableSocketChannel);
|
|
98
102
|
channel.connect(socket);
|
|
99
103
|
|
|
100
104
|
this.connectionsByFrontend.set(frontEndId, channel);
|
|
@@ -136,12 +140,17 @@ export class WebsocketFrontendConnectionService implements FrontendConnectionSer
|
|
|
136
140
|
}
|
|
137
141
|
}
|
|
138
142
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
+
@injectable()
|
|
144
|
+
export class ReconnectableSocketChannel extends AbstractChannel {
|
|
145
|
+
protected socket: Socket | undefined;
|
|
146
|
+
|
|
147
|
+
@inject(SocketWriteBuffer)
|
|
148
|
+
protected socketBuffer: SocketWriteBuffer;
|
|
149
|
+
|
|
150
|
+
protected disposables = new DisposableCollection();
|
|
143
151
|
|
|
144
152
|
connect(socket: Socket): void {
|
|
153
|
+
this.disposables.dispose();
|
|
145
154
|
this.disposables = new DisposableCollection();
|
|
146
155
|
this.socket = socket;
|
|
147
156
|
const errorHandler = (err: Error) => {
|
|
@@ -171,6 +180,10 @@ class ReconnectableSocketChannel extends AbstractChannel {
|
|
|
171
180
|
this.socket = undefined;
|
|
172
181
|
}
|
|
173
182
|
|
|
183
|
+
drainBuffer(): void {
|
|
184
|
+
this.socketBuffer.drain();
|
|
185
|
+
}
|
|
186
|
+
|
|
174
187
|
override getWriteBuffer(): WriteBuffer {
|
|
175
188
|
const writeBuffer = new Uint8ArrayWriteBuffer();
|
|
176
189
|
writeBuffer.onCommit(data => {
|
|
@@ -32,7 +32,15 @@ export class ProcessUtils {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
protected winTerminateProcessTree(ppid: number): void {
|
|
35
|
-
|
|
35
|
+
const result = cp.spawnSync('taskkill.exe', ['/f', '/t', '/pid', ppid.toString(10)], { encoding: 'utf8' });
|
|
36
|
+
if (result.error) {
|
|
37
|
+
throw result.error;
|
|
38
|
+
}
|
|
39
|
+
// taskkill may exit with a non-zero code when some child processes have already exited.
|
|
40
|
+
// This is expected during shutdown — log but don't throw.
|
|
41
|
+
if (result.status !== 0) {
|
|
42
|
+
console.warn(`taskkill.exe exited with ${result.status} for PID ${ppid}. Output:\n${JSON.stringify(result.output)}`);
|
|
43
|
+
}
|
|
36
44
|
}
|
|
37
45
|
|
|
38
46
|
protected unixTerminateProcessTree(ppid: number): void {
|