@theia/core 1.20.0 → 1.21.0-next.11
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 +3 -3
- package/lib/browser/common-frontend-contribution.d.ts +1 -13
- package/lib/browser/common-frontend-contribution.d.ts.map +1 -1
- package/lib/browser/common-frontend-contribution.js +86 -127
- package/lib/browser/common-frontend-contribution.js.map +1 -1
- package/lib/browser/frontend-application-module.d.ts.map +1 -1
- package/lib/browser/frontend-application-module.js +6 -5
- package/lib/browser/frontend-application-module.js.map +1 -1
- package/lib/browser/shell/application-shell.d.ts +7 -7
- package/lib/browser/shell/application-shell.d.ts.map +1 -1
- package/lib/browser/shell/application-shell.js +10 -17
- package/lib/browser/shell/application-shell.js.map +1 -1
- package/lib/browser/shell/current-widget-command-adapter.d.ts +39 -0
- package/lib/browser/shell/current-widget-command-adapter.d.ts.map +1 -0
- package/lib/browser/shell/current-widget-command-adapter.js +42 -0
- package/lib/browser/shell/current-widget-command-adapter.js.map +1 -0
- package/lib/browser/shell/tab-bars.d.ts +8 -2
- package/lib/browser/shell/tab-bars.d.ts.map +1 -1
- package/lib/browser/shell/tab-bars.js +30 -3
- package/lib/browser/shell/tab-bars.js.map +1 -1
- package/lib/common/promise-util.d.ts +3 -0
- package/lib/common/promise-util.d.ts.map +1 -1
- package/lib/common/promise-util.js +16 -1
- package/lib/common/promise-util.js.map +1 -1
- package/lib/common/promise-util.spec.d.ts +2 -0
- package/lib/common/promise-util.spec.d.ts.map +1 -0
- package/lib/common/promise-util.spec.js +42 -0
- package/lib/common/promise-util.spec.js.map +1 -0
- package/lib/electron-main/electron-main-application.d.ts +10 -0
- package/lib/electron-main/electron-main-application.d.ts.map +1 -1
- package/lib/electron-main/electron-main-application.js +13 -3
- package/lib/electron-main/electron-main-application.js.map +1 -1
- package/package.json +3 -3
- package/src/browser/common-frontend-contribution.ts +75 -132
- package/src/browser/frontend-application-module.ts +6 -5
- package/src/browser/shell/application-shell.ts +17 -22
- package/src/browser/shell/current-widget-command-adapter.ts +57 -0
- package/src/browser/shell/tab-bars.ts +30 -5
- package/src/common/promise-util.spec.ts +41 -0
- package/src/common/promise-util.ts +20 -1
- package/src/electron-main/electron-main-application.ts +24 -5
|
@@ -29,10 +29,10 @@ import { Saveable, SaveableWidget, SaveOptions } from '../saveable';
|
|
|
29
29
|
import { StatusBarImpl, StatusBarEntry, StatusBarAlignment } from '../status-bar/status-bar';
|
|
30
30
|
import { TheiaDockPanel, BOTTOM_AREA_ID, MAIN_AREA_ID } from './theia-dock-panel';
|
|
31
31
|
import { SidePanelHandler, SidePanel, SidePanelHandlerFactory } from './side-panel-handler';
|
|
32
|
-
import { TabBarRendererFactory,
|
|
32
|
+
import { TabBarRendererFactory, SHELL_TABBAR_CONTEXT_MENU, ScrollableTabBar, ToolbarAwareTabBar } from './tab-bars';
|
|
33
33
|
import { SplitPositionHandler, SplitPositionOptions } from './split-panels';
|
|
34
34
|
import { FrontendApplicationStateService } from '../frontend-application-state';
|
|
35
|
-
import { TabBarToolbarRegistry, TabBarToolbarFactory
|
|
35
|
+
import { TabBarToolbarRegistry, TabBarToolbarFactory } from './tab-bar-toolbar';
|
|
36
36
|
import { ContextKeyService } from '../context-key-service';
|
|
37
37
|
import { Emitter } from '../../common/event';
|
|
38
38
|
import { waitForRevealed, waitForClosed } from '../widgets';
|
|
@@ -82,9 +82,9 @@ export class DockPanelRenderer implements DockLayout.IRenderer {
|
|
|
82
82
|
readonly tabBarClasses: string[] = [];
|
|
83
83
|
|
|
84
84
|
constructor(
|
|
85
|
-
@inject(TabBarRendererFactory) protected readonly tabBarRendererFactory:
|
|
85
|
+
@inject(TabBarRendererFactory) protected readonly tabBarRendererFactory: TabBarRendererFactory,
|
|
86
86
|
@inject(TabBarToolbarRegistry) protected readonly tabBarToolbarRegistry: TabBarToolbarRegistry,
|
|
87
|
-
@inject(TabBarToolbarFactory) protected readonly tabBarToolbarFactory:
|
|
87
|
+
@inject(TabBarToolbarFactory) protected readonly tabBarToolbarFactory: TabBarToolbarFactory,
|
|
88
88
|
@inject(BreadcrumbsRendererFactory) protected readonly breadcrumbsRendererFactory: BreadcrumbsRendererFactory,
|
|
89
89
|
) { }
|
|
90
90
|
|
|
@@ -847,20 +847,15 @@ export class ApplicationShell extends Widget {
|
|
|
847
847
|
*/
|
|
848
848
|
findTitle(tabBar: TabBar<Widget>, event?: Event): Title<Widget> | undefined {
|
|
849
849
|
if (event?.target instanceof HTMLElement) {
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
return title;
|
|
858
|
-
}
|
|
859
|
-
title = tabBar.titles.find(t => t.label === tabNode!.title);
|
|
860
|
-
if (title) {
|
|
861
|
-
return title;
|
|
862
|
-
}
|
|
850
|
+
const tabNode = event.target;
|
|
851
|
+
|
|
852
|
+
const titleIndex = Array.from(tabBar.contentNode.getElementsByClassName('p-TabBar-tab'))
|
|
853
|
+
.findIndex(node => node.contains(tabNode));
|
|
854
|
+
|
|
855
|
+
if (titleIndex !== -1) {
|
|
856
|
+
return tabBar.titles[titleIndex];
|
|
863
857
|
}
|
|
858
|
+
|
|
864
859
|
}
|
|
865
860
|
return tabBar.currentTitle || undefined;
|
|
866
861
|
}
|
|
@@ -1832,18 +1827,18 @@ export class ApplicationShell extends Widget {
|
|
|
1832
1827
|
return undefined;
|
|
1833
1828
|
}
|
|
1834
1829
|
|
|
1835
|
-
canToggleMaximized(): boolean {
|
|
1836
|
-
const area =
|
|
1830
|
+
canToggleMaximized(widget: Widget | undefined = this.currentWidget): boolean {
|
|
1831
|
+
const area = widget && this.getAreaFor(widget);
|
|
1837
1832
|
return area === 'main' || area === 'bottom';
|
|
1838
1833
|
}
|
|
1839
1834
|
|
|
1840
|
-
toggleMaximized(): void {
|
|
1841
|
-
const area =
|
|
1835
|
+
toggleMaximized(widget: Widget | undefined = this.currentWidget): void {
|
|
1836
|
+
const area = widget && this.getAreaPanelFor(widget);
|
|
1842
1837
|
if (area instanceof TheiaDockPanel && (area === this.mainPanel || area === this.bottomPanel)) {
|
|
1843
1838
|
area.toggleMaximized();
|
|
1839
|
+
this.revealWidget(widget!.id);
|
|
1844
1840
|
}
|
|
1845
1841
|
}
|
|
1846
|
-
|
|
1847
1842
|
}
|
|
1848
1843
|
|
|
1849
1844
|
/**
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/********************************************************************************
|
|
2
|
+
* Copyright (C) 2021 Ericsson 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 WITH Classpath-exception-2.0
|
|
15
|
+
********************************************************************************/
|
|
16
|
+
|
|
17
|
+
import { CommandHandler } from '../../common';
|
|
18
|
+
import { TabBar, Title, Widget } from '../widgets';
|
|
19
|
+
import { ApplicationShell } from './application-shell';
|
|
20
|
+
|
|
21
|
+
type CurrentWidgetCommandAdapterBooleanCheck = (event: Event) => boolean;
|
|
22
|
+
type CurrentWidgetCommandHandlerBooleanCheck = (title: Title<Widget> | undefined, tabbar: TabBar<Widget> | undefined, event: Event) => boolean;
|
|
23
|
+
|
|
24
|
+
export interface TabBarContextMenuCommandHandler extends CommandHandler {
|
|
25
|
+
execute(title: Title<Widget> | undefined, tabbar: TabBar<Widget> | undefined, event: Event): unknown;
|
|
26
|
+
isEnabled?: CurrentWidgetCommandHandlerBooleanCheck;
|
|
27
|
+
isVisible?: CurrentWidgetCommandHandlerBooleanCheck;
|
|
28
|
+
isToggled?: CurrentWidgetCommandHandlerBooleanCheck;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Creates a command handler that acts on either the widget targeted by a DOM event or the current widget.
|
|
33
|
+
*/
|
|
34
|
+
export class CurrentWidgetCommandAdapter implements CommandHandler {
|
|
35
|
+
execute: (event: Event) => unknown;
|
|
36
|
+
isEnabled?: CurrentWidgetCommandAdapterBooleanCheck;
|
|
37
|
+
isVisible?: CurrentWidgetCommandAdapterBooleanCheck;
|
|
38
|
+
isToggled?: CurrentWidgetCommandAdapterBooleanCheck;
|
|
39
|
+
constructor(shell: ApplicationShell, handler: TabBarContextMenuCommandHandler) {
|
|
40
|
+
this.execute = (event: Event) => handler.execute(...this.transformArguments(shell, event));
|
|
41
|
+
if (handler.isEnabled) {
|
|
42
|
+
this.isEnabled = (event: Event) => !!handler.isEnabled?.(...this.transformArguments(shell, event));
|
|
43
|
+
}
|
|
44
|
+
if (handler.isVisible) {
|
|
45
|
+
this.isVisible = (event: Event) => !!handler.isVisible?.(...this.transformArguments(shell, event));
|
|
46
|
+
}
|
|
47
|
+
if (handler.isToggled) {
|
|
48
|
+
this.isToggled = (event: Event) => !!handler.isToggled?.(...this.transformArguments(shell, event));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
protected transformArguments(shell: ApplicationShell, event: Event): [Title<Widget> | undefined, TabBar<Widget> | undefined, Event] {
|
|
53
|
+
const tabBar = shell.findTabBar(event);
|
|
54
|
+
const title = tabBar && shell.findTitle(tabBar, event);
|
|
55
|
+
return [title, tabBar, event];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
import PerfectScrollbar from 'perfect-scrollbar';
|
|
18
18
|
import { TabBar, Title, Widget } from '@phosphor/widgets';
|
|
19
19
|
import { VirtualElement, h, VirtualDOM, ElementInlineStyle } from '@phosphor/virtualdom';
|
|
20
|
-
import { Disposable, DisposableCollection, MenuPath, notEmpty } from '../../common';
|
|
20
|
+
import { Disposable, DisposableCollection, MenuPath, notEmpty, SelectionService } from '../../common';
|
|
21
21
|
import { ContextMenuRenderer } from '../context-menu-renderer';
|
|
22
22
|
import { Signal, Slot } from '@phosphor/signaling';
|
|
23
23
|
import { Message, MessageLoop } from '@phosphor/messaging';
|
|
@@ -37,8 +37,14 @@ const HIDDEN_CONTENT_CLASS = 'theia-TabBar-hidden-content';
|
|
|
37
37
|
|
|
38
38
|
/** Menu path for tab bars used throughout the application shell. */
|
|
39
39
|
export const SHELL_TABBAR_CONTEXT_MENU: MenuPath = ['shell-tabbar-context-menu'];
|
|
40
|
+
export const SHELL_TABBAR_CONTEXT_CLOSE: MenuPath = [...SHELL_TABBAR_CONTEXT_MENU, '0_close'];
|
|
41
|
+
export const SHELL_TABBAR_CONTEXT_COPY: MenuPath = [...SHELL_TABBAR_CONTEXT_MENU, '1_copy'];
|
|
42
|
+
// Kept here in anticipation of tab pinning behavior implemented in tab-bars.ts
|
|
43
|
+
export const SHELL_TABBAR_CONTEXT_PIN: MenuPath = [...SHELL_TABBAR_CONTEXT_MENU, '4_pin'];
|
|
44
|
+
export const SHELL_TABBAR_CONTEXT_SPLIT: MenuPath = [...SHELL_TABBAR_CONTEXT_MENU, '5_split'];
|
|
40
45
|
|
|
41
46
|
export const TabBarRendererFactory = Symbol('TabBarRendererFactory');
|
|
47
|
+
export type TabBarRendererFactory = () => TabBarRenderer;
|
|
42
48
|
|
|
43
49
|
/**
|
|
44
50
|
* Size information of DOM elements used for rendering tabs in side bars.
|
|
@@ -66,7 +72,6 @@ export interface SideBarRenderData extends TabBar.IRenderData<Widget> {
|
|
|
66
72
|
* automatically.
|
|
67
73
|
*/
|
|
68
74
|
export class TabBarRenderer extends TabBar.Renderer {
|
|
69
|
-
|
|
70
75
|
/**
|
|
71
76
|
* The menu path used to render the context menu.
|
|
72
77
|
*/
|
|
@@ -80,7 +85,8 @@ export class TabBarRenderer extends TabBar.Renderer {
|
|
|
80
85
|
constructor(
|
|
81
86
|
protected readonly contextMenuRenderer?: ContextMenuRenderer,
|
|
82
87
|
protected readonly decoratorService?: TabBarDecoratorService,
|
|
83
|
-
protected readonly iconThemeService?: IconThemeService
|
|
88
|
+
protected readonly iconThemeService?: IconThemeService,
|
|
89
|
+
protected readonly selectionService?: SelectionService,
|
|
84
90
|
) {
|
|
85
91
|
super();
|
|
86
92
|
if (this.decoratorService) {
|
|
@@ -437,7 +443,27 @@ export class TabBarRenderer extends TabBar.Renderer {
|
|
|
437
443
|
if (this.contextMenuRenderer && this.contextMenuPath && event.currentTarget instanceof HTMLElement) {
|
|
438
444
|
event.stopPropagation();
|
|
439
445
|
event.preventDefault();
|
|
440
|
-
|
|
446
|
+
let widget: Widget | undefined = undefined;
|
|
447
|
+
if (this.tabBar) {
|
|
448
|
+
const titleIndex = Array.from(this.tabBar.contentNode.getElementsByClassName('p-TabBar-tab'))
|
|
449
|
+
.findIndex(node => node.contains(event.currentTarget as HTMLElement));
|
|
450
|
+
if (titleIndex !== -1) {
|
|
451
|
+
widget = this.tabBar.titles[titleIndex].owner;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const oldSelection = this.selectionService?.selection;
|
|
456
|
+
if (widget && this.selectionService) {
|
|
457
|
+
this.selectionService.selection = NavigatableWidget.is(widget) ? { uri: widget.getResourceUri() } : widget;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
this.contextMenuRenderer.render({
|
|
461
|
+
menuPath: this.contextMenuPath!,
|
|
462
|
+
anchor: event,
|
|
463
|
+
args: [event],
|
|
464
|
+
// We'd like to wait until the command triggered by the context menu has been run, but this should let it get through the preamble, at least.
|
|
465
|
+
onHide: () => setTimeout(() => { if (this.selectionService) { this.selectionService.selection = oldSelection; } })
|
|
466
|
+
});
|
|
441
467
|
}
|
|
442
468
|
};
|
|
443
469
|
|
|
@@ -452,7 +478,6 @@ export class TabBarRenderer extends TabBar.Renderer {
|
|
|
452
478
|
}
|
|
453
479
|
}
|
|
454
480
|
};
|
|
455
|
-
|
|
456
481
|
}
|
|
457
482
|
|
|
458
483
|
/**
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/********************************************************************************
|
|
2
|
+
* Copyright (C) 2021 Red Hat 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 WITH Classpath-exception-2.0
|
|
15
|
+
********************************************************************************/
|
|
16
|
+
import * as assert from 'assert';
|
|
17
|
+
import { waitForEvent } from './promise-util';
|
|
18
|
+
import { Emitter } from './event';
|
|
19
|
+
|
|
20
|
+
describe('promise-util', () => {
|
|
21
|
+
it('should time out', async () => {
|
|
22
|
+
const emitter = new Emitter<string>();
|
|
23
|
+
try {
|
|
24
|
+
await waitForEvent(emitter.event, 1000);
|
|
25
|
+
assert.fail('did not time out');
|
|
26
|
+
} catch (e) {
|
|
27
|
+
// OK
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('promise-util', () => {
|
|
32
|
+
it('should get event', async () => {
|
|
33
|
+
const emitter = new Emitter<string>();
|
|
34
|
+
setTimeout(() => {
|
|
35
|
+
emitter.fire('abcd');
|
|
36
|
+
}, 500);
|
|
37
|
+
assert.strictEqual(await waitForEvent(emitter.event, 1000), 'abcd');
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
});
|
|
@@ -14,7 +14,9 @@
|
|
|
14
14
|
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
|
15
15
|
********************************************************************************/
|
|
16
16
|
|
|
17
|
-
import {
|
|
17
|
+
import { Disposable } from './disposable';
|
|
18
|
+
import { Event } from './event';
|
|
19
|
+
import { CancellationToken, CancellationError, cancelled } from './cancellation';
|
|
18
20
|
|
|
19
21
|
/**
|
|
20
22
|
* Simple implementation of the deferred pattern.
|
|
@@ -90,3 +92,20 @@ export function delay<T>(ms: number): (value: T) => Promise<T> {
|
|
|
90
92
|
export async function wait(ms: number): Promise<void> {
|
|
91
93
|
await delay(ms)(undefined);
|
|
92
94
|
}
|
|
95
|
+
|
|
96
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
97
|
+
export function waitForEvent<T>(event: Event<T>, ms: number, thisArg?: any, disposables?: Disposable[]): Promise<T> {
|
|
98
|
+
return new Promise<T>((resolve, reject) => {
|
|
99
|
+
const registration = setTimeout(() => {
|
|
100
|
+
listener.dispose();
|
|
101
|
+
reject(new CancellationError());
|
|
102
|
+
}, ms);
|
|
103
|
+
|
|
104
|
+
const listener = event((evt: T) => {
|
|
105
|
+
clearTimeout(registration);
|
|
106
|
+
listener.dispose();
|
|
107
|
+
resolve(evt);
|
|
108
|
+
}, thisArg, disposables);
|
|
109
|
+
|
|
110
|
+
});
|
|
111
|
+
}
|
|
@@ -42,6 +42,12 @@ const createYargs: (argv?: string[], cwd?: string) => Argv = require('yargs/yarg
|
|
|
42
42
|
export interface TheiaBrowserWindowOptions extends BrowserWindowConstructorOptions {
|
|
43
43
|
isMaximized?: boolean;
|
|
44
44
|
isFullScreen?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Represents the complete screen layout for all available displays.
|
|
47
|
+
* This field is used to determine if the layout was updated since the electron window was last opened,
|
|
48
|
+
* in which case we want to invalidate the stored options and use the default options instead.
|
|
49
|
+
*/
|
|
50
|
+
screenLayout?: string;
|
|
45
51
|
}
|
|
46
52
|
|
|
47
53
|
/**
|
|
@@ -254,11 +260,14 @@ export class ElectronMainApplication {
|
|
|
254
260
|
}
|
|
255
261
|
|
|
256
262
|
async getLastWindowOptions(): Promise<TheiaBrowserWindowOptions> {
|
|
257
|
-
const
|
|
263
|
+
const previousWindowState: TheiaBrowserWindowOptions | undefined = this.electronStore.get('windowstate');
|
|
264
|
+
const windowState = previousWindowState?.screenLayout === this.getCurrentScreenLayout()
|
|
265
|
+
? previousWindowState
|
|
266
|
+
: this.getDefaultTheiaWindowOptions();
|
|
258
267
|
return {
|
|
259
268
|
frame: this.useNativeWindowFrame,
|
|
260
|
-
...
|
|
261
|
-
...
|
|
269
|
+
...this.getDefaultOptions(),
|
|
270
|
+
...windowState
|
|
262
271
|
};
|
|
263
272
|
}
|
|
264
273
|
|
|
@@ -403,13 +412,23 @@ export class ElectronMainApplication {
|
|
|
403
412
|
height: bounds.height,
|
|
404
413
|
x: bounds.x,
|
|
405
414
|
y: bounds.y,
|
|
406
|
-
frame: this.useNativeWindowFrame
|
|
407
|
-
|
|
415
|
+
frame: this.useNativeWindowFrame,
|
|
416
|
+
screenLayout: this.getCurrentScreenLayout(),
|
|
417
|
+
} as TheiaBrowserWindowOptions);
|
|
408
418
|
} catch (e) {
|
|
409
419
|
console.error('Error while saving window state:', e);
|
|
410
420
|
}
|
|
411
421
|
}
|
|
412
422
|
|
|
423
|
+
/**
|
|
424
|
+
* Return a string unique to the current display layout.
|
|
425
|
+
*/
|
|
426
|
+
protected getCurrentScreenLayout(): string {
|
|
427
|
+
return screen.getAllDisplays().map(
|
|
428
|
+
display => `${display.bounds.x}:${display.bounds.y}:${display.bounds.width}:${display.bounds.height}`
|
|
429
|
+
).sort().join('-');
|
|
430
|
+
}
|
|
431
|
+
|
|
413
432
|
/**
|
|
414
433
|
* Catch certain keybindings to prevent reloading the window using keyboard shortcuts.
|
|
415
434
|
*/
|