@theia/core 1.71.0-next.3 → 1.71.0-next.34
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 +87 -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 +4 -1
- 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/message-rpc/channel.d.ts.map +1 -1
- package/lib/common/message-rpc/channel.js +8 -2
- package/lib/common/message-rpc/channel.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 +8 -5
- package/lib/node/messaging/websocket-frontend-connection-service.d.ts.map +1 -1
- package/lib/node/messaging/websocket-frontend-connection-service.js +17 -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 +2 -1
- 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/message-rpc/channel.ts +2 -0
- 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 +15 -7
- package/src/node/process-utils.ts +9 -1
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
import { inject, injectable, postConstruct } from 'inversify';
|
|
18
18
|
import * as React from 'react';
|
|
19
|
+
import { buttonKeyboardProps, isActivationKey } from '../../keyboard/keyboard-utils';
|
|
19
20
|
import { ContextKeyService } from '../../context-key-service';
|
|
20
21
|
import { CommandRegistry, Disposable, DisposableCollection, nls } from '../../../common';
|
|
21
22
|
import { Anchor, ContextMenuAccess, ContextMenuRenderer } from '../../context-menu-renderer';
|
|
@@ -150,7 +151,10 @@ export class TabBarToolbar extends ReactWidget {
|
|
|
150
151
|
|
|
151
152
|
protected renderMore(): React.ReactNode {
|
|
152
153
|
return !!this.more.size && <div key='__more__' className={TabBarToolbar.Styles.TAB_BAR_TOOLBAR_ITEM + ' enabled'}>
|
|
153
|
-
<div id='__more__' className={codicon('ellipsis', true)}
|
|
154
|
+
<div id='__more__' className={codicon('ellipsis', true)}
|
|
155
|
+
{...buttonKeyboardProps(nls.localizeByDefault('More Actions...'))}
|
|
156
|
+
onClick={this.showMoreContextMenu}
|
|
157
|
+
onKeyDown={this.handleMoreKeyDown}
|
|
154
158
|
title={nls.localizeByDefault('More Actions...')} />
|
|
155
159
|
</div>;
|
|
156
160
|
}
|
|
@@ -162,6 +166,15 @@ export class TabBarToolbar extends ReactWidget {
|
|
|
162
166
|
this.renderMoreContextMenu(anchor);
|
|
163
167
|
};
|
|
164
168
|
|
|
169
|
+
protected handleMoreKeyDown = (event: React.KeyboardEvent) => {
|
|
170
|
+
if (isActivationKey(event)) {
|
|
171
|
+
event.preventDefault();
|
|
172
|
+
event.stopPropagation();
|
|
173
|
+
const { left, bottom } = (event.currentTarget as HTMLElement).getBoundingClientRect();
|
|
174
|
+
this.renderMoreContextMenu({ x: left, y: bottom });
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
165
178
|
renderMoreContextMenu(anchor: Anchor): ContextMenuAccess {
|
|
166
179
|
const toDisposeOnHide = new DisposableCollection();
|
|
167
180
|
this.addClass('menu-open');
|
|
@@ -23,6 +23,7 @@ import { KeybindingRegistry } from '../../keybinding';
|
|
|
23
23
|
import { ACTION_ITEM } from '../../widgets';
|
|
24
24
|
import { TabBarToolbar } from './tab-bar-toolbar';
|
|
25
25
|
import * as React from 'react';
|
|
26
|
+
import { buttonKeyboardProps, isActivationKey } from '../../keyboard/keyboard-utils';
|
|
26
27
|
import { ActionMenuNode, GroupImpl, MenuNode } from '../../../common/menu';
|
|
27
28
|
|
|
28
29
|
export interface TabBarToolbarItem {
|
|
@@ -195,16 +196,24 @@ export class RenderedToolbarItemImpl extends AbstractToolbarItemImpl<RenderedToo
|
|
|
195
196
|
};
|
|
196
197
|
|
|
197
198
|
protected executeCommand(e: React.MouseEvent<HTMLElement>, widget: Widget): void {
|
|
199
|
+
this.doExecuteCommand(e, widget);
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
protected doExecuteCommand(e: React.SyntheticEvent<HTMLElement>, widget: Widget): void {
|
|
198
203
|
e.preventDefault();
|
|
199
204
|
e.stopPropagation();
|
|
200
|
-
|
|
201
205
|
if (!this.isEnabled(widget)) {
|
|
202
206
|
return;
|
|
203
207
|
}
|
|
204
|
-
|
|
205
208
|
if (this.action.command) {
|
|
206
209
|
this.commandRegistry.executeCommand(this.action.command, widget);
|
|
207
210
|
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
protected onKeyDownEvent = (e: React.KeyboardEvent<HTMLElement>, widget: Widget) => {
|
|
214
|
+
if (isActivationKey(e)) {
|
|
215
|
+
this.doExecuteCommand(e, widget);
|
|
216
|
+
}
|
|
208
217
|
};
|
|
209
218
|
|
|
210
219
|
protected renderItem(widget: Widget): React.ReactNode {
|
|
@@ -249,7 +258,9 @@ export class RenderedToolbarItemImpl extends AbstractToolbarItemImpl<RenderedToo
|
|
|
249
258
|
onMouseUp={this.onMouseUpEvent}
|
|
250
259
|
onMouseOut={this.onMouseUpEvent} >
|
|
251
260
|
<div id={this.action.id} className={classNames.join(' ')}
|
|
261
|
+
{...buttonKeyboardProps(tooltip)}
|
|
252
262
|
onClick={e => this.executeCommand(e, widget)}
|
|
263
|
+
onKeyDown={e => this.onKeyDownEvent(e, widget)}
|
|
253
264
|
title={tooltip} > {innerText}
|
|
254
265
|
</div>
|
|
255
266
|
</div>;
|
|
@@ -101,11 +101,13 @@
|
|
|
101
101
|
transition: opacity 0.15s;
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
.theia-Card:hover .theia-Card-footer-time
|
|
104
|
+
.theia-Card:hover .theia-Card-footer-time,
|
|
105
|
+
.theia-Card:focus-within .theia-Card-footer-time {
|
|
105
106
|
opacity: 0;
|
|
106
107
|
}
|
|
107
108
|
|
|
108
|
-
.theia-Card:hover .theia-Card-footer-actions
|
|
109
|
+
.theia-Card:hover .theia-Card-footer-actions,
|
|
110
|
+
.theia-Card:focus-within .theia-Card-footer-actions {
|
|
109
111
|
opacity: 1;
|
|
110
112
|
pointer-events: auto;
|
|
111
113
|
}
|
|
@@ -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();
|
|
@@ -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>();
|
|
@@ -94,7 +97,7 @@ export class WebsocketFrontendConnectionService implements FrontendConnectionSer
|
|
|
94
97
|
|
|
95
98
|
protected createConnection(socket: Socket, frontEndId: string): ReconnectableSocketChannel {
|
|
96
99
|
console.info(`creating connection for ${frontEndId}`);
|
|
97
|
-
const channel =
|
|
100
|
+
const channel = this.container.get(ReconnectableSocketChannel);
|
|
98
101
|
channel.connect(socket);
|
|
99
102
|
|
|
100
103
|
this.connectionsByFrontend.set(frontEndId, channel);
|
|
@@ -136,12 +139,17 @@ export class WebsocketFrontendConnectionService implements FrontendConnectionSer
|
|
|
136
139
|
}
|
|
137
140
|
}
|
|
138
141
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
142
|
+
@injectable()
|
|
143
|
+
export class ReconnectableSocketChannel extends AbstractChannel {
|
|
144
|
+
protected socket: Socket | undefined;
|
|
145
|
+
|
|
146
|
+
@inject(SocketWriteBuffer)
|
|
147
|
+
protected socketBuffer: SocketWriteBuffer;
|
|
148
|
+
|
|
149
|
+
protected disposables = new DisposableCollection();
|
|
143
150
|
|
|
144
151
|
connect(socket: Socket): void {
|
|
152
|
+
this.disposables.dispose();
|
|
145
153
|
this.disposables = new DisposableCollection();
|
|
146
154
|
this.socket = socket;
|
|
147
155
|
const errorHandler = (err: Error) => {
|
|
@@ -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 {
|