@theia/core 1.40.1 → 1.41.0
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 +6 -6
- package/i18n/nls.cs.json +5 -1
- package/i18n/nls.de.json +5 -1
- package/i18n/nls.es.json +5 -1
- package/i18n/nls.fr.json +5 -1
- package/i18n/nls.hu.json +5 -1
- package/i18n/nls.it.json +5 -1
- package/i18n/nls.ja.json +5 -1
- package/i18n/nls.json +5 -1
- package/i18n/nls.pl.json +5 -1
- package/i18n/nls.pt-br.json +5 -1
- package/i18n/nls.pt-pt.json +5 -1
- package/i18n/nls.ru.json +5 -1
- package/i18n/nls.zh-cn.json +5 -1
- package/lib/browser/common-frontend-contribution.d.ts +11 -0
- package/lib/browser/common-frontend-contribution.d.ts.map +1 -1
- package/lib/browser/common-frontend-contribution.js +97 -14
- package/lib/browser/common-frontend-contribution.js.map +1 -1
- package/lib/browser/context-menu-renderer.d.ts +5 -0
- package/lib/browser/context-menu-renderer.d.ts.map +1 -1
- package/lib/browser/label-parser.d.ts +8 -0
- package/lib/browser/label-parser.d.ts.map +1 -1
- package/lib/browser/label-parser.js +14 -0
- package/lib/browser/label-parser.js.map +1 -1
- package/lib/browser/label-parser.spec.js +33 -0
- package/lib/browser/label-parser.spec.js.map +1 -1
- package/lib/browser/menu/browser-context-menu-renderer.d.ts +1 -1
- package/lib/browser/menu/browser-context-menu-renderer.d.ts.map +1 -1
- package/lib/browser/menu/browser-context-menu-renderer.js +2 -2
- package/lib/browser/menu/browser-context-menu-renderer.js.map +1 -1
- package/lib/browser/menu/browser-menu-plugin.d.ts +1 -1
- package/lib/browser/menu/browser-menu-plugin.d.ts.map +1 -1
- package/lib/browser/menu/browser-menu-plugin.js +2 -2
- package/lib/browser/menu/browser-menu-plugin.js.map +1 -1
- package/lib/browser/performance/frontend-stopwatch.d.ts.map +1 -1
- package/lib/browser/performance/frontend-stopwatch.js +6 -2
- package/lib/browser/performance/frontend-stopwatch.js.map +1 -1
- package/lib/browser/preferences/preference-contribution.d.ts +2 -0
- package/lib/browser/preferences/preference-contribution.d.ts.map +1 -1
- package/lib/browser/preferences/preference-contribution.js +48 -24
- package/lib/browser/preferences/preference-contribution.js.map +1 -1
- package/lib/browser/saveable.d.ts +15 -1
- package/lib/browser/saveable.d.ts.map +1 -1
- package/lib/browser/saveable.js +34 -1
- package/lib/browser/saveable.js.map +1 -1
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.d.ts +46 -1
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.d.ts.map +1 -1
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.js +87 -6
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.js.map +1 -1
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-types.d.ts +20 -2
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-types.d.ts.map +1 -1
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-types.js +28 -1
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-types.js.map +1 -1
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.d.ts +25 -1
- 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 +79 -7
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.js.map +1 -1
- package/lib/browser/shell/tab-bars.d.ts.map +1 -1
- package/lib/browser/shell/tab-bars.js +9 -1
- package/lib/browser/shell/tab-bars.js.map +1 -1
- package/lib/browser/tree/tree-model.d.ts +2 -0
- package/lib/browser/tree/tree-model.d.ts.map +1 -1
- package/lib/browser/tree/tree-model.js +6 -0
- package/lib/browser/tree/tree-model.js.map +1 -1
- package/lib/browser/tree/tree-widget.d.ts +18 -0
- package/lib/browser/tree/tree-widget.d.ts.map +1 -1
- package/lib/browser/tree/tree-widget.js +57 -0
- package/lib/browser/tree/tree-widget.js.map +1 -1
- package/lib/browser/tree/tree.d.ts +18 -0
- package/lib/browser/tree/tree.d.ts.map +1 -1
- package/lib/browser/tree/tree.js +6 -0
- package/lib/browser/tree/tree.js.map +1 -1
- package/lib/browser/widgets/enhanced-preview-widget.d.ts +7 -0
- package/lib/browser/widgets/enhanced-preview-widget.d.ts.map +1 -0
- package/lib/browser/widgets/enhanced-preview-widget.js +27 -0
- package/lib/browser/widgets/enhanced-preview-widget.js.map +1 -0
- package/lib/browser/window-contribution.js +1 -1
- package/lib/browser/window-contribution.js.map +1 -1
- package/lib/common/array-utils.d.ts +7 -0
- package/lib/common/array-utils.d.ts.map +1 -1
- package/lib/common/array-utils.js +21 -0
- package/lib/common/array-utils.js.map +1 -1
- package/lib/common/event.d.ts +5 -0
- package/lib/common/event.d.ts.map +1 -1
- package/lib/common/event.js +5 -1
- package/lib/common/event.js.map +1 -1
- package/lib/common/menu/menu-model-registry.d.ts +12 -1
- package/lib/common/menu/menu-model-registry.d.ts.map +1 -1
- package/lib/common/menu/menu-model-registry.js +46 -0
- package/lib/common/menu/menu-model-registry.js.map +1 -1
- package/lib/common/performance/measurement.d.ts +21 -0
- package/lib/common/performance/measurement.d.ts.map +1 -1
- package/lib/common/performance/stopwatch.d.ts +10 -2
- package/lib/common/performance/stopwatch.d.ts.map +1 -1
- package/lib/common/performance/stopwatch.js +34 -11
- package/lib/common/performance/stopwatch.js.map +1 -1
- package/lib/common/promise-util.d.ts +4 -0
- package/lib/common/promise-util.d.ts.map +1 -1
- package/lib/common/promise-util.js +11 -1
- package/lib/common/promise-util.js.map +1 -1
- package/lib/common/promise-util.spec.js +26 -12
- package/lib/common/promise-util.spec.js.map +1 -1
- package/lib/common/types.d.ts +4 -0
- package/lib/common/types.d.ts.map +1 -1
- package/lib/common/types.js +16 -1
- package/lib/common/types.js.map +1 -1
- package/lib/common/uri.d.ts +1 -0
- package/lib/common/uri.d.ts.map +1 -1
- package/lib/common/uri.js +3 -0
- package/lib/common/uri.js.map +1 -1
- package/lib/electron-browser/menu/electron-context-menu-renderer.js +2 -2
- package/lib/electron-browser/menu/electron-context-menu-renderer.js.map +1 -1
- package/lib/electron-browser/menu/electron-main-menu-factory.d.ts +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 +2 -2
- package/lib/electron-browser/menu/electron-main-menu-factory.js.map +1 -1
- package/lib/electron-main/electron-main-application.d.ts.map +1 -1
- package/lib/electron-main/electron-main-application.js +3 -0
- package/lib/electron-main/electron-main-application.js.map +1 -1
- package/lib/node/backend-application.d.ts +5 -1
- package/lib/node/backend-application.d.ts.map +1 -1
- package/lib/node/backend-application.js +28 -5
- package/lib/node/backend-application.js.map +1 -1
- package/lib/node/logger-cli-contribution.spec.js +1 -1
- package/lib/node/logger-cli-contribution.spec.js.map +1 -1
- package/lib/node/main.d.ts +2 -5
- package/lib/node/main.d.ts.map +1 -1
- package/lib/node/main.js.map +1 -1
- package/lib/node/performance/node-stopwatch.js +1 -1
- package/lib/node/performance/node-stopwatch.js.map +1 -1
- package/package.json +7 -7
- package/src/browser/common-frontend-contribution.ts +107 -17
- package/src/browser/context-menu-renderer.ts +5 -0
- package/src/browser/label-parser.spec.ts +38 -0
- package/src/browser/label-parser.ts +15 -0
- package/src/browser/menu/browser-context-menu-renderer.ts +2 -2
- package/src/browser/menu/browser-menu-plugin.ts +2 -2
- package/src/browser/performance/frontend-stopwatch.ts +5 -2
- package/src/browser/preferences/preference-contribution.ts +49 -24
- package/src/browser/saveable.ts +41 -2
- package/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.ts +94 -8
- package/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar-types.ts +28 -1
- package/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar.tsx +87 -8
- package/src/browser/shell/tab-bars.ts +8 -1
- package/src/browser/style/tabs.css +32 -2
- package/src/browser/tree/tree-model.ts +8 -0
- package/src/browser/tree/tree-widget.tsx +66 -0
- package/src/browser/tree/tree.ts +27 -0
- package/src/browser/widgets/enhanced-preview-widget.ts +27 -0
- package/src/browser/window-contribution.ts +1 -1
- package/src/common/array-utils.ts +20 -0
- package/src/common/event.ts +12 -2
- package/src/common/i18n/nls.metadata.json +5616 -5359
- package/src/common/menu/menu-model-registry.ts +50 -0
- package/src/common/performance/measurement.ts +26 -0
- package/src/common/performance/stopwatch.ts +38 -12
- package/src/common/promise-util.spec.ts +43 -12
- package/src/common/promise-util.ts +12 -0
- package/src/common/types.ts +17 -0
- package/src/common/uri.ts +4 -0
- package/src/electron-browser/menu/electron-context-menu-renderer.ts +2 -2
- package/src/electron-browser/menu/electron-main-menu-factory.ts +2 -2
- package/src/electron-main/electron-main-application.ts +3 -0
- package/src/node/backend-application.ts +30 -5
- package/src/node/logger-cli-contribution.spec.ts +1 -1
- package/src/node/main.ts +1 -6
- package/src/node/performance/node-stopwatch.ts +1 -1
|
@@ -17,11 +17,11 @@
|
|
|
17
17
|
import debounce = require('lodash.debounce');
|
|
18
18
|
import { inject, injectable, named } from 'inversify';
|
|
19
19
|
// eslint-disable-next-line max-len
|
|
20
|
-
import { CommandMenuNode, CommandRegistry, CompoundMenuNode, ContributionProvider, Disposable, DisposableCollection, Emitter, Event, MenuModelRegistry, MenuPath } from '../../../common';
|
|
20
|
+
import { CommandMenuNode, CommandRegistry, CompoundMenuNode, ContributionProvider, Disposable, DisposableCollection, Emitter, Event, MenuModelRegistry, MenuNode, MenuPath } from '../../../common';
|
|
21
21
|
import { ContextKeyService } from '../../context-key-service';
|
|
22
22
|
import { FrontendApplicationContribution } from '../../frontend-application';
|
|
23
23
|
import { Widget } from '../../widgets';
|
|
24
|
-
import { MenuDelegate, ReactTabBarToolbarItem, TabBarToolbarItem } from './tab-bar-toolbar-types';
|
|
24
|
+
import { AnyToolbarItem, ConditionalToolbarItem, MenuDelegate, MenuToolbarItem, ReactTabBarToolbarItem, TabBarToolbarItem } from './tab-bar-toolbar-types';
|
|
25
25
|
import { ToolbarMenuNodeWrapper } from './tab-bar-toolbar-menu-adapters';
|
|
26
26
|
|
|
27
27
|
/**
|
|
@@ -103,10 +103,7 @@ export class TabBarToolbarRegistry implements FrontendApplicationContribution {
|
|
|
103
103
|
}
|
|
104
104
|
const result: Array<TabBarToolbarItem | ReactTabBarToolbarItem> = [];
|
|
105
105
|
for (const item of this.items.values()) {
|
|
106
|
-
|
|
107
|
-
? this.commandRegistry.isVisible(item.command, widget)
|
|
108
|
-
: (!item.isVisible || item.isVisible(widget));
|
|
109
|
-
if (visible && (!item.when || this.contextKeyService.match(item.when, widget.node))) {
|
|
106
|
+
if (this.isItemVisible(item, widget)) {
|
|
110
107
|
result.push(item);
|
|
111
108
|
}
|
|
112
109
|
}
|
|
@@ -139,6 +136,83 @@ export class TabBarToolbarRegistry implements FrontendApplicationContribution {
|
|
|
139
136
|
return result;
|
|
140
137
|
}
|
|
141
138
|
|
|
139
|
+
/**
|
|
140
|
+
* Query whether a toolbar `item` should be shown in the toolbar.
|
|
141
|
+
* This implementation delegates to item-specific checks according to their type.
|
|
142
|
+
*
|
|
143
|
+
* @param item a menu toolbar item
|
|
144
|
+
* @param widget the widget that is updating the toolbar
|
|
145
|
+
* @returns `false` if the `item` should be suppressed, otherwise `true`
|
|
146
|
+
*/
|
|
147
|
+
protected isItemVisible(item: TabBarToolbarItem | ReactTabBarToolbarItem, widget: Widget): boolean {
|
|
148
|
+
if (TabBarToolbarItem.is(item) && item.command && !this.isTabBarToolbarItemVisible(item, widget)) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
if (MenuToolbarItem.is(item) && !this.isMenuToolbarItemVisible(item, widget)) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
if (AnyToolbarItem.isConditional(item) && !this.isConditionalItemVisible(item, widget)) {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
// The item is not vetoed. Accept it
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Query whether a conditional toolbar `item` should be shown in the toolbar.
|
|
163
|
+
* This implementation delegates to the `item`'s own intrinsic conditionality.
|
|
164
|
+
*
|
|
165
|
+
* @param item a menu toolbar item
|
|
166
|
+
* @param widget the widget that is updating the toolbar
|
|
167
|
+
* @returns `false` if the `item` should be suppressed, otherwise `true`
|
|
168
|
+
*/
|
|
169
|
+
protected isConditionalItemVisible(item: ConditionalToolbarItem, widget: Widget): boolean {
|
|
170
|
+
if (item.isVisible && !item.isVisible(widget)) {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
if (item.when && !this.contextKeyService.match(item.when, widget.node)) {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Query whether a tab-bar toolbar `item` that has a command should be shown in the toolbar.
|
|
181
|
+
* This implementation returns `false` if the `item`'s command is not visible in the
|
|
182
|
+
* `widget` according to the command registry.
|
|
183
|
+
*
|
|
184
|
+
* @param item a tab-bar toolbar item that has a non-empty `command`
|
|
185
|
+
* @param widget the widget that is updating the toolbar
|
|
186
|
+
* @returns `false` if the `item` should be suppressed, otherwise `true`
|
|
187
|
+
*/
|
|
188
|
+
protected isTabBarToolbarItemVisible(item: TabBarToolbarItem, widget: Widget): boolean {
|
|
189
|
+
return this.commandRegistry.isVisible(item.command, widget);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Query whether a menu toolbar `item` should be shown in the toolbar.
|
|
194
|
+
* This implementation returns `false` if the `item` does not have any actual menu to show.
|
|
195
|
+
*
|
|
196
|
+
* @param item a menu toolbar item
|
|
197
|
+
* @param widget the widget that is updating the toolbar
|
|
198
|
+
* @returns `false` if the `item` should be suppressed, otherwise `true`
|
|
199
|
+
*/
|
|
200
|
+
protected isMenuToolbarItemVisible(item: MenuToolbarItem, widget: Widget): boolean {
|
|
201
|
+
const menu = this.menuRegistry.getMenu(item.menuPath);
|
|
202
|
+
const isVisible: (node: MenuNode) => boolean = node =>
|
|
203
|
+
node.children?.length
|
|
204
|
+
// Either the node is a sub-menu that has some visible child ...
|
|
205
|
+
? node.children?.some(isVisible)
|
|
206
|
+
// ... or there is a command ...
|
|
207
|
+
: !!node.command
|
|
208
|
+
// ... that is visible ...
|
|
209
|
+
&& this.commandRegistry.isVisible(node.command, widget)
|
|
210
|
+
// ... and a "when" clause does not suppress the menu node.
|
|
211
|
+
&& (!node.when || this.contextKeyService.match(node.when, widget?.node));
|
|
212
|
+
|
|
213
|
+
return isVisible(menu);
|
|
214
|
+
}
|
|
215
|
+
|
|
142
216
|
unregisterItem(itemOrId: TabBarToolbarItem | ReactTabBarToolbarItem | string): void {
|
|
143
217
|
const id = typeof itemOrId === 'string' ? itemOrId : itemOrId.id;
|
|
144
218
|
if (this.items.delete(id)) {
|
|
@@ -147,7 +221,7 @@ export class TabBarToolbarRegistry implements FrontendApplicationContribution {
|
|
|
147
221
|
}
|
|
148
222
|
|
|
149
223
|
registerMenuDelegate(menuPath: MenuPath, when?: string | ((widget: Widget) => boolean)): Disposable {
|
|
150
|
-
const id =
|
|
224
|
+
const id = this.toElementId(menuPath);
|
|
151
225
|
if (!this.menuDelegates.has(id)) {
|
|
152
226
|
const isVisible: MenuDelegate['isVisible'] = !when
|
|
153
227
|
? yes
|
|
@@ -163,8 +237,20 @@ export class TabBarToolbarRegistry implements FrontendApplicationContribution {
|
|
|
163
237
|
}
|
|
164
238
|
|
|
165
239
|
unregisterMenuDelegate(menuPath: MenuPath): void {
|
|
166
|
-
if (this.menuDelegates.delete(
|
|
240
|
+
if (this.menuDelegates.delete(this.toElementId(menuPath))) {
|
|
167
241
|
this.fireOnDidChange();
|
|
168
242
|
}
|
|
169
243
|
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Generate a single ID string from a menu path that
|
|
247
|
+
* is likely to be unique amongst the items in the toolbar.
|
|
248
|
+
*
|
|
249
|
+
* @param menuPath a menubar path
|
|
250
|
+
* @returns a likely unique ID based on the path
|
|
251
|
+
*/
|
|
252
|
+
toElementId(menuPath: MenuPath): string {
|
|
253
|
+
return menuPath.join(menuDelegateSeparator);
|
|
254
|
+
}
|
|
255
|
+
|
|
170
256
|
}
|
|
@@ -83,7 +83,7 @@ export interface MenuToolbarItem {
|
|
|
83
83
|
menuPath: MenuPath;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
interface ConditionalToolbarItem {
|
|
86
|
+
export interface ConditionalToolbarItem {
|
|
87
87
|
/**
|
|
88
88
|
* https://code.visualstudio.com/docs/getstarted/keybindings#_when-clause-contexts
|
|
89
89
|
*/
|
|
@@ -130,6 +130,7 @@ export interface TabBarToolbarItem extends RegisteredToolbarItem,
|
|
|
130
130
|
RenderedToolbarItem,
|
|
131
131
|
Omit<ConditionalToolbarItem, 'isVisible'>,
|
|
132
132
|
Pick<InlineToolbarItemMetadata, 'priority'>,
|
|
133
|
+
Partial<MenuToolbarItem>,
|
|
133
134
|
Partial<MenuToolbarItemMetadata> { }
|
|
134
135
|
|
|
135
136
|
/**
|
|
@@ -174,7 +175,33 @@ export namespace TabBarToolbarItem {
|
|
|
174
175
|
}
|
|
175
176
|
|
|
176
177
|
export namespace MenuToolbarItem {
|
|
178
|
+
/**
|
|
179
|
+
* Type guard for a toolbar item that actually is a menu item, amongst
|
|
180
|
+
* the other kinds of item that it may also be.
|
|
181
|
+
*
|
|
182
|
+
* @param item a toolbar item
|
|
183
|
+
* @returns whether the `item` is a menu item
|
|
184
|
+
*/
|
|
185
|
+
export function is<T extends AnyToolbarItem>(item: T): item is T & MenuToolbarItem {
|
|
186
|
+
return Array.isArray(item.menuPath);
|
|
187
|
+
}
|
|
188
|
+
|
|
177
189
|
export function getMenuPath(item: AnyToolbarItem): MenuPath | undefined {
|
|
178
190
|
return Array.isArray(item.menuPath) ? item.menuPath : undefined;
|
|
179
191
|
}
|
|
180
192
|
}
|
|
193
|
+
|
|
194
|
+
export namespace AnyToolbarItem {
|
|
195
|
+
/**
|
|
196
|
+
* Type guard for a toolbar item that actually manifests any of the
|
|
197
|
+
* features of a conditional toolbar item.
|
|
198
|
+
*
|
|
199
|
+
* @param item a toolbar item
|
|
200
|
+
* @returns whether the `item` is a conditional item
|
|
201
|
+
*/
|
|
202
|
+
export function isConditional<T extends AnyToolbarItem>(item: T): item is T & ConditionalToolbarItem {
|
|
203
|
+
return 'isVisible' in item && typeof item.isVisible === 'function'
|
|
204
|
+
|| 'onDidChange' in item && typeof item.onDidChange === 'function'
|
|
205
|
+
|| 'when' in item && typeof item.when === 'string';
|
|
206
|
+
}
|
|
207
|
+
}
|
|
@@ -22,7 +22,7 @@ import { Anchor, ContextMenuAccess, ContextMenuRenderer } from '../../context-me
|
|
|
22
22
|
import { LabelIcon, LabelParser } from '../../label-parser';
|
|
23
23
|
import { ACTION_ITEM, codicon, ReactWidget, Widget } from '../../widgets';
|
|
24
24
|
import { TabBarToolbarRegistry } from './tab-bar-toolbar-registry';
|
|
25
|
-
import { AnyToolbarItem, ReactTabBarToolbarItem, TabBarDelegator, TabBarToolbarItem, TAB_BAR_TOOLBAR_CONTEXT_MENU } from './tab-bar-toolbar-types';
|
|
25
|
+
import { AnyToolbarItem, ReactTabBarToolbarItem, TabBarDelegator, TabBarToolbarItem, TAB_BAR_TOOLBAR_CONTEXT_MENU, MenuToolbarItem } from './tab-bar-toolbar-types';
|
|
26
26
|
import { KeybindingRegistry } from '../..//keybinding';
|
|
27
27
|
|
|
28
28
|
/**
|
|
@@ -33,6 +33,11 @@ export interface TabBarToolbarFactory {
|
|
|
33
33
|
(): TabBarToolbar;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Class name indicating rendering of a toolbar item without an icon but instead with a text label.
|
|
38
|
+
*/
|
|
39
|
+
const NO_ICON_CLASS = 'no-icon';
|
|
40
|
+
|
|
36
41
|
/**
|
|
37
42
|
* Tab-bar toolbar widget representing the active [tab-bar toolbar items](TabBarToolbarItem).
|
|
38
43
|
*/
|
|
@@ -149,7 +154,9 @@ export class TabBarToolbar extends ReactWidget {
|
|
|
149
154
|
this.keybindingContextKeys.clear();
|
|
150
155
|
return <React.Fragment>
|
|
151
156
|
{this.renderMore()}
|
|
152
|
-
{[...this.inline.values()].map(item => TabBarToolbarItem.is(item)
|
|
157
|
+
{[...this.inline.values()].map(item => TabBarToolbarItem.is(item)
|
|
158
|
+
? (MenuToolbarItem.is(item) && !item.command ? this.renderMenuItem(item) : this.renderItem(item))
|
|
159
|
+
: item.render(this.current))}
|
|
153
160
|
</React.Fragment>;
|
|
154
161
|
}
|
|
155
162
|
|
|
@@ -176,8 +183,12 @@ export class TabBarToolbar extends ReactWidget {
|
|
|
176
183
|
protected renderItem(item: AnyToolbarItem): React.ReactNode {
|
|
177
184
|
let innerText = '';
|
|
178
185
|
const classNames = [];
|
|
179
|
-
|
|
180
|
-
|
|
186
|
+
const command = item.command ? this.commands.getCommand(item.command) : undefined;
|
|
187
|
+
// Fall back to the item ID in extremis so there is _something_ to render in the
|
|
188
|
+
// case that there is neither an icon nor a title
|
|
189
|
+
const itemText = item.text || command?.label || command?.id || item.id;
|
|
190
|
+
if (itemText) {
|
|
191
|
+
for (const labelPart of this.labelParser.parse(itemText)) {
|
|
181
192
|
if (LabelIcon.is(labelPart)) {
|
|
182
193
|
const className = `fa fa-${labelPart.name}${labelPart.animation ? ' fa-' + labelPart.animation : ''}`;
|
|
183
194
|
classNames.push(...className.split(' '));
|
|
@@ -186,13 +197,23 @@ export class TabBarToolbar extends ReactWidget {
|
|
|
186
197
|
}
|
|
187
198
|
}
|
|
188
199
|
}
|
|
189
|
-
const
|
|
190
|
-
let iconClass = (typeof item.icon === 'function' && item.icon()) || item.icon as string || (command && command.iconClass);
|
|
200
|
+
const iconClass = (typeof item.icon === 'function' && item.icon()) || item.icon as string || (command && command.iconClass);
|
|
191
201
|
if (iconClass) {
|
|
192
|
-
iconClass += ` ${ACTION_ITEM}`;
|
|
193
202
|
classNames.push(iconClass);
|
|
194
203
|
}
|
|
195
|
-
const
|
|
204
|
+
const tooltipText = item.tooltip || (command && command.label) || '';
|
|
205
|
+
const tooltip = `${this.labelParser.stripIcons(tooltipText)}${this.resolveKeybindingForCommand(command?.id)}`;
|
|
206
|
+
|
|
207
|
+
// Only present text if there is no icon
|
|
208
|
+
if (classNames.length) {
|
|
209
|
+
innerText = '';
|
|
210
|
+
} else if (innerText) {
|
|
211
|
+
// Make room for the label text
|
|
212
|
+
classNames.push(NO_ICON_CLASS);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// In any case, this is an action item, with or without icon.
|
|
216
|
+
classNames.push(ACTION_ITEM);
|
|
196
217
|
|
|
197
218
|
const toolbarItemClassNames = this.getToolbarItemClassNames(item);
|
|
198
219
|
return <div key={item.id}
|
|
@@ -224,6 +245,10 @@ export class TabBarToolbar extends ReactWidget {
|
|
|
224
245
|
if (this.commandIsToggled(item.command)) {
|
|
225
246
|
classNames.push('toggled');
|
|
226
247
|
}
|
|
248
|
+
} else {
|
|
249
|
+
if (this.isEnabled(item)) {
|
|
250
|
+
classNames.push('enabled');
|
|
251
|
+
}
|
|
227
252
|
}
|
|
228
253
|
return classNames;
|
|
229
254
|
}
|
|
@@ -281,6 +306,60 @@ export class TabBarToolbar extends ReactWidget {
|
|
|
281
306
|
args: [this.current],
|
|
282
307
|
anchor,
|
|
283
308
|
context: this.current?.node,
|
|
309
|
+
onHide: () => toDisposeOnHide.dispose(),
|
|
310
|
+
skipSingleRootNode: true,
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Renders a toolbar item that is a menu, presenting it as a button with a little
|
|
316
|
+
* chevron decoration that pops up a floating menu when clicked.
|
|
317
|
+
*
|
|
318
|
+
* @param item a toolbar item that is a menu item
|
|
319
|
+
* @returns the rendered toolbar item
|
|
320
|
+
*/
|
|
321
|
+
protected renderMenuItem(item: TabBarToolbarItem & MenuToolbarItem): React.ReactNode {
|
|
322
|
+
const icon = typeof item.icon === 'function' ? item.icon() : item.icon ?? 'ellipsis';
|
|
323
|
+
return <div key={item.id}
|
|
324
|
+
className={TabBarToolbar.Styles.TAB_BAR_TOOLBAR_ITEM + ' enabled menu'}
|
|
325
|
+
onClick={this.showPopupMenu.bind(this, item.menuPath)}>
|
|
326
|
+
<div id={item.id} className={codicon(icon, true)}
|
|
327
|
+
title={item.text} />
|
|
328
|
+
<div className={codicon('chevron-down') + ' chevron'} />
|
|
329
|
+
</div >;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Presents the menu to popup on the `event` that is the clicking of
|
|
334
|
+
* a menu toolbar item.
|
|
335
|
+
*
|
|
336
|
+
* @param menuPath the path of the registered menu to show
|
|
337
|
+
* @param event the mouse event triggering the menu
|
|
338
|
+
*/
|
|
339
|
+
protected showPopupMenu = (menuPath: MenuPath, event: React.MouseEvent) => {
|
|
340
|
+
event.stopPropagation();
|
|
341
|
+
event.preventDefault();
|
|
342
|
+
const anchor = this.toAnchor(event);
|
|
343
|
+
this.renderPopupMenu(menuPath, anchor);
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Renders the menu popped up on a menu toolbar item.
|
|
348
|
+
*
|
|
349
|
+
* @param menuPath the path of the registered menu to render
|
|
350
|
+
* @param anchor a description of where to render the menu
|
|
351
|
+
* @returns platform-specific access to the rendered context menu
|
|
352
|
+
*/
|
|
353
|
+
protected renderPopupMenu(menuPath: MenuPath, anchor: Anchor): ContextMenuAccess {
|
|
354
|
+
const toDisposeOnHide = new DisposableCollection();
|
|
355
|
+
this.addClass('menu-open');
|
|
356
|
+
toDisposeOnHide.push(Disposable.create(() => this.removeClass('menu-open')));
|
|
357
|
+
|
|
358
|
+
return this.contextMenuRenderer.render({
|
|
359
|
+
menuPath,
|
|
360
|
+
args: [this.current],
|
|
361
|
+
anchor,
|
|
362
|
+
context: this.current?.node,
|
|
284
363
|
onHide: () => toDisposeOnHide.dispose()
|
|
285
364
|
});
|
|
286
365
|
}
|
|
@@ -38,6 +38,7 @@ import { Root, createRoot } from 'react-dom/client';
|
|
|
38
38
|
import { SelectComponent } from '../widgets/select-component';
|
|
39
39
|
import { createElement } from 'react';
|
|
40
40
|
import { PreviewableWidget } from '../widgets/previewable-widget';
|
|
41
|
+
import { EnhancedPreviewWidget } from '../widgets/enhanced-preview-widget';
|
|
41
42
|
|
|
42
43
|
/** The class name added to hidden content nodes, which are required to render vertical side bars. */
|
|
43
44
|
const HIDDEN_CONTENT_CLASS = 'theia-TabBar-hidden-content';
|
|
@@ -504,7 +505,13 @@ export class TabBarRenderer extends TabBar.Renderer {
|
|
|
504
505
|
labelElement.classList.add('theia-horizontal-tabBar-hover-title');
|
|
505
506
|
labelElement.textContent = title.label;
|
|
506
507
|
hoverBox.append(labelElement);
|
|
507
|
-
|
|
508
|
+
const widget = title.owner;
|
|
509
|
+
if (EnhancedPreviewWidget.is(widget)) {
|
|
510
|
+
const enhancedPreviewNode = widget.getEnhancedPreviewNode();
|
|
511
|
+
if (enhancedPreviewNode) {
|
|
512
|
+
hoverBox.appendChild(enhancedPreviewNode);
|
|
513
|
+
}
|
|
514
|
+
} else if (title.caption) {
|
|
508
515
|
const captionElement = document.createElement('p');
|
|
509
516
|
captionElement.classList.add('theia-horizontal-tabBar-hover-caption');
|
|
510
517
|
captionElement.textContent = title.caption;
|
|
@@ -254,8 +254,8 @@
|
|
|
254
254
|
}
|
|
255
255
|
|
|
256
256
|
.p-TabBar.theia-app-centers
|
|
257
|
-
.p-TabBar-tab
|
|
258
|
-
> .p-TabBar-tabCloseIcon {
|
|
257
|
+
.p-TabBar-tab.p-mod-closable
|
|
258
|
+
> .p-TabBar-tabCloseIcon:hover {
|
|
259
259
|
border-radius: 5px;
|
|
260
260
|
background-color: rgba(50%, 50%, 50%, 0.2);
|
|
261
261
|
}
|
|
@@ -459,6 +459,10 @@
|
|
|
459
459
|
cursor: pointer;
|
|
460
460
|
}
|
|
461
461
|
|
|
462
|
+
.p-TabBar-toolbar .item.enabled .action-label::before {
|
|
463
|
+
display: flex;
|
|
464
|
+
}
|
|
465
|
+
|
|
462
466
|
.p-TabBar-toolbar :not(.item.enabled) .action-label {
|
|
463
467
|
background: transparent;
|
|
464
468
|
cursor: default;
|
|
@@ -476,6 +480,11 @@
|
|
|
476
480
|
line-height: 18px;
|
|
477
481
|
}
|
|
478
482
|
|
|
483
|
+
.p-TabBar-toolbar .item > div.no-icon {
|
|
484
|
+
/* Make room for a text label instead of an icon. */
|
|
485
|
+
width: 100%;
|
|
486
|
+
}
|
|
487
|
+
|
|
479
488
|
.p-TabBar-toolbar .item .collapse-all {
|
|
480
489
|
background: var(--theia-icon-collapse-all) no-repeat;
|
|
481
490
|
}
|
|
@@ -496,6 +505,27 @@
|
|
|
496
505
|
background: var(--theia-icon-close) no-repeat;
|
|
497
506
|
}
|
|
498
507
|
|
|
508
|
+
/** Configure layout of a toolbar item that shows a pop-up menu. */
|
|
509
|
+
.p-TabBar-toolbar .item.menu {
|
|
510
|
+
display: grid;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/** The elements of the item that shows a pop-up menu are stack atop one other. */
|
|
514
|
+
.p-TabBar-toolbar .item.menu > div {
|
|
515
|
+
grid-area: 1 / 1;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* The chevron for the pop-up menu indication is shrunk and
|
|
520
|
+
* stuffed in the bottom-right corner.
|
|
521
|
+
*/
|
|
522
|
+
.p-TabBar-toolbar .item.menu > .chevron {
|
|
523
|
+
scale: 50%;
|
|
524
|
+
align-self: end;
|
|
525
|
+
justify-self: end;
|
|
526
|
+
translate: 5px 3px;
|
|
527
|
+
}
|
|
528
|
+
|
|
499
529
|
#theia-main-content-panel
|
|
500
530
|
.p-TabBar:not(.theia-tabBar-active)
|
|
501
531
|
.p-TabBar-toolbar {
|
|
@@ -472,6 +472,14 @@ export class TreeModelImpl implements TreeModel, SelectionProvider<ReadonlyArray
|
|
|
472
472
|
return this.tree.markAsBusy(node, ms, token);
|
|
473
473
|
}
|
|
474
474
|
|
|
475
|
+
get onDidUpdate(): Event<TreeNode[]> {
|
|
476
|
+
return this.tree.onDidUpdate;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
markAsChecked(node: TreeNode, checked: boolean): void {
|
|
480
|
+
this.tree.markAsChecked(node, checked);
|
|
481
|
+
}
|
|
482
|
+
|
|
475
483
|
}
|
|
476
484
|
export namespace TreeModelImpl {
|
|
477
485
|
export interface State {
|
|
@@ -243,12 +243,16 @@ export class TreeWidget extends ReactWidget implements StatefulWidget {
|
|
|
243
243
|
}),
|
|
244
244
|
]);
|
|
245
245
|
}
|
|
246
|
+
this.node.addEventListener('mousedown', this.handleMiddleClickEvent.bind(this));
|
|
247
|
+
this.node.addEventListener('mouseup', this.handleMiddleClickEvent.bind(this));
|
|
248
|
+
this.node.addEventListener('auxclick', this.handleMiddleClickEvent.bind(this));
|
|
246
249
|
this.toDispose.pushAll([
|
|
247
250
|
this.model,
|
|
248
251
|
this.model.onChanged(() => this.updateRows()),
|
|
249
252
|
this.model.onSelectionChanged(() => this.scheduleUpdateScrollToRow({ resize: false })),
|
|
250
253
|
this.focusService.onDidChangeFocus(() => this.scheduleUpdateScrollToRow({ resize: false })),
|
|
251
254
|
this.model.onDidChangeBusy(() => this.update()),
|
|
255
|
+
this.model.onDidUpdate(() => this.update()),
|
|
252
256
|
this.model.onNodeRefreshed(() => this.updateDecorations()),
|
|
253
257
|
this.model.onExpansionChanged(() => this.updateDecorations()),
|
|
254
258
|
this.decoratorService,
|
|
@@ -575,6 +579,40 @@ export class TreeWidget extends ReactWidget implements StatefulWidget {
|
|
|
575
579
|
</div>;
|
|
576
580
|
}
|
|
577
581
|
|
|
582
|
+
/**
|
|
583
|
+
* Render the node expansion toggle.
|
|
584
|
+
* @param node the tree node.
|
|
585
|
+
* @param props the node properties.
|
|
586
|
+
*/
|
|
587
|
+
protected renderCheckbox(node: TreeNode, props: NodeProps): React.ReactNode {
|
|
588
|
+
if (node.checkboxInfo === undefined) {
|
|
589
|
+
// eslint-disable-next-line no-null/no-null
|
|
590
|
+
return null;
|
|
591
|
+
}
|
|
592
|
+
return <input data-node-id={node.id}
|
|
593
|
+
readOnly
|
|
594
|
+
type='checkbox'
|
|
595
|
+
checked={!!node.checkboxInfo.checked}
|
|
596
|
+
title={node.checkboxInfo.tooltip}
|
|
597
|
+
aria-label={node.checkboxInfo.accessibilityInformation?.label}
|
|
598
|
+
role={node.checkboxInfo.accessibilityInformation?.role}
|
|
599
|
+
className='theia-input'
|
|
600
|
+
onClick={event => this.toggleChecked(event)} />;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
protected toggleChecked(event: React.MouseEvent<HTMLElement>): void {
|
|
604
|
+
const nodeId = event.currentTarget.getAttribute('data-node-id');
|
|
605
|
+
if (nodeId) {
|
|
606
|
+
const node = this.model.getNode(nodeId);
|
|
607
|
+
if (node) {
|
|
608
|
+
this.model.markAsChecked(node, !node.checkboxInfo!.checked);
|
|
609
|
+
} else {
|
|
610
|
+
this.handleClickEvent(node, event);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
event.preventDefault();
|
|
614
|
+
event.stopPropagation();
|
|
615
|
+
}
|
|
578
616
|
/**
|
|
579
617
|
* Render the tree node caption given the node properties.
|
|
580
618
|
* @param node the tree node.
|
|
@@ -902,6 +940,7 @@ export class TreeWidget extends ReactWidget implements StatefulWidget {
|
|
|
902
940
|
const attributes = this.createNodeAttributes(node, props);
|
|
903
941
|
const content = <div className={TREE_NODE_CONTENT_CLASS}>
|
|
904
942
|
{this.renderExpansionToggle(node, props)}
|
|
943
|
+
{this.renderCheckbox(node, props)}
|
|
905
944
|
{this.decorateIcon(node, this.renderIcon(node, props))}
|
|
906
945
|
{this.renderCaptionAffixes(node, props, 'captionPrefixes')}
|
|
907
946
|
{this.renderCaption(node, props)}
|
|
@@ -924,6 +963,7 @@ export class TreeWidget extends ReactWidget implements StatefulWidget {
|
|
|
924
963
|
style,
|
|
925
964
|
onClick: event => this.handleClickEvent(node, event),
|
|
926
965
|
onDoubleClick: event => this.handleDblClickEvent(node, event),
|
|
966
|
+
onAuxClick: event => this.handleAuxClickEvent(node, event),
|
|
927
967
|
onContextMenu: event => this.handleContextMenuEvent(node, event),
|
|
928
968
|
};
|
|
929
969
|
}
|
|
@@ -1238,6 +1278,32 @@ export class TreeWidget extends ReactWidget implements StatefulWidget {
|
|
|
1238
1278
|
event.stopPropagation();
|
|
1239
1279
|
}
|
|
1240
1280
|
|
|
1281
|
+
/**
|
|
1282
|
+
* Handle the middle-click mouse event.
|
|
1283
|
+
* @param node the tree node if available.
|
|
1284
|
+
* @param event the middle-click mouse event.
|
|
1285
|
+
*/
|
|
1286
|
+
protected handleAuxClickEvent(node: TreeNode | undefined, event: React.MouseEvent<HTMLElement>): void {
|
|
1287
|
+
if (event.button === 1) {
|
|
1288
|
+
this.model.openNode(node);
|
|
1289
|
+
if (SelectableTreeNode.is(node)) {
|
|
1290
|
+
this.model.selectNode(node);
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
event.stopPropagation();
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
/**
|
|
1297
|
+
* Handle the middle-click mouse event.
|
|
1298
|
+
* @param event the middle-click mouse event.
|
|
1299
|
+
*/
|
|
1300
|
+
protected handleMiddleClickEvent(event: MouseEvent): void {
|
|
1301
|
+
// Prevents auto-scrolling behavior when middle-clicking.
|
|
1302
|
+
if (event.button === 1) {
|
|
1303
|
+
event.preventDefault();
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1241
1307
|
/**
|
|
1242
1308
|
* Handle the context menu click event.
|
|
1243
1309
|
* - The context menu click event is triggered by the right-click.
|
package/src/browser/tree/tree.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { Disposable, DisposableCollection } from '../../common/disposable';
|
|
|
20
20
|
import { CancellationToken, CancellationTokenSource } from '../../common/cancellation';
|
|
21
21
|
import { timeout } from '../../common/promise-util';
|
|
22
22
|
import { isObject, Mutable } from '../../common';
|
|
23
|
+
import { AccessibilityInformation } from '../../common/accessibility';
|
|
23
24
|
|
|
24
25
|
export const Tree = Symbol('Tree');
|
|
25
26
|
|
|
@@ -70,6 +71,19 @@ export interface Tree extends Disposable {
|
|
|
70
71
|
* A token source of the given token should be canceled to unmark.
|
|
71
72
|
*/
|
|
72
73
|
markAsBusy(node: Readonly<TreeNode>, ms: number, token: CancellationToken): Promise<void>;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* An update to the tree node occurred, but the tree structure remains unchanged
|
|
77
|
+
*/
|
|
78
|
+
readonly onDidUpdate: Event<TreeNode[]>;
|
|
79
|
+
|
|
80
|
+
markAsChecked(node: TreeNode, checked: boolean): void;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface TreeViewItemCheckboxInfo {
|
|
84
|
+
checked: boolean;
|
|
85
|
+
tooltip?: string;
|
|
86
|
+
accessibilityInformation?: AccessibilityInformation
|
|
73
87
|
}
|
|
74
88
|
|
|
75
89
|
/**
|
|
@@ -120,6 +134,11 @@ export interface TreeNode {
|
|
|
120
134
|
* Whether this node is busy. Greater than 0 then busy; otherwise not.
|
|
121
135
|
*/
|
|
122
136
|
readonly busy?: number;
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Whether this node is checked.
|
|
140
|
+
*/
|
|
141
|
+
readonly checkboxInfo?: TreeViewItemCheckboxInfo;
|
|
123
142
|
}
|
|
124
143
|
|
|
125
144
|
export namespace TreeNode {
|
|
@@ -238,6 +257,8 @@ export class TreeImpl implements Tree {
|
|
|
238
257
|
|
|
239
258
|
protected readonly onDidChangeBusyEmitter = new Emitter<TreeNode>();
|
|
240
259
|
readonly onDidChangeBusy = this.onDidChangeBusyEmitter.event;
|
|
260
|
+
protected readonly onDidUpdateEmitter = new Emitter<TreeNode[]>();
|
|
261
|
+
readonly onDidUpdate = this.onDidUpdateEmitter.event;
|
|
241
262
|
|
|
242
263
|
protected nodes: {
|
|
243
264
|
[id: string]: Mutable<TreeNode> | undefined
|
|
@@ -368,6 +389,12 @@ export class TreeImpl implements Tree {
|
|
|
368
389
|
await this.doMarkAsBusy(node, ms, token);
|
|
369
390
|
}
|
|
370
391
|
}
|
|
392
|
+
|
|
393
|
+
markAsChecked(node: Mutable<TreeNode>, checked: boolean): void {
|
|
394
|
+
node.checkboxInfo!.checked = checked;
|
|
395
|
+
this.onDidUpdateEmitter.fire([node]);
|
|
396
|
+
}
|
|
397
|
+
|
|
371
398
|
protected async doMarkAsBusy(node: Mutable<TreeNode>, ms: number, token: CancellationToken): Promise<void> {
|
|
372
399
|
try {
|
|
373
400
|
await timeout(ms, token);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2023 STMicroelectronics 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 { isFunction, isObject } from '../../common';
|
|
18
|
+
|
|
19
|
+
export interface EnhancedPreviewWidget {
|
|
20
|
+
getEnhancedPreviewNode(): Node | undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export namespace EnhancedPreviewWidget {
|
|
24
|
+
export function is(arg: unknown): arg is EnhancedPreviewWidget {
|
|
25
|
+
return isObject<EnhancedPreviewWidget>(arg) && isFunction(arg.getEnhancedPreviewNode);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -51,7 +51,7 @@ export class WindowContribution implements CommandContribution, KeybindingContri
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
registerMenus(registry: MenuModelRegistry): void {
|
|
54
|
-
registry.registerMenuAction(CommonMenus.
|
|
54
|
+
registry.registerMenuAction(CommonMenus.FILE_NEW_TEXT, {
|
|
55
55
|
commandId: WindowCommands.NEW_WINDOW.id,
|
|
56
56
|
order: 'c'
|
|
57
57
|
});
|
|
@@ -106,4 +106,24 @@ export namespace ArrayUtils {
|
|
|
106
106
|
export function coalesce<T>(array: ReadonlyArray<T | undefined | null>): T[] {
|
|
107
107
|
return <T[]>array.filter(e => !!e);
|
|
108
108
|
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* groups array elements through a comparator function
|
|
112
|
+
* @param data array of elements to group
|
|
113
|
+
* @param compare comparator function: return of 0 means should group, anything above means not group
|
|
114
|
+
* @returns array of arrays with grouped elements
|
|
115
|
+
*/
|
|
116
|
+
export function groupBy<T>(data: ReadonlyArray<T>, compare: (a: T, b: T) => number): T[][] {
|
|
117
|
+
const result: T[][] = [];
|
|
118
|
+
let currentGroup: T[] | undefined = undefined;
|
|
119
|
+
for (const element of data.slice(0).sort(compare)) {
|
|
120
|
+
if (!currentGroup || compare(currentGroup[0], element) !== 0) {
|
|
121
|
+
currentGroup = [element];
|
|
122
|
+
result.push(currentGroup);
|
|
123
|
+
} else {
|
|
124
|
+
currentGroup.push(element);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
109
129
|
}
|