@theia/core 1.43.0 → 1.44.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 (88) hide show
  1. package/README.md +6 -6
  2. package/lib/browser/common-frontend-contribution.d.ts +4 -0
  3. package/lib/browser/common-frontend-contribution.d.ts.map +1 -1
  4. package/lib/browser/common-frontend-contribution.js +16 -1
  5. package/lib/browser/common-frontend-contribution.js.map +1 -1
  6. package/lib/browser/frontend-application-module.d.ts.map +1 -1
  7. package/lib/browser/frontend-application-module.js +3 -0
  8. package/lib/browser/frontend-application-module.js.map +1 -1
  9. package/lib/browser/icon-theme-contribution.d.ts +1 -0
  10. package/lib/browser/icon-theme-contribution.d.ts.map +1 -1
  11. package/lib/browser/icon-theme-contribution.js +1 -0
  12. package/lib/browser/icon-theme-contribution.js.map +1 -1
  13. package/lib/browser/icon-theme-service.d.ts +1 -0
  14. package/lib/browser/icon-theme-service.d.ts.map +1 -1
  15. package/lib/browser/icon-theme-service.js.map +1 -1
  16. package/lib/browser/language-icon-provider.d.ts +15 -0
  17. package/lib/browser/language-icon-provider.d.ts.map +1 -0
  18. package/lib/browser/language-icon-provider.js +74 -0
  19. package/lib/browser/language-icon-provider.js.map +1 -0
  20. package/lib/browser/language-service.d.ts +22 -0
  21. package/lib/browser/language-service.d.ts.map +1 -1
  22. package/lib/browser/language-service.js +28 -0
  23. package/lib/browser/language-service.js.map +1 -1
  24. package/lib/browser/messaging/ws-connection-provider.js +1 -1
  25. package/lib/browser/messaging/ws-connection-provider.js.map +1 -1
  26. package/lib/common/contribution-filter/contribution-filter.d.ts +9 -0
  27. package/lib/common/contribution-filter/contribution-filter.d.ts.map +1 -1
  28. package/lib/common/event.js +1 -1
  29. package/lib/common/event.js.map +1 -1
  30. package/lib/common/nls.js +12 -1
  31. package/lib/common/nls.js.map +1 -1
  32. package/lib/electron-browser/preload.d.ts.map +1 -1
  33. package/lib/electron-browser/preload.js +1 -0
  34. package/lib/electron-browser/preload.js.map +1 -1
  35. package/lib/electron-browser/window/electron-secondary-window-service.d.ts +1 -1
  36. package/lib/electron-browser/window/electron-secondary-window-service.d.ts.map +1 -1
  37. package/lib/electron-browser/window/electron-window-preferences.js +1 -1
  38. package/lib/electron-browser/window/electron-window-preferences.js.map +1 -1
  39. package/lib/electron-common/electron-api.d.ts +1 -0
  40. package/lib/electron-common/electron-api.d.ts.map +1 -1
  41. package/lib/electron-common/electron-api.js.map +1 -1
  42. package/lib/electron-main/electron-main-application.d.ts +2 -0
  43. package/lib/electron-main/electron-main-application.d.ts.map +1 -1
  44. package/lib/electron-main/electron-main-application.js +26 -4
  45. package/lib/electron-main/electron-main-application.js.map +1 -1
  46. package/lib/node/backend-application.d.ts +2 -0
  47. package/lib/node/backend-application.d.ts.map +1 -1
  48. package/lib/node/backend-application.js +11 -9
  49. package/lib/node/backend-application.js.map +1 -1
  50. package/lib/node/cli.d.ts +1 -1
  51. package/lib/node/cli.d.ts.map +1 -1
  52. package/lib/node/cli.js +9 -5
  53. package/lib/node/cli.js.map +1 -1
  54. package/lib/node/cli.spec.js +3 -3
  55. package/lib/node/cli.spec.js.map +1 -1
  56. package/lib/node/env-variables/env-variables-server.d.ts +3 -0
  57. package/lib/node/env-variables/env-variables-server.d.ts.map +1 -1
  58. package/lib/node/env-variables/env-variables-server.js +25 -1
  59. package/lib/node/env-variables/env-variables-server.js.map +1 -1
  60. package/lib/node/i18n/localization-server.d.ts +1 -1
  61. package/lib/node/i18n/localization-server.d.ts.map +1 -1
  62. package/lib/node/messaging/ipc-protocol.js +1 -1
  63. package/lib/node/messaging/ipc-protocol.js.map +1 -1
  64. package/package.json +6 -6
  65. package/src/browser/common-frontend-contribution.ts +15 -0
  66. package/src/browser/frontend-application-module.ts +3 -0
  67. package/src/browser/icon-theme-contribution.ts +1 -0
  68. package/src/browser/icon-theme-service.ts +1 -0
  69. package/src/browser/language-icon-provider.ts +55 -0
  70. package/src/browser/language-service.ts +34 -0
  71. package/src/browser/messaging/ws-connection-provider.ts +1 -1
  72. package/src/browser/style/index.css +1 -0
  73. package/src/browser/style/os.css +87 -0
  74. package/src/common/contribution-filter/contribution-filter.ts +9 -0
  75. package/src/common/event.ts +1 -1
  76. package/src/common/i18n/nls.metadata.json +7143 -6953
  77. package/src/common/nls.ts +12 -1
  78. package/src/electron-browser/preload.ts +1 -0
  79. package/src/electron-browser/window/electron-secondary-window-service.ts +1 -1
  80. package/src/electron-browser/window/electron-window-preferences.ts +1 -1
  81. package/src/electron-common/electron-api.ts +1 -0
  82. package/src/electron-main/electron-main-application.ts +28 -5
  83. package/src/node/backend-application.ts +14 -11
  84. package/src/node/cli.spec.ts +3 -3
  85. package/src/node/cli.ts +9 -5
  86. package/src/node/env-variables/env-variables-server.ts +21 -1
  87. package/src/node/i18n/localization-server.ts +1 -1
  88. package/src/node/messaging/ipc-protocol.ts +1 -1
package/src/common/nls.ts CHANGED
@@ -81,6 +81,10 @@ interface NlsInfo {
81
81
 
82
82
  class LocalizationKeyProvider {
83
83
 
84
+ private preferredKeys = new Set([
85
+ // We only want the `File` translation used in the menu
86
+ 'vscode/fileActions.contribution/filesCategory'
87
+ ]);
84
88
  private data = this.buildData();
85
89
 
86
90
  get(defaultValue: string): string | undefined {
@@ -100,9 +104,16 @@ class LocalizationKeyProvider {
100
104
  const keys: NlsKeys = bundles.keys;
101
105
  const messages: Record<string, string[]> = bundles.messages;
102
106
  const data = new Map<string, string>();
107
+ const foundPreferredKeys = new Set<string>();
103
108
  const keysAndMessages = this.buildKeyMessageTuples(keys, messages);
104
109
  for (const { key, message } of keysAndMessages) {
105
- data.set(message, key);
110
+ if (!foundPreferredKeys.has(message)) {
111
+ data.set(message, key);
112
+ if (this.preferredKeys.has(key)) {
113
+ // Prevent messages with preferred keys to be overridden
114
+ foundPreferredKeys.add(message);
115
+ }
116
+ }
106
117
  }
107
118
  // Second pass adds each message again in upper case, if the message doesn't already exist in upper case
108
119
  // The second pass is needed to not accidentally override any translations which actually use the upper case message
@@ -207,6 +207,7 @@ const api: TheiaCoreAPI = {
207
207
  sendData: data => {
208
208
  ipcRenderer.send(CHANNEL_IPC_CONNECTION, data);
209
209
  },
210
+ useNativeElements: !('THEIA_ELECTRON_DISABLE_NATIVE_ELEMENTS' in process.env && process.env.THEIA_ELECTRON_DISABLE_NATIVE_ELEMENTS === '1')
210
211
  };
211
212
 
212
213
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -16,7 +16,7 @@
16
16
 
17
17
  import { injectable } from 'inversify';
18
18
  import { DefaultSecondaryWindowService } from '../../browser/window/default-secondary-window-service';
19
- import { ApplicationShell, ExtractableWidget } from 'src/browser';
19
+ import { ApplicationShell, ExtractableWidget } from '../../browser';
20
20
 
21
21
  @injectable()
22
22
  export class ElectronSecondaryWindowService extends DefaultSecondaryWindowService {
@@ -46,7 +46,7 @@ export const electronWindowPreferencesSchema: PreferenceSchema = {
46
46
  default: isWindows ? 'custom' : 'native',
47
47
  scope: 'application',
48
48
  // eslint-disable-next-line max-len
49
- description: nls.localizeByDefault('Adjust the appearance of the window title bar. On Linux and Windows, this setting also affects the application and context menu appearances. Changes require a full restart to apply.'),
49
+ description: nls.localizeByDefault('Adjust the appearance of the window title bar to be native by the OS or custom. On Linux and Windows, this setting also affects the application and context menu appearances. Changes require a full restart to apply.'),
50
50
  included: !isOSX
51
51
  },
52
52
  }
@@ -87,6 +87,7 @@ export interface TheiaCoreAPI {
87
87
 
88
88
  sendData(data: Uint8Array): void;
89
89
  onData(handler: (data: Uint8Array) => void): Disposable;
90
+ useNativeElements: boolean;
90
91
  }
91
92
 
92
93
  declare global {
@@ -20,6 +20,7 @@ import * as path from 'path';
20
20
  import { Argv } from 'yargs';
21
21
  import { AddressInfo } from 'net';
22
22
  import { promises as fs } from 'fs';
23
+ import { existsSync, mkdirSync } from 'fs-extra';
23
24
  import { fork, ForkOptions } from 'child_process';
24
25
  import { DefaultTheme, FrontendApplicationConfig } from '@theia/application-package/lib/application-props';
25
26
  import URI from '../common/uri';
@@ -171,6 +172,8 @@ export class ElectronMainApplication {
171
172
  @inject(TheiaElectronWindowFactory)
172
173
  protected readonly windowFactory: TheiaElectronWindowFactory;
173
174
 
175
+ protected isPortable = this.makePortable();
176
+
174
177
  protected readonly electronStore = new Storage<{
175
178
  windowstate?: TheiaBrowserWindowOptions
176
179
  }>();
@@ -194,7 +197,22 @@ export class ElectronMainApplication {
194
197
  return this._config;
195
198
  }
196
199
 
200
+ protected makePortable(): boolean {
201
+ const dataFolderPath = path.join(app.getAppPath(), 'data');
202
+ const appDataPath = path.join(dataFolderPath, 'app-data');
203
+ if (existsSync(dataFolderPath)) {
204
+ if (!existsSync(appDataPath)) {
205
+ mkdirSync(appDataPath);
206
+ }
207
+ app.setPath('userData', appDataPath);
208
+ return true;
209
+ } else {
210
+ return false;
211
+ }
212
+ }
213
+
197
214
  async start(config: FrontendApplicationConfig): Promise<void> {
215
+ const args = this.processArgv.getProcessArgvWithoutBin(process.argv);
198
216
  this.useNativeWindowFrame = this.getTitleBarStyle(config) === 'native';
199
217
  this._config = config;
200
218
  this.hookApplicationEvents();
@@ -206,12 +224,15 @@ export class ElectronMainApplication {
206
224
  await this.startContributions();
207
225
  await this.launch({
208
226
  secondInstance: false,
209
- argv: this.processArgv.getProcessArgvWithoutBin(process.argv),
227
+ argv: args,
210
228
  cwd: process.cwd()
211
229
  });
212
230
  }
213
231
 
214
232
  protected getTitleBarStyle(config: FrontendApplicationConfig): 'native' | 'custom' {
233
+ if ('THEIA_ELECTRON_DISABLE_NATIVE_ELEMENTS' in process.env && process.env.THEIA_ELECTRON_DISABLE_NATIVE_ELEMENTS === '1') {
234
+ return 'custom';
235
+ }
215
236
  if (isOSX) {
216
237
  return 'native';
217
238
  }
@@ -256,7 +277,9 @@ export class ElectronMainApplication {
256
277
  }
257
278
 
258
279
  protected showInitialWindow(): void {
259
- if (this.config.electron.showWindowEarly) {
280
+ if (this.config.electron.showWindowEarly &&
281
+ !('THEIA_ELECTRON_NO_EARLY_WINDOW' in process.env && process.env.THEIA_ELECTRON_NO_EARLY_WINDOW === '1')) {
282
+ console.log('Showing main window early');
260
283
  app.whenReady().then(async () => {
261
284
  const options = await this.getLastWindowOptions();
262
285
  this.initialWindow = await this.createWindow({ ...options });
@@ -461,7 +484,7 @@ export class ElectronMainApplication {
461
484
  */
462
485
  protected attachSaveWindowState(electronWindow: BrowserWindow): void {
463
486
  const windowStateListeners = new DisposableCollection();
464
- let delayedSaveTimeout: NodeJS.Timer | undefined;
487
+ let delayedSaveTimeout: NodeJS.Timeout | undefined;
465
488
  const saveWindowStateDelayed = () => {
466
489
  if (delayedSaveTimeout) {
467
490
  clearTimeout(delayedSaveTimeout);
@@ -547,8 +570,8 @@ export class ElectronMainApplication {
547
570
  backendProcess.on('error', error => {
548
571
  reject(error);
549
572
  });
550
- backendProcess.on('exit', () => {
551
- reject(new Error('backend process exited'));
573
+ backendProcess.on('exit', code => {
574
+ reject(code);
552
575
  });
553
576
  app.on('quit', () => {
554
577
  // Only issue a kill signal if the backend process is running.
@@ -170,6 +170,8 @@ export class BackendApplication {
170
170
  @inject(Stopwatch)
171
171
  protected readonly stopwatch: Stopwatch;
172
172
 
173
+ private _configured: Promise<void>;
174
+
173
175
  constructor(
174
176
  @inject(ContributionProvider) @named(BackendApplicationContribution)
175
177
  protected readonly contributionsProvider: ContributionProvider<BackendApplicationContribution>,
@@ -198,7 +200,7 @@ export class BackendApplication {
198
200
  }
199
201
 
200
202
  protected async initialize(): Promise<void> {
201
- for (const contribution of this.contributionsProvider.getContributions()) {
203
+ await Promise.all(this.contributionsProvider.getContributions().map(async contribution => {
202
204
  if (contribution.initialize) {
203
205
  try {
204
206
  await this.measure(contribution.constructor.name + '.initialize',
@@ -208,18 +210,20 @@ export class BackendApplication {
208
210
  console.error('Could not initialize contribution', error);
209
211
  }
210
212
  }
211
- }
213
+ }));
214
+ }
215
+
216
+ get configured(): Promise<void> {
217
+ return this._configured;
212
218
  }
213
219
 
214
220
  @postConstruct()
215
221
  protected init(): void {
216
- this.configure();
222
+ this._configured = this.configure();
217
223
  }
218
224
 
219
225
  protected async configure(): Promise<void> {
220
- // Do not await the initialization because contributions are expected to handle
221
- // concurrent initialize/configure in undefined order if they provide both
222
- this.initialize();
226
+ await this.initialize();
223
227
 
224
228
  this.app.get('*.js', this.serveGzipped.bind(this, 'text/javascript'));
225
229
  this.app.get('*.js.map', this.serveGzipped.bind(this, 'application/json'));
@@ -233,17 +237,16 @@ export class BackendApplication {
233
237
  this.app.get('*.woff', this.serveGzipped.bind(this, 'font/woff'));
234
238
  this.app.get('*.woff2', this.serveGzipped.bind(this, 'font/woff2'));
235
239
 
236
- for (const contribution of this.contributionsProvider.getContributions()) {
240
+ await Promise.all(this.contributionsProvider.getContributions().map(async contribution => {
237
241
  if (contribution.configure) {
238
242
  try {
239
- await this.measure(contribution.constructor.name + '.configure',
240
- () => contribution.configure!(this.app)
241
- );
243
+ await contribution.configure!(this.app);
242
244
  } catch (error) {
243
245
  console.error('Could not configure contribution', error);
244
246
  }
245
247
  }
246
- }
248
+ }));
249
+ console.info('configured all backend app contributions');
247
250
  }
248
251
 
249
252
  use(...handlers: express.Handler[]): void {
@@ -44,7 +44,7 @@ describe('CliManager', () => {
44
44
  value.resolve(args['foo'] as string);
45
45
  }
46
46
  });
47
- await manager.initializeCli(['-f', 'bla']);
47
+ await manager.initializeCli(['-f', 'bla'], () => Promise.resolve(), () => Promise.resolve());
48
48
  chai.assert.equal(await value.promise, 'bla');
49
49
  });
50
50
 
@@ -59,14 +59,14 @@ describe('CliManager', () => {
59
59
  value.resolve(args['bar'] as string);
60
60
  }
61
61
  });
62
- await manager.initializeCli(['--foo']);
62
+ await manager.initializeCli(['--foo'], () => Promise.resolve(), () => Promise.resolve());
63
63
  chai.assert.equal(await value.promise, 'my-default');
64
64
  });
65
65
 
66
66
  it('prints help and exits', async () =>
67
67
  assertExits(async () => {
68
68
  const manager = new TestCliManager();
69
- await manager.initializeCli(['--help']);
69
+ await manager.initializeCli(['--help'], () => Promise.resolve(), () => Promise.resolve());
70
70
  })
71
71
  );
72
72
  });
package/src/node/cli.ts CHANGED
@@ -35,7 +35,7 @@ export class CliManager {
35
35
  constructor(@inject(ContributionProvider) @named(CliContribution)
36
36
  protected readonly contributionsProvider: ContributionProvider<CliContribution>) { }
37
37
 
38
- async initializeCli(argv: string[]): Promise<void> {
38
+ async initializeCli<T>(argv: string[], postSetArguments: () => Promise<void>, defaultCommand: () => Promise<void>): Promise<void> {
39
39
  const pack = require('../../package.json');
40
40
  const version = pack.version;
41
41
  const command = yargs.version(version);
@@ -43,14 +43,18 @@ export class CliManager {
43
43
  for (const contrib of this.contributionsProvider.getContributions()) {
44
44
  contrib.configure(command);
45
45
  }
46
- const args = command
46
+ await command
47
47
  .detectLocale(false)
48
48
  .showHelpOnFail(false, 'Specify --help for available options')
49
49
  .help('help')
50
+ .middleware(async args => {
51
+ for (const contrib of this.contributionsProvider.getContributions()) {
52
+ await contrib.setArguments(args);
53
+ }
54
+ await postSetArguments();
55
+ })
56
+ .command('$0', false, () => { }, defaultCommand)
50
57
  .parse(argv);
51
- for (const contrib of this.contributionsProvider.getContributions()) {
52
- await contrib.setArguments(args);
53
- }
54
58
  }
55
59
 
56
60
  protected isExit(): boolean {
@@ -18,6 +18,7 @@ import { join } from 'path';
18
18
  import { homedir } from 'os';
19
19
  import { injectable } from 'inversify';
20
20
  import * as drivelist from 'drivelist';
21
+ import { pathExists, mkdir } from 'fs-extra';
21
22
  import { EnvVariable, EnvVariablesServer } from '../../common/env-variables';
22
23
  import { isWindows } from '../../common/os';
23
24
  import { FileUri } from '../file-uri';
@@ -28,6 +29,7 @@ export class EnvVariablesServerImpl implements EnvVariablesServer {
28
29
  protected readonly envs: { [key: string]: EnvVariable } = {};
29
30
  protected readonly homeDirUri = FileUri.create(homedir()).toString();
30
31
  protected readonly configDirUri: Promise<string>;
32
+ protected readonly pathExistenceCache: { [key: string]: boolean } = {};
31
33
 
32
34
  constructor() {
33
35
  this.configDirUri = this.createConfigDirUri();
@@ -43,7 +45,25 @@ export class EnvVariablesServerImpl implements EnvVariablesServer {
43
45
  }
44
46
 
45
47
  protected async createConfigDirUri(): Promise<string> {
46
- return FileUri.create(process.env.THEIA_CONFIG_DIR || join(homedir(), '.theia')).toString();
48
+ let dataFolderPath: string = '';
49
+ if (process.env.THEIA_APP_PROJECT_PATH) {
50
+ dataFolderPath = join(process.env.THEIA_APP_PROJECT_PATH, 'data');
51
+ }
52
+ const userDataPath = join(dataFolderPath, 'user-data');
53
+ const dataFolderExists = this.pathExistenceCache[dataFolderPath] ??= await pathExists(dataFolderPath);
54
+ if (dataFolderExists) {
55
+ const userDataExists = this.pathExistenceCache[userDataPath] ??= await pathExists(userDataPath);
56
+ if (userDataExists) {
57
+ process.env.THEIA_CONFIG_DIR = userDataPath;
58
+ } else {
59
+ await mkdir(userDataPath);
60
+ process.env.THEIA_CONFIG_DIR = userDataPath;
61
+ this.pathExistenceCache[userDataPath] = true;
62
+ }
63
+ } else {
64
+ process.env.THEIA_CONFIG_DIR = join(homedir(), '.theia');
65
+ }
66
+ return FileUri.create(process.env.THEIA_CONFIG_DIR).toString();
47
67
  }
48
68
 
49
69
  async getExecPath(): Promise<string> {
@@ -15,7 +15,7 @@
15
15
  // *****************************************************************************
16
16
 
17
17
  import { inject, injectable } from 'inversify';
18
- import { Localization } from 'src/common/i18n/localization';
18
+ import { Localization } from '../../common/i18n/localization';
19
19
  import { LocalizationServer } from '../../common/i18n/localization-server';
20
20
  import { nls } from '../../common/nls';
21
21
  import { Deferred } from '../../common/promise-util';
@@ -51,7 +51,7 @@ export function checkParentAlive(): void {
51
51
  } catch {
52
52
  process.exit();
53
53
  }
54
- }, 5000);
54
+ }, 5000).unref(); // we don't want this timeout to keep the process alive
55
55
  }
56
56
  }
57
57
  }