@theia/core 1.53.0-next.6 → 1.53.0-next.64

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.
Files changed (162) hide show
  1. package/README.md +7 -7
  2. package/i18n/nls.cs.json +30 -0
  3. package/i18n/nls.de.json +30 -0
  4. package/i18n/nls.es.json +30 -0
  5. package/i18n/nls.fr.json +30 -0
  6. package/i18n/nls.hu.json +30 -0
  7. package/i18n/nls.it.json +30 -0
  8. package/i18n/nls.ja.json +30 -0
  9. package/i18n/nls.json +31 -1
  10. package/i18n/nls.ko.json +582 -0
  11. package/i18n/nls.pl.json +30 -0
  12. package/i18n/nls.pt-br.json +30 -0
  13. package/i18n/nls.ru.json +30 -0
  14. package/i18n/nls.tr.json +582 -0
  15. package/i18n/nls.zh-cn.json +30 -0
  16. package/i18n/nls.zh-tw.json +582 -0
  17. package/lib/browser/authentication-service.d.ts +15 -14
  18. package/lib/browser/authentication-service.d.ts.map +1 -1
  19. package/lib/browser/authentication-service.js +5 -5
  20. package/lib/browser/authentication-service.js.map +1 -1
  21. package/lib/browser/catalog.json +6937 -0
  22. package/lib/browser/context-key-service.d.ts +3 -2
  23. package/lib/browser/context-key-service.d.ts.map +1 -1
  24. package/lib/browser/context-key-service.js.map +1 -1
  25. package/lib/browser/core-preferences.d.ts.map +1 -1
  26. package/lib/browser/core-preferences.js +9 -0
  27. package/lib/browser/core-preferences.js.map +1 -1
  28. package/lib/browser/frontend-application-module.d.ts.map +1 -1
  29. package/lib/browser/frontend-application-module.js.map +1 -1
  30. package/lib/browser/json-schema-store.d.ts +0 -3
  31. package/lib/browser/json-schema-store.d.ts.map +1 -1
  32. package/lib/browser/json-schema-store.js +2 -12
  33. package/lib/browser/json-schema-store.js.map +1 -1
  34. package/lib/browser/open-with-service.d.ts +13 -1
  35. package/lib/browser/open-with-service.d.ts.map +1 -1
  36. package/lib/browser/open-with-service.js +48 -9
  37. package/lib/browser/open-with-service.js.map +1 -1
  38. package/lib/browser/opener-service.d.ts +8 -0
  39. package/lib/browser/opener-service.d.ts.map +1 -1
  40. package/lib/browser/opener-service.js +18 -3
  41. package/lib/browser/opener-service.js.map +1 -1
  42. package/lib/browser/saveable-service.d.ts.map +1 -1
  43. package/lib/browser/saveable-service.js +6 -2
  44. package/lib/browser/saveable-service.js.map +1 -1
  45. package/lib/browser/saveable.d.ts +17 -1
  46. package/lib/browser/saveable.d.ts.map +1 -1
  47. package/lib/browser/saveable.js +62 -1
  48. package/lib/browser/saveable.js.map +1 -1
  49. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.d.ts +5 -4
  50. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.d.ts.map +1 -1
  51. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.js +2 -1
  52. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.js.map +1 -1
  53. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.d.ts +6 -16
  54. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.d.ts.map +1 -1
  55. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.js +12 -29
  56. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.js.map +1 -1
  57. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-types.d.ts +43 -78
  58. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-types.d.ts.map +1 -1
  59. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-types.js +8 -39
  60. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-types.js.map +1 -1
  61. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.d.ts +10 -10
  62. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.d.ts.map +1 -1
  63. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.js +43 -32
  64. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.js.map +1 -1
  65. package/lib/browser/view-container.d.ts +2 -2
  66. package/lib/browser/view-container.d.ts.map +1 -1
  67. package/lib/browser/view-container.js.map +1 -1
  68. package/lib/browser/widget-open-handler.d.ts +4 -1
  69. package/lib/browser/widget-open-handler.d.ts.map +1 -1
  70. package/lib/browser/widget-open-handler.js.map +1 -1
  71. package/lib/browser/widgets/extractable-widget.js +1 -1
  72. package/lib/browser/widgets/extractable-widget.js.map +1 -1
  73. package/lib/browser/widgets/index.d.ts +1 -0
  74. package/lib/browser/widgets/index.d.ts.map +1 -1
  75. package/lib/browser/widgets/index.js +1 -0
  76. package/lib/browser/widgets/index.js.map +1 -1
  77. package/lib/browser/widgets/split-widget.d.ts +45 -0
  78. package/lib/browser/widgets/split-widget.d.ts.map +1 -0
  79. package/lib/browser/widgets/split-widget.js +126 -0
  80. package/lib/browser/widgets/split-widget.js.map +1 -0
  81. package/lib/common/event.d.ts +2 -0
  82. package/lib/common/event.d.ts.map +1 -1
  83. package/lib/common/event.js +4 -0
  84. package/lib/common/event.js.map +1 -1
  85. package/lib/common/glob.d.ts +4 -4
  86. package/lib/common/json-schema.d.ts +2 -0
  87. package/lib/common/json-schema.d.ts.map +1 -1
  88. package/lib/common/menu/menu-types.d.ts.map +1 -1
  89. package/lib/common/menu/menu-types.js.map +1 -1
  90. package/lib/electron-browser/electron-uri-handler.d.ts +6 -0
  91. package/lib/electron-browser/electron-uri-handler.d.ts.map +1 -0
  92. package/lib/electron-browser/electron-uri-handler.js +49 -0
  93. package/lib/electron-browser/electron-uri-handler.js.map +1 -0
  94. package/lib/electron-browser/menu/electron-main-menu-factory.d.ts +1 -1
  95. package/lib/electron-browser/menu/electron-main-menu-factory.d.ts.map +1 -1
  96. package/lib/electron-browser/menu/electron-main-menu-factory.js +6 -6
  97. package/lib/electron-browser/menu/electron-main-menu-factory.js.map +1 -1
  98. package/lib/electron-browser/preload.d.ts.map +1 -1
  99. package/lib/electron-browser/preload.js +12 -0
  100. package/lib/electron-browser/preload.js.map +1 -1
  101. package/lib/electron-browser/window/electron-window-module.d.ts.map +1 -1
  102. package/lib/electron-browser/window/electron-window-module.js +3 -0
  103. package/lib/electron-browser/window/electron-window-module.js.map +1 -1
  104. package/lib/electron-browser/window/external-app-open-handler.js +1 -1
  105. package/lib/electron-browser/window/external-app-open-handler.js.map +1 -1
  106. package/lib/electron-common/electron-api.d.ts +2 -0
  107. package/lib/electron-common/electron-api.d.ts.map +1 -1
  108. package/lib/electron-common/electron-api.js +2 -1
  109. package/lib/electron-common/electron-api.js.map +1 -1
  110. package/lib/electron-main/electron-api-main.d.ts +2 -0
  111. package/lib/electron-main/electron-api-main.d.ts.map +1 -1
  112. package/lib/electron-main/electron-api-main.js +27 -3
  113. package/lib/electron-main/electron-api-main.js.map +1 -1
  114. package/lib/electron-main/electron-main-application.d.ts +5 -3
  115. package/lib/electron-main/electron-main-application.d.ts.map +1 -1
  116. package/lib/electron-main/electron-main-application.js +57 -14
  117. package/lib/electron-main/electron-main-application.js.map +1 -1
  118. package/lib/electron-main/theia-electron-window.d.ts +1 -0
  119. package/lib/electron-main/theia-electron-window.d.ts.map +1 -1
  120. package/lib/electron-main/theia-electron-window.js +3 -0
  121. package/lib/electron-main/theia-electron-window.js.map +1 -1
  122. package/lib/node/i18n/theia-localization-contribution.d.ts.map +1 -1
  123. package/lib/node/i18n/theia-localization-contribution.js +12 -8
  124. package/lib/node/i18n/theia-localization-contribution.js.map +1 -1
  125. package/package.json +10 -8
  126. package/src/browser/authentication-service.ts +18 -19
  127. package/src/browser/context-key-service.ts +3 -3
  128. package/src/browser/core-preferences.ts +9 -0
  129. package/src/browser/frontend-application-module.ts +0 -1
  130. package/src/browser/json-schema-store.ts +2 -11
  131. package/src/browser/open-with-service.ts +54 -7
  132. package/src/browser/opener-service.ts +25 -2
  133. package/src/browser/saveable-service.ts +6 -2
  134. package/src/browser/saveable.ts +69 -1
  135. package/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.ts +3 -3
  136. package/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.ts +19 -33
  137. package/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar-types.ts +66 -124
  138. package/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar.tsx +42 -27
  139. package/src/browser/style/index.css +1 -0
  140. package/src/browser/style/split-widget.css +38 -0
  141. package/src/browser/style/tabs.css +13 -24
  142. package/src/browser/style/view-container.css +0 -7
  143. package/src/browser/view-container.ts +2 -2
  144. package/src/browser/widget-open-handler.ts +4 -1
  145. package/src/browser/widgets/extractable-widget.ts +1 -1
  146. package/src/browser/widgets/index.ts +1 -0
  147. package/src/browser/widgets/split-widget.ts +163 -0
  148. package/src/common/event.ts +6 -0
  149. package/src/common/glob.ts +4 -4
  150. package/src/common/json-schema.ts +2 -0
  151. package/src/common/menu/menu-types.ts +1 -0
  152. package/src/electron-browser/electron-uri-handler.ts +42 -0
  153. package/src/electron-browser/menu/electron-main-menu-factory.ts +7 -6
  154. package/src/electron-browser/preload.ts +16 -1
  155. package/src/electron-browser/window/electron-window-module.ts +3 -0
  156. package/src/electron-browser/window/external-app-open-handler.ts +1 -1
  157. package/src/electron-common/electron-api.ts +3 -0
  158. package/src/electron-main/electron-api-main.ts +31 -5
  159. package/src/electron-main/electron-main-application.ts +62 -20
  160. package/src/electron-main/theia-electron-window.ts +5 -0
  161. package/src/node/i18n/theia-localization-contribution.ts +12 -8
  162. package/i18n/nls.pt-pt.json +0 -552
@@ -16,6 +16,7 @@
16
16
 
17
17
  import { injectable } from 'inversify';
18
18
  import { Emitter, Event } from '../common/event';
19
+ import { Disposable } from '../common';
19
20
 
20
21
  export type ContextKeyValue = null | undefined | boolean | number | string
21
22
  | Array<null | undefined | boolean | number | string>
@@ -83,11 +84,10 @@ export interface ContextKeyService extends ContextMatcher {
83
84
  setContext(key: string, value: unknown): void;
84
85
  }
85
86
 
86
- export type ScopedValueStore = Omit<ContextKeyService, 'onDidChange' | 'match' | 'parseKeys' | 'with' | 'createOverlay'>;
87
+ export type ScopedValueStore = Omit<ContextKeyService, 'onDidChange' | 'match' | 'parseKeys' | 'with' | 'createOverlay'> & Disposable;
87
88
 
88
89
  @injectable()
89
90
  export class ContextKeyServiceDummyImpl implements ContextKeyService {
90
-
91
91
  protected readonly onDidChangeEmitter = new Emitter<ContextKeyChangeEvent>();
92
92
  readonly onDidChange = this.onDidChangeEmitter.event;
93
93
  protected fireDidChange(event: ContextKeyChangeEvent): void {
@@ -122,7 +122,7 @@ export class ContextKeyServiceDummyImpl implements ContextKeyService {
122
122
  /**
123
123
  * Details should implemented by an extension, e.g. by the monaco extension.
124
124
  */
125
- createScoped(target: HTMLElement): ContextKeyService {
125
+ createScoped(target: HTMLElement): ScopedValueStore {
126
126
  return this;
127
127
  }
128
128
 
@@ -281,6 +281,15 @@ export const corePreferenceSchema: PreferenceSchema = {
281
281
  default: 200,
282
282
  minimum: 10,
283
283
  description: nls.localize('theia/core/tabDefaultSize', 'Specifies the default size for tabs.')
284
+ },
285
+ 'workbench.editorAssociations': {
286
+ type: 'object',
287
+ markdownDescription: nls.localizeByDefault('Configure [glob patterns](https://aka.ms/vscode-glob-patterns) to editors (for example `"*.hex": "hexEditor.hexedit"`). These have precedence over the default behavior.'),
288
+ patternProperties: {
289
+ '.*': {
290
+ type: 'string'
291
+ }
292
+ }
284
293
  }
285
294
  }
286
295
  };
@@ -465,7 +465,6 @@ export const frontendApplicationModule = new ContainerModule((bind, _unbind, _is
465
465
  bind(FrontendApplicationContribution).toService(StylingService);
466
466
 
467
467
  bind(SecondaryWindowHandler).toSelf().inSingletonScope();
468
-
469
468
  bind(ViewColumnService).toSelf().inSingletonScope();
470
469
 
471
470
  bind(UndoRedoHandlerService).toSelf().inSingletonScope();
@@ -18,9 +18,7 @@ import { injectable, inject, named } from 'inversify';
18
18
  import { ContributionProvider } from '../common/contribution-provider';
19
19
  import { FrontendApplicationContribution } from './frontend-application-contribution';
20
20
  import { MaybePromise } from '../common';
21
- import { Endpoint } from './endpoint';
22
21
  import { timeout, Deferred } from '../common/promise-util';
23
- import { RequestContext, RequestService } from '@theia/request';
24
22
 
25
23
  export interface JsonSchemaConfiguration {
26
24
  fileMatch: string | string[];
@@ -95,16 +93,9 @@ export class JsonSchemaStore implements FrontendApplicationContribution {
95
93
 
96
94
  @injectable()
97
95
  export class DefaultJsonSchemaContribution implements JsonSchemaContribution {
98
-
99
- @inject(RequestService)
100
- protected readonly requestService: RequestService;
101
-
102
- protected readonly jsonSchemaUrl = `${new Endpoint().httpScheme}//schemastore.org/api/json/catalog.json`;
103
-
104
96
  async registerSchemas(context: JsonSchemaRegisterContext): Promise<void> {
105
- const response = await this.requestService.request({ url: this.jsonSchemaUrl });
106
- const schemas = RequestContext.asJson<{ schemas: DefaultJsonSchemaContribution.SchemaData[] }>(response).schemas;
107
- for (const s of schemas) {
97
+ const catalog = require('./catalog.json') as { schemas: DefaultJsonSchemaContribution.SchemaData[] };
98
+ for (const s of catalog.schemas) {
108
99
  if (s.fileMatch) {
109
100
  context.registerSchema({
110
101
  fileMatch: s.fileMatch,
@@ -19,7 +19,9 @@ import { Disposable } from '../common/disposable';
19
19
  import { nls } from '../common/nls';
20
20
  import { MaybePromise } from '../common/types';
21
21
  import { URI } from '../common/uri';
22
- import { QuickInputService } from './quick-input';
22
+ import { QuickInputService, QuickPickItem, QuickPickItemOrSeparator } from './quick-input';
23
+ import { PreferenceScope, PreferenceService } from './preferences';
24
+ import { getDefaultHandler } from './opener-service';
23
25
 
24
26
  export interface OpenWithHandler {
25
27
  /**
@@ -46,6 +48,11 @@ export interface OpenWithHandler {
46
48
  * A returned value indicating a priority of this handler.
47
49
  */
48
50
  canHandle(uri: URI): number;
51
+ /**
52
+ * Test whether this handler and open the given URI
53
+ * and return the order of this handler in the list.
54
+ */
55
+ getOrder?(uri: URI): number;
49
56
  /**
50
57
  * Open a widget for the given URI and options.
51
58
  * Resolve to an opened widget or undefined, e.g. if a page is opened.
@@ -54,12 +61,19 @@ export interface OpenWithHandler {
54
61
  open(uri: URI): MaybePromise<object | undefined>;
55
62
  }
56
63
 
64
+ export interface OpenWithQuickPickItem extends QuickPickItem {
65
+ handler: OpenWithHandler;
66
+ }
67
+
57
68
  @injectable()
58
69
  export class OpenWithService {
59
70
 
60
71
  @inject(QuickInputService)
61
72
  protected readonly quickInputService: QuickInputService;
62
73
 
74
+ @inject(PreferenceService)
75
+ protected readonly preferenceService: PreferenceService;
76
+
63
77
  protected readonly handlers: OpenWithHandler[] = [];
64
78
 
65
79
  registerHandler(handler: OpenWithHandler): Disposable {
@@ -73,17 +87,50 @@ export class OpenWithService {
73
87
  }
74
88
 
75
89
  async openWith(uri: URI): Promise<object | undefined> {
90
+ // Clone the object, because all objects returned by the preferences service are frozen.
91
+ const associations: Record<string, unknown> = { ...this.preferenceService.get('workbench.editorAssociations') };
92
+ const ext = `*${uri.path.ext}`;
76
93
  const handlers = this.getHandlers(uri);
77
- const result = await this.quickInputService.pick(handlers.map(handler => ({
78
- handler: handler,
79
- label: handler.label ?? handler.id,
80
- detail: handler.providerName
81
- })), {
94
+ const ordered = handlers.slice().sort((a, b) => this.getOrder(b, uri) - this.getOrder(a, uri));
95
+ const defaultHandler = getDefaultHandler(uri, this.preferenceService) ?? handlers[0]?.id;
96
+ const items = this.getQuickPickItems(ordered, defaultHandler);
97
+ // Only offer to select a default editor when the file has a file extension
98
+ const extraItems: QuickPickItemOrSeparator[] = uri.path.ext ? [{
99
+ type: 'separator'
100
+ }, {
101
+ label: nls.localizeByDefault("Configure default editor for '{0}'...", ext)
102
+ }] : [];
103
+ const result = await this.quickInputService.pick<OpenWithQuickPickItem | { label: string }>([...items, ...extraItems], {
82
104
  placeHolder: nls.localizeByDefault("Select editor for '{0}'", uri.path.base)
83
105
  });
84
106
  if (result) {
85
- return result.handler.open(uri);
107
+ if ('handler' in result) {
108
+ return result.handler.open(uri);
109
+ } else if (result.label) {
110
+ const configureResult = await this.quickInputService.pick(items, {
111
+ placeHolder: nls.localizeByDefault("Select new default editor for '{0}'", ext)
112
+ });
113
+ if (configureResult) {
114
+ associations[ext] = configureResult.handler.id;
115
+ this.preferenceService.set('workbench.editorAssociations', associations, PreferenceScope.User);
116
+ return configureResult.handler.open(uri);
117
+ }
118
+ }
86
119
  }
120
+ return undefined;
121
+ }
122
+
123
+ protected getQuickPickItems(handlers: OpenWithHandler[], defaultHandler?: string): OpenWithQuickPickItem[] {
124
+ return handlers.map(handler => ({
125
+ handler,
126
+ label: handler.label ?? handler.id,
127
+ detail: handler.providerName ?? '',
128
+ description: handler.id === defaultHandler ? nls.localizeByDefault('Default') : undefined
129
+ }));
130
+ }
131
+
132
+ protected getOrder(handler: OpenWithHandler, uri: URI): number {
133
+ return handler.getOrder ? handler.getOrder(uri) : handler.canHandle(uri);
87
134
  }
88
135
 
89
136
  getHandlers(uri: URI): OpenWithHandler[] {
@@ -17,6 +17,8 @@
17
17
  import { named, injectable, inject } from 'inversify';
18
18
  import URI from '../common/uri';
19
19
  import { ContributionProvider, Prioritizeable, MaybePromise, Emitter, Event, Disposable } from '../common';
20
+ import { PreferenceService } from './preferences';
21
+ import { match } from '../common/glob';
20
22
 
21
23
  export interface OpenerOptions {
22
24
  }
@@ -79,6 +81,12 @@ export interface OpenerService {
79
81
  * Add open handler i.e. for custom editors
80
82
  */
81
83
  addHandler?(openHandler: OpenHandler): Disposable;
84
+
85
+ /**
86
+ * Remove open handler
87
+ */
88
+ removeHandler?(openHandler: OpenHandler): void;
89
+
82
90
  /**
83
91
  * Event that fires when a new opener is added or removed.
84
92
  */
@@ -90,6 +98,17 @@ export async function open(openerService: OpenerService, uri: URI, options?: Ope
90
98
  return opener.open(uri, options);
91
99
  }
92
100
 
101
+ export function getDefaultHandler(uri: URI, preferenceService: PreferenceService): string | undefined {
102
+ const associations = preferenceService.get('workbench.editorAssociations', {});
103
+ const defaultHandler = Object.entries(associations).find(([key]) => match(key, uri.path.base))?.[1];
104
+ if (typeof defaultHandler === 'string') {
105
+ return defaultHandler;
106
+ }
107
+ return undefined;
108
+ }
109
+
110
+ export const defaultHandlerPriority = 100_000;
111
+
93
112
  @injectable()
94
113
  export class DefaultOpenerService implements OpenerService {
95
114
  // Collection of open-handlers for custom-editor contributions.
@@ -108,11 +127,15 @@ export class DefaultOpenerService implements OpenerService {
108
127
  this.onDidChangeOpenersEmitter.fire();
109
128
 
110
129
  return Disposable.create(() => {
111
- this.customEditorOpenHandlers.splice(this.customEditorOpenHandlers.indexOf(openHandler), 1);
112
- this.onDidChangeOpenersEmitter.fire();
130
+ this.removeHandler(openHandler);
113
131
  });
114
132
  }
115
133
 
134
+ removeHandler(openHandler: OpenHandler): void {
135
+ this.customEditorOpenHandlers.splice(this.customEditorOpenHandlers.indexOf(openHandler), 1);
136
+ this.onDidChangeOpenersEmitter.fire();
137
+ }
138
+
116
139
  async getOpener(uri: URI, options?: OpenerOptions): Promise<OpenHandler> {
117
140
  const handlers = await this.prioritize(uri, options);
118
141
  if (handlers.length >= 1) {
@@ -174,7 +174,10 @@ export class SaveableService implements FrontendApplicationContribution {
174
174
  setDirty(saveableWidget, saveable.dirty);
175
175
  saveable.onDirtyChanged(() => setDirty(saveableWidget, saveable.dirty));
176
176
  const closeWithSaving = this.createCloseWithSaving();
177
- const closeWithoutSaving = () => this.closeWithoutSaving(saveableWidget, false);
177
+ const closeWithoutSaving = async () => {
178
+ const revert = Saveable.closingWidgetWouldLoseSaveable(saveableWidget, Array.from(this.saveThrottles.keys()));
179
+ await this.closeWithoutSaving(saveableWidget, revert);
180
+ };
178
181
  Object.assign(saveableWidget, {
179
182
  closeWithoutSaving,
180
183
  closeWithSaving,
@@ -224,7 +227,8 @@ export class SaveableService implements FrontendApplicationContribution {
224
227
  }
225
228
  const notLastWithDocument = !Saveable.closingWidgetWouldLoseSaveable(widget, Array.from(this.saveThrottles.keys()));
226
229
  if (notLastWithDocument) {
227
- return widget.closeWithoutSaving(false).then(() => undefined);
230
+ await widget.closeWithoutSaving(false);
231
+ return undefined;
228
232
  }
229
233
  if (options && options.shouldSave) {
230
234
  return options.shouldSave();
@@ -21,7 +21,7 @@ import { MaybePromise } from '../common/types';
21
21
  import { Key } from './keyboard/keys';
22
22
  import { AbstractDialog } from './dialogs';
23
23
  import { nls } from '../common/nls';
24
- import { DisposableCollection, isObject } from '../common';
24
+ import { Disposable, DisposableCollection, isObject } from '../common';
25
25
  import { BinaryBuffer } from '../common/buffer';
26
26
 
27
27
  export type AutoSaveMode = 'off' | 'afterDelay' | 'onFocusChange' | 'onWindowChange';
@@ -112,6 +112,74 @@ export class DelegatingSaveable implements Saveable {
112
112
 
113
113
  }
114
114
 
115
+ export class CompositeSaveable implements Saveable {
116
+ protected isDirty = false;
117
+ protected readonly onDirtyChangedEmitter = new Emitter<void>();
118
+ protected readonly onContentChangedEmitter = new Emitter<void>();
119
+ protected readonly toDispose = new DisposableCollection(this.onDirtyChangedEmitter, this.onContentChangedEmitter);
120
+ protected readonly saveablesMap = new Map<Saveable, Disposable>();
121
+
122
+ get dirty(): boolean {
123
+ return this.isDirty;
124
+ }
125
+
126
+ get onDirtyChanged(): Event<void> {
127
+ return this.onDirtyChangedEmitter.event;
128
+ }
129
+
130
+ get onContentChanged(): Event<void> {
131
+ return this.onContentChangedEmitter.event;
132
+ }
133
+
134
+ async save(options?: SaveOptions): Promise<void> {
135
+ await Promise.all(this.saveables.map(saveable => saveable.save(options)));
136
+ }
137
+
138
+ async revert(options?: Saveable.RevertOptions): Promise<void> {
139
+ await Promise.all(this.saveables.map(saveable => saveable.revert?.(options)));
140
+ }
141
+
142
+ get saveables(): readonly Saveable[] {
143
+ return Array.from(this.saveablesMap.keys());
144
+ }
145
+
146
+ add(saveable: Saveable): void {
147
+ if (this.saveablesMap.has(saveable)) {
148
+ return;
149
+ }
150
+ const toDispose = new DisposableCollection();
151
+ this.toDispose.push(toDispose);
152
+ this.saveablesMap.set(saveable, toDispose);
153
+ toDispose.push(Disposable.create(() => {
154
+ this.saveablesMap.delete(saveable);
155
+ }));
156
+ toDispose.push(saveable.onDirtyChanged(() => {
157
+ const wasDirty = this.isDirty;
158
+ this.isDirty = this.saveables.some(s => s.dirty);
159
+ if (this.isDirty !== wasDirty) {
160
+ this.onDirtyChangedEmitter.fire();
161
+ }
162
+ }));
163
+ toDispose.push(saveable.onContentChanged(() => {
164
+ this.onContentChangedEmitter.fire();
165
+ }));
166
+ if (saveable.dirty && !this.isDirty) {
167
+ this.isDirty = true;
168
+ this.onDirtyChangedEmitter.fire();
169
+ }
170
+ }
171
+
172
+ remove(saveable: Saveable): boolean {
173
+ const toDispose = this.saveablesMap.get(saveable);
174
+ toDispose?.dispose();
175
+ return !!toDispose;
176
+ }
177
+
178
+ dispose(): void {
179
+ this.toDispose.dispose();
180
+ }
181
+ }
182
+
115
183
  export namespace Saveable {
116
184
  export interface RevertOptions {
117
185
  /**
@@ -15,12 +15,12 @@
15
15
  // *****************************************************************************
16
16
 
17
17
  import { MenuNode, MenuPath } from '../../../common';
18
- import { NAVIGATION, TabBarToolbarItem } from './tab-bar-toolbar-types';
18
+ import { NAVIGATION, RenderedToolbarItem } from './tab-bar-toolbar-types';
19
19
 
20
20
  export const TOOLBAR_WRAPPER_ID_SUFFIX = '-as-tabbar-toolbar-item';
21
21
 
22
- export class ToolbarMenuNodeWrapper implements TabBarToolbarItem {
23
- constructor(protected readonly menuNode: MenuNode, readonly group?: string, readonly menuPath?: MenuPath) { }
22
+ export class ToolbarMenuNodeWrapper implements RenderedToolbarItem {
23
+ constructor(protected readonly menuNode: MenuNode, readonly group: string | undefined, readonly delegateMenuPath: MenuPath, readonly menuPath?: MenuPath) { }
24
24
  get id(): string { return this.menuNode.id + TOOLBAR_WRAPPER_ID_SUFFIX; }
25
25
  get command(): string { return this.menuNode.command ?? ''; };
26
26
  get icon(): string | undefined { return this.menuNode.icon; }
@@ -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, MenuNode, MenuPath } from '../../../common';
20
+ import { CommandRegistry, 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-contribution';
23
23
  import { Widget } from '../../widgets';
24
- import { AnyToolbarItem, ConditionalToolbarItem, MenuDelegate, MenuToolbarItem, ReactTabBarToolbarItem, TabBarToolbarItem } from './tab-bar-toolbar-types';
24
+ import { MenuDelegate, ReactTabBarToolbarItem, RenderedToolbarItem, TabBarToolbarItem } from './tab-bar-toolbar-types';
25
25
  import { ToolbarMenuNodeWrapper } from './tab-bar-toolbar-menu-adapters';
26
26
 
27
27
  /**
@@ -75,7 +75,7 @@ export class TabBarToolbarRegistry implements FrontendApplicationContribution {
75
75
  *
76
76
  * @param item the item to register.
77
77
  */
78
- registerItem(item: TabBarToolbarItem | ReactTabBarToolbarItem): Disposable {
78
+ registerItem(item: RenderedToolbarItem | ReactTabBarToolbarItem): Disposable {
79
79
  const { id } = item;
80
80
  if (this.items.has(id)) {
81
81
  throw new Error(`A toolbar item is already registered with the '${id}' ID.`);
@@ -110,24 +110,18 @@ export class TabBarToolbarRegistry implements FrontendApplicationContribution {
110
110
  for (const delegate of this.menuDelegates.values()) {
111
111
  if (delegate.isVisible(widget)) {
112
112
  const menu = this.menuRegistry.getMenu(delegate.menuPath);
113
- const children = CompoundMenuNode.getFlatChildren(menu.children);
114
- for (const child of children) {
113
+ for (const child of menu.children) {
115
114
  if (!child.when || this.contextKeyService.match(child.when, widget.node)) {
116
115
  if (child.children) {
117
116
  for (const grandchild of child.children) {
118
117
  if (!grandchild.when || this.contextKeyService.match(grandchild.when, widget.node)) {
119
- if (CommandMenuNode.is(grandchild)) {
120
- result.push(new ToolbarMenuNodeWrapper(grandchild, child.id, delegate.menuPath));
121
- } else if (CompoundMenuNode.is(grandchild)) {
122
- let menuPath;
123
- if (menuPath = this.menuRegistry.getPath(grandchild)) {
124
- result.push(new ToolbarMenuNodeWrapper(grandchild, child.id, menuPath));
125
- }
126
- }
118
+ const menuPath = this.menuRegistry.getPath(grandchild);
119
+ result.push(new ToolbarMenuNodeWrapper(grandchild, child.id, delegate.menuPath, menuPath));
127
120
  }
128
121
  }
129
122
  } else if (child.command) {
130
- result.push(new ToolbarMenuNodeWrapper(child, '', delegate.menuPath));
123
+ const menuPath = this.menuRegistry.getPath(child);
124
+ result.push(new ToolbarMenuNodeWrapper(child, undefined, delegate.menuPath, menuPath));
131
125
  }
132
126
  }
133
127
  }
@@ -145,15 +139,17 @@ export class TabBarToolbarRegistry implements FrontendApplicationContribution {
145
139
  * @returns `false` if the `item` should be suppressed, otherwise `true`
146
140
  */
147
141
  protected isItemVisible(item: TabBarToolbarItem | ReactTabBarToolbarItem, widget: Widget): boolean {
148
- if (TabBarToolbarItem.is(item) && item.command && !this.isTabBarToolbarItemVisible(item, widget)) {
142
+ if (!this.isConditionalItemVisible(item, widget)) {
149
143
  return false;
150
144
  }
151
- if (MenuToolbarItem.is(item) && !this.isMenuToolbarItemVisible(item, widget)) {
145
+
146
+ if (item.command && !this.commandRegistry.isVisible(item.command, widget)) {
152
147
  return false;
153
148
  }
154
- if (AnyToolbarItem.isConditional(item) && !this.isConditionalItemVisible(item, widget)) {
149
+ if (item.menuPath && !this.isNonEmptyMenu(item, widget)) {
155
150
  return false;
156
151
  }
152
+
157
153
  // The item is not vetoed. Accept it
158
154
  return true;
159
155
  }
@@ -166,7 +162,7 @@ export class TabBarToolbarRegistry implements FrontendApplicationContribution {
166
162
  * @param widget the widget that is updating the toolbar
167
163
  * @returns `false` if the `item` should be suppressed, otherwise `true`
168
164
  */
169
- protected isConditionalItemVisible(item: ConditionalToolbarItem, widget: Widget): boolean {
165
+ protected isConditionalItemVisible(item: TabBarToolbarItem, widget: Widget): boolean {
170
166
  if (item.isVisible && !item.isVisible(widget)) {
171
167
  return false;
172
168
  }
@@ -176,19 +172,6 @@ export class TabBarToolbarRegistry implements FrontendApplicationContribution {
176
172
  return true;
177
173
  }
178
174
 
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
175
  /**
193
176
  * Query whether a menu toolbar `item` should be shown in the toolbar.
194
177
  * This implementation returns `false` if the `item` does not have any actual menu to show.
@@ -197,7 +180,10 @@ export class TabBarToolbarRegistry implements FrontendApplicationContribution {
197
180
  * @param widget the widget that is updating the toolbar
198
181
  * @returns `false` if the `item` should be suppressed, otherwise `true`
199
182
  */
200
- protected isMenuToolbarItemVisible(item: MenuToolbarItem, widget: Widget): boolean {
183
+ isNonEmptyMenu(item: TabBarToolbarItem, widget: Widget | undefined): boolean {
184
+ if (!item.menuPath) {
185
+ return false;
186
+ }
201
187
  const menu = this.menuRegistry.getMenu(item.menuPath);
202
188
  const isVisible: (node: MenuNode) => boolean = node =>
203
189
  node.children?.length
@@ -220,7 +206,7 @@ export class TabBarToolbarRegistry implements FrontendApplicationContribution {
220
206
  }
221
207
  }
222
208
 
223
- registerMenuDelegate(menuPath: MenuPath, when?: string | ((widget: Widget) => boolean)): Disposable {
209
+ registerMenuDelegate(menuPath: MenuPath, when?: ((widget: Widget) => boolean)): Disposable {
224
210
  const id = this.toElementId(menuPath);
225
211
  if (!this.menuDelegates.has(id)) {
226
212
  const isVisible: MenuDelegate['isVisible'] = !when