@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.
Files changed (167) hide show
  1. package/README.md +6 -6
  2. package/i18n/nls.cs.json +5 -1
  3. package/i18n/nls.de.json +5 -1
  4. package/i18n/nls.es.json +5 -1
  5. package/i18n/nls.fr.json +5 -1
  6. package/i18n/nls.hu.json +5 -1
  7. package/i18n/nls.it.json +5 -1
  8. package/i18n/nls.ja.json +5 -1
  9. package/i18n/nls.json +5 -1
  10. package/i18n/nls.pl.json +5 -1
  11. package/i18n/nls.pt-br.json +5 -1
  12. package/i18n/nls.pt-pt.json +5 -1
  13. package/i18n/nls.ru.json +5 -1
  14. package/i18n/nls.zh-cn.json +5 -1
  15. package/lib/browser/common-frontend-contribution.d.ts +11 -0
  16. package/lib/browser/common-frontend-contribution.d.ts.map +1 -1
  17. package/lib/browser/common-frontend-contribution.js +97 -14
  18. package/lib/browser/common-frontend-contribution.js.map +1 -1
  19. package/lib/browser/context-menu-renderer.d.ts +5 -0
  20. package/lib/browser/context-menu-renderer.d.ts.map +1 -1
  21. package/lib/browser/label-parser.d.ts +8 -0
  22. package/lib/browser/label-parser.d.ts.map +1 -1
  23. package/lib/browser/label-parser.js +14 -0
  24. package/lib/browser/label-parser.js.map +1 -1
  25. package/lib/browser/label-parser.spec.js +33 -0
  26. package/lib/browser/label-parser.spec.js.map +1 -1
  27. package/lib/browser/menu/browser-context-menu-renderer.d.ts +1 -1
  28. package/lib/browser/menu/browser-context-menu-renderer.d.ts.map +1 -1
  29. package/lib/browser/menu/browser-context-menu-renderer.js +2 -2
  30. package/lib/browser/menu/browser-context-menu-renderer.js.map +1 -1
  31. package/lib/browser/menu/browser-menu-plugin.d.ts +1 -1
  32. package/lib/browser/menu/browser-menu-plugin.d.ts.map +1 -1
  33. package/lib/browser/menu/browser-menu-plugin.js +2 -2
  34. package/lib/browser/menu/browser-menu-plugin.js.map +1 -1
  35. package/lib/browser/performance/frontend-stopwatch.d.ts.map +1 -1
  36. package/lib/browser/performance/frontend-stopwatch.js +6 -2
  37. package/lib/browser/performance/frontend-stopwatch.js.map +1 -1
  38. package/lib/browser/preferences/preference-contribution.d.ts +2 -0
  39. package/lib/browser/preferences/preference-contribution.d.ts.map +1 -1
  40. package/lib/browser/preferences/preference-contribution.js +48 -24
  41. package/lib/browser/preferences/preference-contribution.js.map +1 -1
  42. package/lib/browser/saveable.d.ts +15 -1
  43. package/lib/browser/saveable.d.ts.map +1 -1
  44. package/lib/browser/saveable.js +34 -1
  45. package/lib/browser/saveable.js.map +1 -1
  46. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.d.ts +46 -1
  47. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.d.ts.map +1 -1
  48. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.js +87 -6
  49. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.js.map +1 -1
  50. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-types.d.ts +20 -2
  51. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-types.d.ts.map +1 -1
  52. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-types.js +28 -1
  53. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-types.js.map +1 -1
  54. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.d.ts +25 -1
  55. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.d.ts.map +1 -1
  56. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.js +79 -7
  57. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.js.map +1 -1
  58. package/lib/browser/shell/tab-bars.d.ts.map +1 -1
  59. package/lib/browser/shell/tab-bars.js +9 -1
  60. package/lib/browser/shell/tab-bars.js.map +1 -1
  61. package/lib/browser/tree/tree-model.d.ts +2 -0
  62. package/lib/browser/tree/tree-model.d.ts.map +1 -1
  63. package/lib/browser/tree/tree-model.js +6 -0
  64. package/lib/browser/tree/tree-model.js.map +1 -1
  65. package/lib/browser/tree/tree-widget.d.ts +18 -0
  66. package/lib/browser/tree/tree-widget.d.ts.map +1 -1
  67. package/lib/browser/tree/tree-widget.js +57 -0
  68. package/lib/browser/tree/tree-widget.js.map +1 -1
  69. package/lib/browser/tree/tree.d.ts +18 -0
  70. package/lib/browser/tree/tree.d.ts.map +1 -1
  71. package/lib/browser/tree/tree.js +6 -0
  72. package/lib/browser/tree/tree.js.map +1 -1
  73. package/lib/browser/widgets/enhanced-preview-widget.d.ts +7 -0
  74. package/lib/browser/widgets/enhanced-preview-widget.d.ts.map +1 -0
  75. package/lib/browser/widgets/enhanced-preview-widget.js +27 -0
  76. package/lib/browser/widgets/enhanced-preview-widget.js.map +1 -0
  77. package/lib/browser/window-contribution.js +1 -1
  78. package/lib/browser/window-contribution.js.map +1 -1
  79. package/lib/common/array-utils.d.ts +7 -0
  80. package/lib/common/array-utils.d.ts.map +1 -1
  81. package/lib/common/array-utils.js +21 -0
  82. package/lib/common/array-utils.js.map +1 -1
  83. package/lib/common/event.d.ts +5 -0
  84. package/lib/common/event.d.ts.map +1 -1
  85. package/lib/common/event.js +5 -1
  86. package/lib/common/event.js.map +1 -1
  87. package/lib/common/menu/menu-model-registry.d.ts +12 -1
  88. package/lib/common/menu/menu-model-registry.d.ts.map +1 -1
  89. package/lib/common/menu/menu-model-registry.js +46 -0
  90. package/lib/common/menu/menu-model-registry.js.map +1 -1
  91. package/lib/common/performance/measurement.d.ts +21 -0
  92. package/lib/common/performance/measurement.d.ts.map +1 -1
  93. package/lib/common/performance/stopwatch.d.ts +10 -2
  94. package/lib/common/performance/stopwatch.d.ts.map +1 -1
  95. package/lib/common/performance/stopwatch.js +34 -11
  96. package/lib/common/performance/stopwatch.js.map +1 -1
  97. package/lib/common/promise-util.d.ts +4 -0
  98. package/lib/common/promise-util.d.ts.map +1 -1
  99. package/lib/common/promise-util.js +11 -1
  100. package/lib/common/promise-util.js.map +1 -1
  101. package/lib/common/promise-util.spec.js +26 -12
  102. package/lib/common/promise-util.spec.js.map +1 -1
  103. package/lib/common/types.d.ts +4 -0
  104. package/lib/common/types.d.ts.map +1 -1
  105. package/lib/common/types.js +16 -1
  106. package/lib/common/types.js.map +1 -1
  107. package/lib/common/uri.d.ts +1 -0
  108. package/lib/common/uri.d.ts.map +1 -1
  109. package/lib/common/uri.js +3 -0
  110. package/lib/common/uri.js.map +1 -1
  111. package/lib/electron-browser/menu/electron-context-menu-renderer.js +2 -2
  112. package/lib/electron-browser/menu/electron-context-menu-renderer.js.map +1 -1
  113. package/lib/electron-browser/menu/electron-main-menu-factory.d.ts +1 -1
  114. package/lib/electron-browser/menu/electron-main-menu-factory.d.ts.map +1 -1
  115. package/lib/electron-browser/menu/electron-main-menu-factory.js +2 -2
  116. package/lib/electron-browser/menu/electron-main-menu-factory.js.map +1 -1
  117. package/lib/electron-main/electron-main-application.d.ts.map +1 -1
  118. package/lib/electron-main/electron-main-application.js +3 -0
  119. package/lib/electron-main/electron-main-application.js.map +1 -1
  120. package/lib/node/backend-application.d.ts +5 -1
  121. package/lib/node/backend-application.d.ts.map +1 -1
  122. package/lib/node/backend-application.js +28 -5
  123. package/lib/node/backend-application.js.map +1 -1
  124. package/lib/node/logger-cli-contribution.spec.js +1 -1
  125. package/lib/node/logger-cli-contribution.spec.js.map +1 -1
  126. package/lib/node/main.d.ts +2 -5
  127. package/lib/node/main.d.ts.map +1 -1
  128. package/lib/node/main.js.map +1 -1
  129. package/lib/node/performance/node-stopwatch.js +1 -1
  130. package/lib/node/performance/node-stopwatch.js.map +1 -1
  131. package/package.json +7 -7
  132. package/src/browser/common-frontend-contribution.ts +107 -17
  133. package/src/browser/context-menu-renderer.ts +5 -0
  134. package/src/browser/label-parser.spec.ts +38 -0
  135. package/src/browser/label-parser.ts +15 -0
  136. package/src/browser/menu/browser-context-menu-renderer.ts +2 -2
  137. package/src/browser/menu/browser-menu-plugin.ts +2 -2
  138. package/src/browser/performance/frontend-stopwatch.ts +5 -2
  139. package/src/browser/preferences/preference-contribution.ts +49 -24
  140. package/src/browser/saveable.ts +41 -2
  141. package/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.ts +94 -8
  142. package/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar-types.ts +28 -1
  143. package/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar.tsx +87 -8
  144. package/src/browser/shell/tab-bars.ts +8 -1
  145. package/src/browser/style/tabs.css +32 -2
  146. package/src/browser/tree/tree-model.ts +8 -0
  147. package/src/browser/tree/tree-widget.tsx +66 -0
  148. package/src/browser/tree/tree.ts +27 -0
  149. package/src/browser/widgets/enhanced-preview-widget.ts +27 -0
  150. package/src/browser/window-contribution.ts +1 -1
  151. package/src/common/array-utils.ts +20 -0
  152. package/src/common/event.ts +12 -2
  153. package/src/common/i18n/nls.metadata.json +5616 -5359
  154. package/src/common/menu/menu-model-registry.ts +50 -0
  155. package/src/common/performance/measurement.ts +26 -0
  156. package/src/common/performance/stopwatch.ts +38 -12
  157. package/src/common/promise-util.spec.ts +43 -12
  158. package/src/common/promise-util.ts +12 -0
  159. package/src/common/types.ts +17 -0
  160. package/src/common/uri.ts +4 -0
  161. package/src/electron-browser/menu/electron-context-menu-renderer.ts +2 -2
  162. package/src/electron-browser/menu/electron-main-menu-factory.ts +2 -2
  163. package/src/electron-main/electron-main-application.ts +3 -0
  164. package/src/node/backend-application.ts +30 -5
  165. package/src/node/logger-cli-contribution.spec.ts +1 -1
  166. package/src/node/main.ts +1 -6
  167. package/src/node/performance/node-stopwatch.ts +1 -1
@@ -254,6 +254,56 @@ export class MenuModelRegistry {
254
254
  return this.findGroup(menuPath);
255
255
  }
256
256
 
257
+ /**
258
+ * Checks the given menu model whether it will show a menu with a single submenu.
259
+ *
260
+ * @param fullMenuModel the menu model to analyze
261
+ * @param menuPath the menu's path
262
+ * @returns if the menu will show a single submenu this returns a menu that will show the child elements of the submenu,
263
+ * otherwise the given `fullMenuModel` is return
264
+ */
265
+ removeSingleRootNode(fullMenuModel: MutableCompoundMenuNode, menuPath: MenuPath): CompoundMenuNode {
266
+ // check whether all children are compound menus and that there is only one child that has further children
267
+ if (!this.allChildrenCompound(fullMenuModel.children)) {
268
+ return fullMenuModel;
269
+ }
270
+ let nonEmptyNode = undefined;
271
+ for (const child of fullMenuModel.children) {
272
+ if (!this.isEmpty(child.children || [])) {
273
+ if (nonEmptyNode === undefined) {
274
+ nonEmptyNode = child;
275
+ } else {
276
+ return fullMenuModel;
277
+ }
278
+ }
279
+ }
280
+
281
+ if (CompoundMenuNode.is(nonEmptyNode) && nonEmptyNode.children.length === 1 && CompoundMenuNode.is(nonEmptyNode.children[0])) {
282
+ nonEmptyNode = nonEmptyNode.children[0];
283
+ }
284
+
285
+ return CompoundMenuNode.is(nonEmptyNode) ? nonEmptyNode : fullMenuModel;
286
+ }
287
+
288
+ protected allChildrenCompound(children: ReadonlyArray<MenuNode>): boolean {
289
+ return children.every(CompoundMenuNode.is);
290
+ }
291
+
292
+ protected isEmpty(children: ReadonlyArray<MenuNode>): boolean {
293
+ if (children.length === 0) {
294
+ return true;
295
+ }
296
+ if (!this.allChildrenCompound(children)) {
297
+ return false;
298
+ }
299
+ for (const child of children) {
300
+ if (!this.isEmpty(child.children || [])) {
301
+ return false;
302
+ }
303
+ }
304
+ return true;
305
+ }
306
+
257
307
  /**
258
308
  * Returns the {@link MenuPath path} at which a given menu node can be accessed from this registry, if it can be determined.
259
309
  * Returns `undefined` if the `parent` of any node in the chain is unknown.
@@ -101,4 +101,30 @@ export interface MeasurementOptions {
101
101
  * @see {@link thresholdLogLevel}
102
102
  */
103
103
  thresholdMillis?: number;
104
+
105
+ /**
106
+ * Flag to indicate whether the stopwatch should store measurement results for later retrieval.
107
+ * For example the cache can be used to retrieve measurements which were taken during startup before a listener had a chance to register.
108
+ */
109
+ storeResults?: boolean
110
+ }
111
+
112
+ /**
113
+ * Captures the result of a {@link Measurement} in a serializable format.
114
+ */
115
+ export interface MeasurementResult {
116
+ /** The measurement name. This may show up in the performance measurement framework appropriate to the application context. */
117
+ name: string;
118
+
119
+ /** The time when the measurement recording has been started */
120
+ startTime: number;
121
+
122
+ /**
123
+ * The elapsed time measured, if it has been {@link stop stopped} and measured, or `NaN` if the platform disabled
124
+ * performance measurement.
125
+ */
126
+ elapsed: number;
127
+
128
+ /** An optional label for the application the start of which (in real time) is the basis of all measurements. */
129
+ owner?: string;
104
130
  }
@@ -19,7 +19,8 @@
19
19
  import { inject, injectable } from 'inversify';
20
20
  import { ILogger, LogLevel } from '../logger';
21
21
  import { MaybePromise } from '../types';
22
- import { Measurement, MeasurementOptions } from './measurement';
22
+ import { Measurement, MeasurementOptions, MeasurementResult } from './measurement';
23
+ import { Emitter, Event } from '../event';
23
24
 
24
25
  /** The default log level for measurements that are not otherwise configured with a default. */
25
26
  const DEFAULT_LOG_LEVEL = LogLevel.INFO;
@@ -50,10 +51,20 @@ export abstract class Stopwatch {
50
51
  @inject(ILogger)
51
52
  protected readonly logger: ILogger;
52
53
 
54
+ protected _storedMeasurements: MeasurementResult[] = [];
55
+
56
+ protected onDidAddMeasurementResultEmitter = new Emitter<MeasurementResult>();
57
+ get onDidAddMeasurementResult(): Event<MeasurementResult> {
58
+ return this.onDidAddMeasurementResultEmitter.event;
59
+ }
60
+
53
61
  constructor(protected readonly defaultLogOptions: LogOptions) {
54
62
  if (!defaultLogOptions.defaultLogLevel) {
55
63
  defaultLogOptions.defaultLogLevel = DEFAULT_LOG_LEVEL;
56
64
  }
65
+ if (defaultLogOptions.storeResults === undefined) {
66
+ defaultLogOptions.storeResults = true;
67
+ }
57
68
  }
58
69
 
59
70
  /**
@@ -91,25 +102,36 @@ export abstract class Stopwatch {
91
102
  return result;
92
103
  }
93
104
 
94
- protected createMeasurement(name: string, measurement: () => number, options?: MeasurementOptions): Measurement {
105
+ protected createMeasurement(name: string, measure: () => { startTime: number, duration: number }, options?: MeasurementOptions): Measurement {
95
106
  const logOptions = this.mergeLogOptions(options);
96
107
 
97
- const result: Measurement = {
108
+ const measurement: Measurement = {
98
109
  name,
99
110
  stop: () => {
100
- if (result.elapsed === undefined) {
101
- result.elapsed = measurement();
111
+ if (measurement.elapsed === undefined) {
112
+ const { startTime, duration } = measure();
113
+ measurement.elapsed = duration;
114
+ const result: MeasurementResult = {
115
+ name,
116
+ elapsed: duration,
117
+ startTime,
118
+ owner: logOptions.owner
119
+ };
120
+ if (logOptions.storeResults) {
121
+ this._storedMeasurements.push(result);
122
+ }
123
+ this.onDidAddMeasurementResultEmitter.fire(result);
102
124
  }
103
- return result.elapsed;
125
+ return measurement.elapsed;
104
126
  },
105
- log: (activity: string, ...optionalArgs: any[]) => this.log(result, activity, this.atLevel(logOptions, undefined, optionalArgs)),
106
- debug: (activity: string, ...optionalArgs: any[]) => this.log(result, activity, this.atLevel(logOptions, LogLevel.DEBUG, optionalArgs)),
107
- info: (activity: string, ...optionalArgs: any[]) => this.log(result, activity, this.atLevel(logOptions, LogLevel.INFO, optionalArgs)),
108
- warn: (activity: string, ...optionalArgs: any[]) => this.log(result, activity, this.atLevel(logOptions, LogLevel.WARN, optionalArgs)),
109
- error: (activity: string, ...optionalArgs: any[]) => this.log(result, activity, this.atLevel(logOptions, LogLevel.ERROR, optionalArgs)),
127
+ log: (activity: string, ...optionalArgs: any[]) => this.log(measurement, activity, this.atLevel(logOptions, undefined, optionalArgs)),
128
+ debug: (activity: string, ...optionalArgs: any[]) => this.log(measurement, activity, this.atLevel(logOptions, LogLevel.DEBUG, optionalArgs)),
129
+ info: (activity: string, ...optionalArgs: any[]) => this.log(measurement, activity, this.atLevel(logOptions, LogLevel.INFO, optionalArgs)),
130
+ warn: (activity: string, ...optionalArgs: any[]) => this.log(measurement, activity, this.atLevel(logOptions, LogLevel.WARN, optionalArgs)),
131
+ error: (activity: string, ...optionalArgs: any[]) => this.log(measurement, activity, this.atLevel(logOptions, LogLevel.ERROR, optionalArgs)),
110
132
  };
111
133
 
112
- return result;
134
+ return measurement;
113
135
  }
114
136
 
115
137
  protected mergeLogOptions(logOptions?: Partial<LogOptions>): LogOptions {
@@ -154,4 +176,8 @@ export abstract class Stopwatch {
154
176
  this.logger.log(level, `${whatWasMeasured}: ${elapsed.toFixed(1)} ms [${timeFromStart}]`, ...(options.arguments ?? []));
155
177
  }
156
178
 
179
+ get storedMeasurements(): ReadonlyArray<MeasurementResult> {
180
+ return this._storedMeasurements;
181
+ }
182
+
157
183
  }
@@ -13,22 +13,19 @@
13
13
  //
14
14
  // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
15
  // *****************************************************************************
16
- import * as assert from 'assert';
17
- import { waitForEvent } from './promise-util';
16
+ import * as assert from 'assert/strict';
17
+ import { firstTrue, waitForEvent } from './promise-util';
18
18
  import { Emitter } from './event';
19
+ import { CancellationError } from './cancellation';
19
20
 
20
21
  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
22
 
31
- describe('promise-util', () => {
23
+ describe('waitForEvent', () => {
24
+ it('should time out', async () => {
25
+ const emitter = new Emitter<string>();
26
+ await assert.rejects(waitForEvent(emitter.event, 1000), reason => reason instanceof CancellationError);
27
+ });
28
+
32
29
  it('should get event', async () => {
33
30
  const emitter = new Emitter<string>();
34
31
  setTimeout(() => {
@@ -38,4 +35,38 @@ describe('promise-util', () => {
38
35
  });
39
36
  });
40
37
 
38
+ describe('firstTrue', () => {
39
+ it('should resolve to false when the promises arg is empty', async () => {
40
+ const actual = await firstTrue();
41
+ assert.strictEqual(actual, false);
42
+ });
43
+
44
+ it('should resolve to true when the first promise resolves to true', async () => {
45
+ const signals: string[] = [];
46
+ const createPromise = (signal: string, timeout: number, result: boolean) =>
47
+ new Promise<boolean>(resolve => setTimeout(() => {
48
+ signals.push(signal);
49
+ resolve(result);
50
+ }, timeout));
51
+ const actual = await firstTrue(
52
+ createPromise('a', 10, false),
53
+ createPromise('b', 20, false),
54
+ createPromise('c', 30, true),
55
+ createPromise('d', 40, false),
56
+ createPromise('e', 50, true)
57
+ );
58
+ assert.strictEqual(actual, true);
59
+ assert.deepStrictEqual(signals, ['a', 'b', 'c']);
60
+ });
61
+
62
+ it('should reject when one of the promises rejects', async () => {
63
+ await assert.rejects(firstTrue(
64
+ new Promise<boolean>(resolve => setTimeout(() => resolve(false), 10)),
65
+ new Promise<boolean>(resolve => setTimeout(() => resolve(false), 20)),
66
+ new Promise<boolean>((_, reject) => setTimeout(() => reject(new Error('my test error')), 30)),
67
+ new Promise<boolean>(resolve => setTimeout(() => resolve(true), 40)),
68
+ ), /Error: my test error/);
69
+ });
70
+ });
71
+
41
72
  });
@@ -129,3 +129,15 @@ export function waitForEvent<T>(event: Event<T>, ms: number, thisArg?: any, disp
129
129
  export function isThenable<T>(obj: unknown): obj is Promise<T> {
130
130
  return isObject<Promise<unknown>>(obj) && isFunction(obj.then);
131
131
  }
132
+
133
+ /**
134
+ * Returns with a promise that waits until the first promise resolves to `true`.
135
+ */
136
+ // Based on https://stackoverflow.com/a/51160727/5529090
137
+ export function firstTrue(...promises: readonly Promise<boolean>[]): Promise<boolean> {
138
+ const newPromises = promises.map(promise => new Promise<boolean>(
139
+ (resolve, reject) => promise.then(result => result && resolve(true), reject)
140
+ ));
141
+ newPromises.push(Promise.all(promises).then(() => false));
142
+ return Promise.race(newPromises);
143
+ }
@@ -56,6 +56,23 @@ export function isFunction<T extends (...args: unknown[]) => unknown>(value: unk
56
56
  return typeof value === 'function';
57
57
  }
58
58
 
59
+ /**
60
+ * @returns whether the provided parameter is an empty JavaScript Object or not.
61
+ */
62
+ export function isEmptyObject(obj: unknown): obj is object {
63
+ if (!isObject(obj)) {
64
+ return false;
65
+ }
66
+
67
+ for (const key in obj) {
68
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
69
+ return false;
70
+ }
71
+ }
72
+
73
+ return true;
74
+ }
75
+
59
76
  export function isObject<T extends object>(value: unknown): value is UnknownObject<T> {
60
77
  // eslint-disable-next-line no-null/no-null
61
78
  return typeof value === 'object' && value !== null;
package/src/common/uri.ts CHANGED
@@ -27,6 +27,10 @@ export class URI {
27
27
  return new URI(Uri.file(path));
28
28
  }
29
29
 
30
+ public static isUri(uri: unknown): boolean {
31
+ return Uri.isUri(uri);
32
+ }
33
+
30
34
  private readonly codeUri: Uri;
31
35
  private _path: Path | undefined;
32
36
 
@@ -100,8 +100,8 @@ export class ElectronContextMenuRenderer extends BrowserContextMenuRenderer {
100
100
 
101
101
  protected override doRender(options: RenderContextMenuOptions): ContextMenuAccess {
102
102
  if (this.useNativeStyle) {
103
- const { menuPath, anchor, args, onHide, context, contextKeyService } = options;
104
- const menu = this.electronMenuFactory.createElectronContextMenu(menuPath, args, context, contextKeyService);
103
+ const { menuPath, anchor, args, onHide, context, contextKeyService, skipSingleRootNode } = options;
104
+ const menu = this.electronMenuFactory.createElectronContextMenu(menuPath, args, context, contextKeyService, skipSingleRootNode);
105
105
  const { x, y } = coordinateFromAnchor(anchor);
106
106
 
107
107
  const menuHandle = window.electronTheiaCore.popup(menu, x, y, () => {
@@ -125,8 +125,8 @@ export class ElectronMainMenuFactory extends BrowserMainMenuFactory {
125
125
  return undefined;
126
126
  }
127
127
 
128
- createElectronContextMenu(menuPath: MenuPath, args?: any[], context?: HTMLElement, contextKeyService?: ContextMatcher): MenuDto[] {
129
- const menuModel = this.menuProvider.getMenu(menuPath);
128
+ createElectronContextMenu(menuPath: MenuPath, args?: any[], context?: HTMLElement, contextKeyService?: ContextMatcher, skipSingleRootNode?: boolean): MenuDto[] {
129
+ const menuModel = skipSingleRootNode ? this.menuProvider.removeSingleRootNode(this.menuProvider.getMenu(menuPath), menuPath) : this.menuProvider.getMenu(menuPath);
130
130
  return this.fillMenuTemplate([], menuModel, args, { showDisabled: true, context, rootMenuPath: menuPath, contextKeyService });
131
131
  }
132
132
 
@@ -507,6 +507,9 @@ export class ElectronMainApplication {
507
507
  backendProcess.on('error', error => {
508
508
  reject(error);
509
509
  });
510
+ backendProcess.on('exit', () => {
511
+ reject(new Error('backend process exited'));
512
+ });
510
513
  app.on('quit', () => {
511
514
  // Only issue a kill signal if the backend process is running.
512
515
  // eslint-disable-next-line no-null/no-null
@@ -14,6 +14,7 @@
14
14
  // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
15
  // *****************************************************************************
16
16
 
17
+ import * as dns from 'dns';
17
18
  import * as path from 'path';
18
19
  import * as http from 'http';
19
20
  import * as https from 'https';
@@ -29,6 +30,8 @@ import { AddressInfo } from 'net';
29
30
  import { ApplicationPackage } from '@theia/application-package';
30
31
  import { ProcessUtils } from './process-utils';
31
32
 
33
+ export type DnsResultOrder = 'ipv4first' | 'verbatim' | 'nodeDefault';
34
+
32
35
  const APP_PROJECT_PATH = 'app-project-path';
33
36
 
34
37
  const TIMER_WARNING_THRESHOLD = 50;
@@ -36,6 +39,7 @@ const TIMER_WARNING_THRESHOLD = 50;
36
39
  const DEFAULT_PORT = environment.electron.is() ? 0 : 3000;
37
40
  const DEFAULT_HOST = 'localhost';
38
41
  const DEFAULT_SSL = false;
42
+ const DEFAULT_DNS_DEFAULT_RESULT_ORDER: DnsResultOrder = 'ipv4first';
39
43
 
40
44
  export const BackendApplicationServer = Symbol('BackendApplicationServer');
41
45
  /**
@@ -107,6 +111,7 @@ export class BackendApplicationCliContribution implements CliContribution {
107
111
 
108
112
  port: number;
109
113
  hostname: string | undefined;
114
+ dnsDefaultResultOrder: DnsResultOrder = DEFAULT_DNS_DEFAULT_RESULT_ORDER;
110
115
  ssl: boolean | undefined;
111
116
  cert: string | undefined;
112
117
  certkey: string | undefined;
@@ -119,6 +124,12 @@ export class BackendApplicationCliContribution implements CliContribution {
119
124
  conf.option('cert', { description: 'Path to SSL certificate.', type: 'string' });
120
125
  conf.option('certkey', { description: 'Path to SSL certificate key.', type: 'string' });
121
126
  conf.option(APP_PROJECT_PATH, { description: 'Sets the application project directory', default: this.appProjectPath() });
127
+ conf.option('dnsDefaultResultOrder', {
128
+ type: 'string',
129
+ description: 'Configure Node\'s DNS resolver default behavior, see https://nodejs.org/docs/latest-v18.x/api/dns.html#dnssetdefaultresultorderorder',
130
+ choices: ['ipv4first', 'verbatim', 'nodeDefault'],
131
+ default: DEFAULT_DNS_DEFAULT_RESULT_ORDER
132
+ });
122
133
  }
123
134
 
124
135
  setArguments(args: yargs.Arguments): void {
@@ -128,6 +139,7 @@ export class BackendApplicationCliContribution implements CliContribution {
128
139
  this.cert = args.cert as string;
129
140
  this.certkey = args.certkey as string;
130
141
  this.projectPath = args[APP_PROJECT_PATH] as string;
142
+ this.dnsDefaultResultOrder = args.dnsDefaultResultOrder as DnsResultOrder;
131
143
  }
132
144
 
133
145
  protected appProjectPath(): string {
@@ -234,9 +246,13 @@ export class BackendApplication {
234
246
  this.app.use(...handlers);
235
247
  }
236
248
 
237
- async start(aPort?: number, aHostname?: string): Promise<http.Server | https.Server> {
238
- const hostname = aHostname !== undefined ? aHostname : this.cliParams.hostname;
239
- const port = aPort !== undefined ? aPort : this.cliParams.port;
249
+ async start(port?: number, hostname?: string): Promise<http.Server | https.Server> {
250
+ hostname ??= this.cliParams.hostname;
251
+ port ??= this.cliParams.port;
252
+
253
+ if (this.cliParams.dnsDefaultResultOrder !== 'nodeDefault') {
254
+ dns.setDefaultResultOrder(this.cliParams.dnsDefaultResultOrder);
255
+ }
240
256
 
241
257
  const deferred = new Deferred<http.Server | https.Server>();
242
258
  let server: http.Server | https.Server;
@@ -279,8 +295,10 @@ export class BackendApplication {
279
295
  });
280
296
 
281
297
  server.listen(port, hostname, () => {
282
- const scheme = this.cliParams.ssl ? 'https' : 'http';
283
- console.info(`Theia app listening on ${scheme}://${hostname || 'localhost'}:${(server.address() as AddressInfo).port}.`);
298
+ // address should be defined at this point
299
+ const address = server.address()!;
300
+ const url = typeof address === 'string' ? address : this.getHttpUrl(address, this.cliParams.ssl);
301
+ console.info(`Theia app listening on ${url}.`);
284
302
  deferred.resolve(server);
285
303
  });
286
304
 
@@ -301,6 +319,13 @@ export class BackendApplication {
301
319
  return this.stopwatch.startAsync('server', 'Finished starting backend application', () => deferred.promise);
302
320
  }
303
321
 
322
+ protected getHttpUrl({ address, port, family }: AddressInfo, ssl?: boolean): string {
323
+ const scheme = ssl ? 'https' : 'http';
324
+ return family.toLowerCase() === 'ipv6'
325
+ ? `${scheme}://[${address}]:${port}`
326
+ : `${scheme}://${address}:${port}`;
327
+ }
328
+
304
329
  protected onStop(): void {
305
330
  console.info('>>> Stopping backend contributions...');
306
331
  for (const contrib of this.contributionsProvider.getContributions()) {
@@ -137,7 +137,7 @@ describe('log-level-cli-contribution', () => {
137
137
 
138
138
  const args: yargs.Arguments = yargs.parse(['--log-config', file.path]);
139
139
  await cli.setArguments(args);
140
- sinon.assert.calledWithMatch(consoleErrorSpy, 'Unexpected token { in JSON at position 1');
140
+ sinon.assert.calledWithMatch(consoleErrorSpy, 'Error reading log config file');
141
141
  });
142
142
 
143
143
  // Skip this test because it is flaky, sometimes we don't receive the event.
package/src/node/main.ts CHANGED
@@ -26,12 +26,7 @@ process.on('unhandledRejection', (reason, promise) => {
26
26
  throw reason;
27
27
  });
28
28
 
29
- export interface Address {
30
- readonly port: number;
31
- readonly address: string;
32
- }
33
-
34
- export async function start(serverModule: MaybePromise<http.Server | https.Server>): Promise<Address> {
29
+ export async function start(serverModule: MaybePromise<http.Server | https.Server>): Promise<AddressInfo> {
35
30
  const server = await serverModule;
36
31
  return server.address() as AddressInfo;
37
32
  }
@@ -33,7 +33,7 @@ export class NodeStopwatch extends Stopwatch {
33
33
 
34
34
  return this.createMeasurement(name, () => {
35
35
  const duration = performance.now() - startTime;
36
- return duration;
36
+ return { duration, startTime };
37
37
  }, options);
38
38
  }
39
39