@theia/plugin-ext 1.75.0-next.21 → 1.75.0-next.28

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 (73) hide show
  1. package/lib/common/errors.d.ts +1 -14
  2. package/lib/common/errors.d.ts.map +1 -1
  3. package/lib/common/errors.js +3 -16
  4. package/lib/common/errors.js.map +1 -1
  5. package/lib/common/plugin-protocol.d.ts +45 -648
  6. package/lib/common/plugin-protocol.d.ts.map +1 -1
  7. package/lib/common/plugin-protocol.js +11 -34
  8. package/lib/common/plugin-protocol.js.map +1 -1
  9. package/lib/hosted/browser/worker/plugin-manifest-loader.d.ts.map +1 -1
  10. package/lib/hosted/browser/worker/plugin-manifest-loader.js +5 -36
  11. package/lib/hosted/browser/worker/plugin-manifest-loader.js.map +1 -1
  12. package/lib/hosted/browser/worker/worker-main.js +2 -1
  13. package/lib/hosted/browser/worker/worker-main.js.map +1 -1
  14. package/lib/hosted/node/hosted-plugin-localization-service.d.ts.map +1 -1
  15. package/lib/hosted/node/hosted-plugin-localization-service.js +4 -68
  16. package/lib/hosted/node/hosted-plugin-localization-service.js.map +1 -1
  17. package/lib/hosted/node/plugin-host-rpc.d.ts.map +1 -1
  18. package/lib/hosted/node/plugin-host-rpc.js +2 -2
  19. package/lib/hosted/node/plugin-host-rpc.js.map +1 -1
  20. package/lib/hosted/node/plugin-reader.d.ts.map +1 -1
  21. package/lib/hosted/node/plugin-reader.js +4 -3
  22. package/lib/hosted/node/plugin-reader.js.map +1 -1
  23. package/lib/hosted/node/scanners/grammars-reader.d.ts +3 -2
  24. package/lib/hosted/node/scanners/grammars-reader.d.ts.map +1 -1
  25. package/lib/hosted/node/scanners/grammars-reader.js +5 -23
  26. package/lib/hosted/node/scanners/grammars-reader.js.map +1 -1
  27. package/lib/hosted/node/scanners/scanner-theia.d.ts +38 -71
  28. package/lib/hosted/node/scanners/scanner-theia.d.ts.map +1 -1
  29. package/lib/hosted/node/scanners/scanner-theia.js +117 -768
  30. package/lib/hosted/node/scanners/scanner-theia.js.map +1 -1
  31. package/lib/hosted/node/scanners/scanner-theia.spec.js +191 -0
  32. package/lib/hosted/node/scanners/scanner-theia.spec.js.map +1 -1
  33. package/lib/main/browser/plugin-contribution-handler.js +1 -1
  34. package/lib/main/browser/plugin-contribution-handler.js.map +1 -1
  35. package/lib/main/browser/plugin-icon-service.d.ts.map +1 -1
  36. package/lib/main/browser/plugin-icon-service.js +2 -1
  37. package/lib/main/browser/plugin-icon-service.js.map +1 -1
  38. package/lib/main/browser/plugin-icon-theme-service.d.ts.map +1 -1
  39. package/lib/main/browser/plugin-icon-theme-service.js +2 -1
  40. package/lib/main/browser/plugin-icon-theme-service.js.map +1 -1
  41. package/lib/main/browser/plugin-shared-style.d.ts.map +1 -1
  42. package/lib/main/browser/plugin-shared-style.js +2 -1
  43. package/lib/main/browser/plugin-shared-style.js.map +1 -1
  44. package/package.json +31 -30
  45. package/src/common/errors.ts +1 -25
  46. package/src/common/plugin-protocol.ts +195 -662
  47. package/src/hosted/browser/worker/plugin-manifest-loader.ts +8 -41
  48. package/src/hosted/browser/worker/worker-main.ts +2 -1
  49. package/src/hosted/node/hosted-plugin-localization-service.ts +3 -82
  50. package/src/hosted/node/plugin-host-rpc.ts +3 -3
  51. package/src/hosted/node/plugin-reader.ts +4 -3
  52. package/src/hosted/node/scanners/grammars-reader.ts +14 -23
  53. package/src/hosted/node/scanners/scanner-theia.spec.ts +225 -1
  54. package/src/hosted/node/scanners/scanner-theia.ts +204 -881
  55. package/src/main/browser/plugin-contribution-handler.ts +1 -1
  56. package/src/main/browser/plugin-icon-service.ts +2 -1
  57. package/src/main/browser/plugin-icon-theme-service.ts +2 -1
  58. package/src/main/browser/plugin-shared-style.ts +2 -1
  59. package/lib/hosted/node/plugin-activation-events.d.ts +0 -7
  60. package/lib/hosted/node/plugin-activation-events.d.ts.map +0 -1
  61. package/lib/hosted/node/plugin-activation-events.js +0 -95
  62. package/lib/hosted/node/plugin-activation-events.js.map +0 -1
  63. package/lib/hosted/node/plugin-manifest-loader.d.ts +0 -3
  64. package/lib/hosted/node/plugin-manifest-loader.d.ts.map +0 -1
  65. package/lib/hosted/node/plugin-manifest-loader.js +0 -34
  66. package/lib/hosted/node/plugin-manifest-loader.js.map +0 -1
  67. package/lib/main/node/errors.spec.d.ts +0 -2
  68. package/lib/main/node/errors.spec.d.ts.map +0 -1
  69. package/lib/main/node/errors.spec.js +0 -36
  70. package/lib/main/node/errors.spec.js.map +0 -1
  71. package/src/hosted/node/plugin-activation-events.ts +0 -112
  72. package/src/hosted/node/plugin-manifest-loader.ts +0 -32
  73. package/src/main/node/errors.spec.ts +0 -37
@@ -18,151 +18,104 @@
18
18
 
19
19
  import { inject, injectable, unmanaged, named } from '@theia/core/shared/inversify';
20
20
  import {
21
- AutoClosingPair,
22
- AutoClosingPairConditional,
23
- buildFrontendModuleName,
21
+ CustomEditor,
24
22
  DebuggerContribution,
25
- IconThemeContribution,
26
23
  IconContribution,
24
+ IconThemeContribution,
27
25
  IconUrl,
28
26
  Keybinding,
29
- LanguageConfiguration,
30
27
  LanguageContribution,
28
+ Localization,
31
29
  Menu,
32
30
  PluginCommand,
33
31
  PluginContribution,
34
32
  PluginEngine,
33
+ PluginEntryPoint,
35
34
  PluginLifecycle,
36
35
  PluginModel,
37
36
  PluginPackage,
38
37
  PluginPackageCommand,
38
+ PluginPackageCustomEditor,
39
39
  PluginPackageDebuggersContribution,
40
40
  PluginPackageKeybinding,
41
41
  PluginPackageLanguageContribution,
42
- PluginPackageLanguageContributionConfiguration,
42
+ PluginPackageLocalization,
43
43
  PluginPackageMenu,
44
44
  PluginPackageSubmenu,
45
+ PluginPackageTranslation,
45
46
  PluginPackageView,
46
47
  PluginPackageViewContainer,
47
48
  PluginPackageViewWelcome,
48
49
  PluginScanner,
49
50
  PluginTaskDefinitionContribution,
51
+ PluginIdentifiers,
50
52
  SnippetContribution,
51
53
  Submenu,
54
+ TerminalProfile,
52
55
  ThemeContribution,
56
+ Translation,
53
57
  View,
54
58
  ViewContainer,
55
59
  ViewWelcome,
56
- PluginPackageCustomEditor,
57
- CustomEditor,
58
- CustomEditorPriority,
59
- PluginPackageLocalization,
60
- Localization,
61
- PluginPackageTranslation,
62
- Translation,
63
- PluginIdentifiers,
64
- TerminalProfile,
65
- PluginIconContribution,
66
- PluginEntryPoint,
67
- PluginPackageContribution
60
+ WalkthroughContribution,
68
61
  } from '../../../common/plugin-protocol';
69
- import { promises as fs, readdirSync } from 'fs';
62
+ import { promises as fs } from 'fs';
70
63
  import * as path from 'path';
71
- import { isObject, isStringArray } from '@theia/core/lib/common/types';
72
- import { GrammarsReader } from './grammars-reader';
73
- import { CharacterPair } from '../../../common/plugin-api-rpc';
74
- import { isENOENT } from '../../../common/errors';
75
64
  import * as jsoncparser from 'jsonc-parser';
76
- import { IJSONSchema, JsonType } from '@theia/core/lib/common/json-schema';
77
- import { JSONValue } from '@theia/core/shared/@lumino/coreutils';
78
- import { deepClone } from '@theia/core/lib/common/objects';
79
- import { PreferenceSchema, PreferenceDataProperty } from '@theia/core/lib/common/preferences/preference-schema';
80
- import { TaskDefinition } from '@theia/task/lib/common/task-protocol';
81
- import { ColorDefinition } from '@theia/core/lib/common/color';
82
- import { CSSIcon } from '@theia/core/lib/common/markdown-rendering/icon-utilities';
65
+ import { GrammarsReader } from './grammars-reader';
66
+ import { isENOENT } from '@theia/plugin-utils/lib/common/utils';
83
67
  import { PluginUriFactory } from './plugin-uri-factory';
84
- import { PreferenceScope } from '@theia/core/lib/common/preferences/preference-scope';
85
68
  import { ILogger } from '@theia/core';
69
+ import { IJSONSchema } from '@theia/core/lib/common/json-schema';
70
+ import { PreferenceSchema } from '@theia/core/lib/common/preferences/preference-schema';
71
+ import { PreferenceScope } from '@theia/core/lib/common/preferences/preference-scope';
72
+ import { TaskDefinition } from '@theia/task/lib/common/task-protocol';
73
+ import { AsyncLocalStorage } from 'node:async_hooks';
74
+ import {
75
+ extractPluginViewsIds as extractPluginViewsIdsShared,
76
+ getScope as getScopeShared,
77
+ normalizeContributions,
78
+ readColors as readColorsShared,
79
+ readCommand as readCommandShared,
80
+ readConfiguration as readConfigurationShared,
81
+ readCustomEditor as readCustomEditorShared,
82
+ readDebugger as readDebuggerShared,
83
+ readIcons as readIconsShared,
84
+ readIconThemes as readIconThemesShared,
85
+ readKeybinding as readKeybindingShared,
86
+ readLanguage as readLanguageShared,
87
+ readMenu as readMenuShared,
88
+ readSnippets as readSnippetsShared,
89
+ readSubmenu as readSubmenuShared,
90
+ readTaskDefinition as readTaskDefinitionShared,
91
+ readLocalization as readLocalizationShared,
92
+ readLocalizations as readLocalizationsShared,
93
+ readTerminals as readTerminalsShared,
94
+ readThemes as readThemesShared,
95
+ readTranslation as readTranslationShared,
96
+ readWalkthroughs as readWalkthroughsShared,
97
+ readView as readViewShared,
98
+ readViewContainer as readViewContainerShared,
99
+ readViewWelcome as readViewWelcomeShared,
100
+ toSchema as toSchemaShared,
101
+ transformIconUrl as transformIconUrlShared,
102
+ } from '@theia/plugin-utils/lib/node/normalize-contributions';
103
+ import type {
104
+ IConfigurationNode,
105
+ NormalizeContributionsContext,
106
+ } from '@theia/plugin-utils/lib/common/contribution-types';
107
+ import type { ColorDefinition } from '@theia/plugin-utils/lib/common/protocol-shims';
108
+ import {
109
+ applyTrustExtraction as applyTrustExtractionShared,
110
+ buildEntryPointForTheia as buildEntryPointForTheiaShared,
111
+ buildLifecycle,
112
+ buildModelForTheia as buildModelForTheiaShared,
113
+ toPluginUrl as toPluginUrlShared
114
+ } from '@theia/plugin-utils/lib/common/plugin-model';
115
+ import { getPluginRootFileUrl as getPluginRootFileUrlShared } from '@theia/plugin-utils/lib/node/plugin-model';
86
116
 
87
- const colorIdPattern = '^\\w+[.\\w+]*$';
88
- const iconIdPattern = `^${CSSIcon.iconNameSegment}(-${CSSIcon.iconNameSegment})+$`;
89
-
90
- /** Maps the view container locations used by VS Code extensions to the Theia shell locations. */
91
- const VIEW_CONTAINER_LOCATION_ALIASES: Record<string, string> = {
92
- activitybar: 'left',
93
- panel: 'bottom',
94
- secondarySidebar: 'right',
95
- auxiliarybar: 'right'
96
- };
97
-
98
- function getFileExtension(filePath: string): string {
99
- const index = filePath.lastIndexOf('.');
100
- return index === -1 ? '' : filePath.substring(index + 1);
101
- }
102
-
103
- /**
104
- * Mirrors VS Code's `ConfigurationRegistry.getDefaultValue(type)`: plugins expect typed-but-defaultless properties to read as a type-based default, not `undefined`.
105
- */
106
- function deriveDefaultForType(type: JsonType | JsonType[] | undefined): JSONValue {
107
- const t = Array.isArray(type) ? type[0] : type;
108
- switch (t) {
109
- case 'boolean': return false;
110
- case 'integer':
111
- case 'number': return 0;
112
- case 'string': return '';
113
- case 'array': return [];
114
- case 'object': return {};
115
- default: return null;
116
- }
117
- }
118
-
119
- type PluginPackageWithContributes = PluginPackage & { contributes: PluginPackageContribution };
120
-
121
- type ScopeString = 'machine-overridable' | 'window' | 'resource' | 'language-overridable' | 'application' | 'machine';
122
-
123
- type EditPresentationTypes = 'multilineText' | 'singleLineText';
124
- export interface IConfigurationPropertySchema extends IJSONSchema {
125
-
126
- scope?: ScopeString;
127
-
128
- /**
129
- * When `false` this property is excluded from the registry. Default is to include.
130
- */
131
- included?: boolean;
132
-
133
- /**
134
- * List of tags associated to the property.
135
- * - A tag can be used for filtering
136
- * - Use `experimental` tag for marking the setting as experimental.
137
- * - Use `onExP` tag for marking that the default of the setting can be changed by running experiments.
138
- */
139
- tags?: string[];
140
-
141
- /**
142
- * When specified, controls the presentation format of string settings.
143
- * Otherwise, the presentation format defaults to `singleline`.
144
- */
145
- editPresentation?: EditPresentationTypes;
146
-
147
- /**
148
- * When specified, gives an order number for the setting
149
- * within the settings editor. Otherwise, the setting is placed at the end.
150
- */
151
- order?: number;
152
-
153
- }
154
-
155
- export interface IExtensionInfo {
156
- id: string;
157
- displayName?: string;
158
- }
159
-
160
- export interface IConfigurationNode {
161
- title?: string;
162
- description?: string;
163
- properties?: Record<string, IConfigurationPropertySchema>;
164
- scope?: ScopeString;
165
- }
117
+ /** Async-local contribution ctx — safe for concurrent scans on the singleton scanner. */
118
+ const contributionCtxStorage = new AsyncLocalStorage<NormalizeContributionsContext<PluginPackage>>();
166
119
 
167
120
  @injectable()
168
121
  export abstract class AbstractPluginScanner implements PluginScanner {
@@ -184,34 +137,23 @@ export abstract class AbstractPluginScanner implements PluginScanner {
184
137
 
185
138
  getModel(plugin: PluginPackage): PluginModel {
186
139
  const publisher = plugin.publisher ?? PluginIdentifiers.UNPUBLISHED;
187
- const result: PluginModel = {
188
- packagePath: plugin.packagePath,
189
- packageUri: this.pluginUriFactory.createUri(plugin).toString(),
190
- // see id definition: https://github.com/microsoft/vscode/blob/15916055fe0cb9411a5f36119b3b012458fe0a1d/src/vs/platform/extensions/common/extensions.ts#L167-L169
191
- id: `${publisher.toLowerCase()}.${plugin.name.toLowerCase()}`,
192
- name: plugin.name,
140
+ const result = buildModelForTheiaShared({
141
+ ...plugin,
193
142
  publisher,
194
- version: plugin.version,
195
- displayName: plugin.displayName,
196
- description: plugin.description,
197
- l10n: plugin.l10n,
198
- engine: {
199
- type: this._apiType,
200
- version: plugin.engines[this._apiType]
201
- },
202
- entryPoint: this.getEntryPoint(plugin),
203
- licenseUrl: this.getLicenseUrl(plugin),
204
- readmeUrl: this.getReadmeUrl(plugin)
205
- };
143
+ packageUri: this.pluginUriFactory.createUri(plugin).toString(),
144
+ });
145
+
146
+ result.engine.type = this._apiType;
147
+ result.engine.version = plugin.engines[this._apiType] ?? '*';
148
+ result.entryPoint = this.getEntryPoint(plugin);
149
+ result.licenseUrl = this.getLicenseUrl(plugin);
150
+ result.readmeUrl = this.getReadmeUrl(plugin);
206
151
  this.applyTrustExtraction(plugin, result);
207
152
  return result;
208
153
  }
209
154
 
210
155
  protected applyTrustExtraction(plugin: PluginPackage, result: PluginModel): void {
211
- const untrustedWorkspacesSupport = plugin.capabilities?.untrustedWorkspaces?.supported;
212
- if (untrustedWorkspacesSupport !== undefined) {
213
- result.untrustedWorkspacesSupport = untrustedWorkspacesSupport;
214
- }
156
+ applyTrustExtractionShared(plugin, result);
215
157
  }
216
158
 
217
159
  protected getReadmeUrl(plugin: PluginPackage): string | undefined {
@@ -223,32 +165,16 @@ export abstract class AbstractPluginScanner implements PluginScanner {
223
165
  }
224
166
 
225
167
  protected getPluginRootFileUrl(plugin: PluginPackage, names: string[]): string | undefined {
226
- const nameSet = new Set(names.map(n => n.toLowerCase()));
227
- try {
228
- const dir = readdirSync(plugin.packagePath, { withFileTypes: true });
229
- for (const dirent of dir) {
230
- if (dirent.isFile() && nameSet.has(dirent.name.toLowerCase())) {
231
- return PluginPackage.toPluginUrl(plugin, dirent.name);
232
- }
233
- }
234
- } catch {
235
- return undefined;
236
- }
168
+ return getPluginRootFileUrlShared(plugin, names);
237
169
  }
238
170
 
239
171
  protected abstract getEntryPoint(plugin: PluginPackage): PluginEntryPoint;
240
172
 
241
173
  getLifecycle(plugin: PluginPackage): PluginLifecycle {
242
- const result: PluginLifecycle = {
243
- startMethod: 'start',
244
- stopMethod: 'stop',
245
- frontendModuleName: buildFrontendModuleName(plugin),
246
- };
247
-
174
+ const result = buildLifecycle(plugin, 'theiaPlugin');
248
175
  if (this._backendInitPath) {
249
176
  result.backendInitPath = path.join(__dirname, this._backendInitPath);
250
177
  }
251
-
252
178
  return result;
253
179
  }
254
180
 
@@ -270,10 +196,10 @@ export abstract class AbstractPluginScanner implements PluginScanner {
270
196
  return contributions;
271
197
  }
272
198
 
273
- return this.readContributions(rawPlugin as PluginPackageWithContributes, contributions);
199
+ return this.readContributions(rawPlugin, contributions);
274
200
  }
275
201
 
276
- protected async readContributions(rawPlugin: PluginPackageWithContributes, contributions: PluginContribution): Promise<PluginContribution> {
202
+ protected async readContributions(rawPlugin: PluginPackage, contributions: PluginContribution): Promise<PluginContribution> {
277
203
  return contributions;
278
204
  }
279
205
 
@@ -289,824 +215,221 @@ export class TheiaPluginScanner extends AbstractPluginScanner {
289
215
  protected readonly logger: ILogger;
290
216
 
291
217
  protected getEntryPoint(plugin: PluginPackage): PluginEntryPoint {
292
- const result: PluginEntryPoint = {
293
- frontend: plugin.theiaPlugin!.frontend,
294
- backend: plugin.theiaPlugin!.backend
218
+ return buildEntryPointForTheiaShared(plugin);
219
+ }
220
+
221
+ static getScope(monacoScope: string | undefined): { scope: PreferenceScope | undefined; overridable: boolean } {
222
+ return getScopeShared(monacoScope);
223
+ }
224
+
225
+ protected override async readContributions(rawPlugin: PluginPackage, contributions: PluginContribution): Promise<PluginContribution> {
226
+ const ctx = this.contributionCtx(rawPlugin);
227
+ return contributionCtxStorage.run(ctx, () => normalizeContributions(ctx, contributions));
228
+ }
229
+
230
+ protected contributionCtx(pck: PluginPackage): NormalizeContributionsContext<PluginPackage> {
231
+ const active = contributionCtxStorage.getStore();
232
+ if (active?.plugin === pck) {
233
+ return active;
234
+ }
235
+
236
+ const ctx: NormalizeContributionsContext<PluginPackage> = {
237
+ plugin: pck,
238
+ resolveUrl: relativePath => this.toPluginUrl(pck, relativePath),
239
+ resolveUri: (_plugin, relativePath) => this.pluginUriFactory.createUri(pck, relativePath).toString(),
240
+ onError: (type, err, detail) => this.logger.error(`Could not read '${pck.name}' contribution '${type}'.`, detail, err),
241
+ onWarn: msg => this.logger.warn(msg),
242
+ readJsonFile: filePath => this.readJson(filePath),
243
+ // Route through protected methods so Inversify/subclass overrides still apply.
244
+ transformIconUrl: (plugin, original) => this.transformIconUrl(plugin, original),
245
+ readLocalization: (entry, pluginPath) => this.readLocalization(entry, pluginPath),
246
+ readTranslation: (entry, pluginPath) => this.readTranslation(entry, pluginPath),
247
+ toSchema: definition => this.toSchema(definition),
248
+ readConfiguration: this.readConfiguration.bind(this),
249
+ readCustomEditors: this.readCustomEditors.bind(this),
250
+ readViews: this.readViews.bind(this),
251
+ readKeybinding: this.readKeybinding.bind(this),
252
+ readDebuggers: this.readDebuggers.bind(this),
253
+ readSubmenus: (submenus, plugin) => this.readSubmenus(submenus, plugin),
254
+ readViewsContainers: (containers, plugin) => this.readViewsContainers(containers, plugin),
255
+ readViewsWelcome: (welcome, views) => this.readViewsWelcome(welcome, views),
256
+ readCommand: (command, plugin) => this.readCommand(command, plugin),
257
+ readMenus: menus => this.readMenus(menus),
258
+ readTaskDefinition: (pluginName, definition) => this.readTaskDefinition(pluginName, definition),
259
+ readSnippets: plugin => this.readSnippets(plugin),
260
+ readThemes: plugin => this.readThemes(plugin),
261
+ readIcons: plugin => this.readIcons(plugin),
262
+ readIconThemes: plugin => this.readIconThemes(plugin),
263
+ readColors: plugin => this.readColors(plugin),
264
+ readTerminals: plugin => this.readTerminals(plugin),
265
+ readLocalizations: plugin => this.readLocalizations(plugin),
266
+ readWalkthroughs: plugin => this.readWalkthroughs(plugin),
267
+ readLanguages: (languages, plugin) => this.readLanguages(languages, plugin),
295
268
  };
296
- if (plugin.theiaPlugin?.headless) {
297
- result.headless = plugin.theiaPlugin.headless;
298
- }
299
- return result;
300
- }
301
-
302
- static getScope(monacoScope: string | undefined): { scope: PreferenceScope | undefined, overridable: boolean } {
303
- switch (monacoScope) {
304
- case 'machine-overridable':
305
- case 'window':
306
- case 'resource':
307
- return { scope: PreferenceScope.Folder, overridable: false };
308
- case 'language-overridable':
309
- return { scope: PreferenceScope.Folder, overridable: true };
310
- case 'application':
311
- case 'machine':
312
- return { scope: PreferenceScope.User, overridable: false };
313
- }
314
- return { scope: undefined, overridable: false };
315
- }
316
-
317
- protected override async readContributions(rawPlugin: PluginPackageWithContributes, contributions: PluginContribution): Promise<PluginContribution> {
318
- try {
319
- if (rawPlugin.contributes.configuration) {
320
- const configurations = Array.isArray(rawPlugin.contributes.configuration) ? rawPlugin.contributes.configuration : [rawPlugin.contributes.configuration];
321
- const hasMultipleConfigs = configurations.length > 1;
322
- contributions.configuration = [];
323
- for (const c of configurations) {
324
- const config = this.readConfiguration(c, rawPlugin.packagePath);
325
- if (config) {
326
- Object.values(config.properties).forEach(property => {
327
- if (hasMultipleConfigs) {
328
- // If there are multiple configuration contributions, we need to distinguish them by their title in the settings UI.
329
- // They are placed directly under the plugin's name in the settings UI.
330
- property.owner = rawPlugin.displayName;
331
- property.group = config.title;
332
- } else {
333
- // If there's only one configuration contribution, we display the title in the settings UI.
334
- property.owner = config.title;
335
- }
336
- });
337
- contributions.configuration.push(config);
338
- }
339
- }
340
- }
341
- } catch (err) {
342
- this.logger.error(`Could not read '${rawPlugin.name}' contribution 'configuration'.`, rawPlugin.contributes.configuration, err);
343
- }
344
-
345
- const configurationDefaults = rawPlugin.contributes.configurationDefaults;
346
- contributions.configurationDefaults = isObject(configurationDefaults) ? configurationDefaults : undefined;
347
-
348
- try {
349
- if (rawPlugin.contributes.submenus) {
350
- contributions.submenus = this.readSubmenus(rawPlugin.contributes.submenus, rawPlugin);
351
- }
352
- } catch (err) {
353
- this.logger.error(`Could not read '${rawPlugin.name}' contribution 'submenus'.`, rawPlugin.contributes.submenus, err);
354
- }
355
-
356
- try {
357
- if (rawPlugin.contributes.customEditors) {
358
- const customEditors = this.readCustomEditors(rawPlugin.contributes.customEditors);
359
- contributions.customEditors = customEditors;
360
- }
361
- } catch (err) {
362
- this.logger.error(`Could not read '${rawPlugin.name}' contribution 'customEditors'.`, rawPlugin.contributes.customEditors, err);
363
- }
364
-
365
- try {
366
- if (rawPlugin.contributes.viewsContainers) {
367
- const viewsContainers = rawPlugin.contributes.viewsContainers;
368
- contributions.viewsContainers = {};
369
-
370
- for (const location of Object.keys(viewsContainers)) {
371
- const containers = this.readViewsContainers(viewsContainers[location], rawPlugin);
372
- const loc = VIEW_CONTAINER_LOCATION_ALIASES[location] ?? location;
373
- if (contributions.viewsContainers[loc]) {
374
- contributions.viewsContainers[loc] = contributions.viewsContainers[loc].concat(containers);
375
- } else {
376
- contributions.viewsContainers[loc] = containers;
377
- }
378
- }
379
- }
380
- } catch (err) {
381
- this.logger.error(`Could not read '${rawPlugin.name}' contribution 'viewsContainers'.`, rawPlugin.contributes.viewsContainers, err);
382
- }
383
-
384
- try {
385
- if (rawPlugin.contributes.views) {
386
- contributions.views = {};
387
-
388
- for (const location of Object.keys(rawPlugin.contributes.views)) {
389
- const views = this.readViews(rawPlugin.contributes.views[location]);
390
- contributions.views[location] = views;
391
- }
392
- }
393
- } catch (err) {
394
- this.logger.error(`Could not read '${rawPlugin.name}' contribution 'views'.`, rawPlugin.contributes.views, err);
395
- }
396
-
397
- try {
398
- if (rawPlugin.contributes.viewsWelcome) {
399
- contributions.viewsWelcome = this.readViewsWelcome(rawPlugin.contributes!.viewsWelcome, rawPlugin.contributes.views);
400
- }
401
- } catch (err) {
402
- this.logger.error(`Could not read '${rawPlugin.name}' contribution 'viewsWelcome'.`, rawPlugin.contributes.viewsWelcome, err);
403
- }
404
-
405
- try {
406
- const pluginCommands = rawPlugin.contributes.commands;
407
- if (pluginCommands) {
408
- const commands = Array.isArray(pluginCommands) ? pluginCommands : [pluginCommands];
409
- contributions.commands = commands.map(command => this.readCommand(command, rawPlugin));
410
- }
411
- } catch (err) {
412
- this.logger.error(`Could not read '${rawPlugin.name}' contribution 'commands'.`, rawPlugin.contributes!.commands, err);
413
- }
414
-
415
- try {
416
- if (rawPlugin.contributes.menus) {
417
- contributions.menus = {};
418
-
419
- for (const location of Object.keys(rawPlugin.contributes.menus)) {
420
- const menus = this.readMenus(rawPlugin.contributes.menus[location]);
421
- contributions.menus[location] = menus;
422
- }
423
- }
424
- } catch (err) {
425
- this.logger.error(`Could not read '${rawPlugin.name}' contribution 'menus'.`, rawPlugin.contributes.menus, err);
426
- }
427
-
428
- try {
429
- if (rawPlugin.contributes.keybindings) {
430
- const rawKeybindings = Array.isArray(rawPlugin.contributes.keybindings) ? rawPlugin.contributes.keybindings : [rawPlugin.contributes.keybindings];
431
- contributions.keybindings = rawKeybindings.map(rawKeybinding => this.readKeybinding(rawKeybinding));
432
- }
433
- } catch (err) {
434
- this.logger.error(`Could not read '${rawPlugin.name}' contribution 'keybindings'.`, rawPlugin.contributes.keybindings, err);
435
- }
436
-
437
- try {
438
- if (rawPlugin.contributes.debuggers) {
439
- const debuggers = this.readDebuggers(rawPlugin.contributes.debuggers);
440
- contributions.debuggers = debuggers;
441
- }
442
- } catch (err) {
443
- this.logger.error(`Could not read '${rawPlugin.name}' contribution 'debuggers'.`, rawPlugin.contributes.debuggers, err);
444
- }
445
-
446
- try {
447
- if (rawPlugin.contributes.taskDefinitions) {
448
- const definitions = rawPlugin.contributes.taskDefinitions!;
449
- contributions.taskDefinitions = definitions.map(definitionContribution => this.readTaskDefinition(rawPlugin.name, definitionContribution));
450
- }
451
- } catch (err) {
452
- this.logger.error(`Could not read '${rawPlugin.name}' contribution 'taskDefinitions'.`, rawPlugin.contributes.taskDefinitions, err);
453
- }
454
-
455
- try {
456
- contributions.problemMatchers = rawPlugin.contributes.problemMatchers;
457
- } catch (err) {
458
- this.logger.error(`Could not read '${rawPlugin.name}' contribution 'problemMatchers'.`, rawPlugin.contributes.problemMatchers, err);
459
- }
460
-
461
- try {
462
- contributions.problemPatterns = rawPlugin.contributes.problemPatterns;
463
- } catch (err) {
464
- this.logger.error(`Could not read '${rawPlugin.name}' contribution 'problemPatterns'.`, rawPlugin.contributes.problemPatterns, err);
465
- }
466
-
467
- try {
468
- contributions.resourceLabelFormatters = rawPlugin.contributes.resourceLabelFormatters;
469
- } catch (err) {
470
- this.logger.error(`Could not read '${rawPlugin.name}' contribution 'resourceLabelFormatters'.`, rawPlugin.contributes.resourceLabelFormatters, err);
471
- }
472
-
473
- try {
474
- contributions.authentication = rawPlugin.contributes.authentication;
475
- } catch (err) {
476
- this.logger.error(`Could not read '${rawPlugin.name}' contribution 'authentication'.`, rawPlugin.contributes.authentication, err);
477
- }
478
-
479
- try {
480
- contributions.notebooks = rawPlugin.contributes.notebooks;
481
- } catch (err) {
482
- this.logger.error(`Could not read '${rawPlugin.name}' contribution 'notebooks'.`, rawPlugin.contributes.notebooks, err);
483
- }
484
-
485
- try {
486
- contributions.notebookRenderer = rawPlugin.contributes.notebookRenderer;
487
- } catch (err) {
488
- this.logger.error(`Could not read '${rawPlugin.name}' contribution 'notebook-renderer'.`, rawPlugin.contributes.notebookRenderer, err);
489
- }
490
-
491
- try {
492
- contributions.notebookPreload = rawPlugin.contributes.notebookPreload;
493
- } catch (err) {
494
- this.logger.error(`Could not read '${rawPlugin.name}' contribution 'notebooks-preload'.`, rawPlugin.contributes.notebookPreload, err);
495
- }
496
-
497
- try {
498
- contributions.snippets = this.readSnippets(rawPlugin);
499
- } catch (err) {
500
- this.logger.error(`Could not read '${rawPlugin.name}' contribution 'snippets'.`, rawPlugin.contributes!.snippets, err);
501
- }
502
-
503
- try {
504
- contributions.themes = this.readThemes(rawPlugin);
505
- } catch (err) {
506
- this.logger.error(`Could not read '${rawPlugin.name}' contribution 'themes'.`, rawPlugin.contributes.themes, err);
507
- }
508
-
509
- try {
510
- contributions.icons = this.readIcons(rawPlugin);
511
- } catch (err) {
512
- this.logger.error(`Could not read '${rawPlugin.name}' contribution 'icons'.`, rawPlugin.contributes.icons, err);
513
- }
514
-
515
- try {
516
- contributions.iconThemes = this.readIconThemes(rawPlugin);
517
- } catch (err) {
518
- this.logger.error(`Could not read '${rawPlugin.name}' contribution 'iconThemes'.`, rawPlugin.contributes.iconThemes, err);
519
- }
520
-
521
- try {
522
- contributions.colors = this.readColors(rawPlugin);
523
- } catch (err) {
524
- this.logger.error(`Could not read '${rawPlugin.name}' contribution 'colors'.`, rawPlugin.contributes.colors, err);
525
- }
526
-
527
- try {
528
- contributions.terminalProfiles = this.readTerminals(rawPlugin);
529
- } catch (err) {
530
- this.logger.error(`Could not read '${rawPlugin.name}' contribution 'terminals'.`, rawPlugin.contributes.terminal, err);
531
- }
532
-
533
- try {
534
- contributions.localizations = this.readLocalizations(rawPlugin);
535
- } catch (err) {
536
- this.logger.error(`Could not read '${rawPlugin.name}' contribution 'localizations'.`, rawPlugin.contributes.localizations, err);
537
- }
538
-
539
- const [languagesResult, grammarsResult] = await Promise.allSettled([
540
- rawPlugin.contributes.languages ? this.readLanguages(rawPlugin.contributes.languages, rawPlugin) : undefined,
541
- rawPlugin.contributes.grammars ? this.grammarsReader.readGrammars(rawPlugin.contributes.grammars, rawPlugin.packagePath) : undefined
542
- ]);
543
-
544
- if (rawPlugin.contributes.languages) {
545
- if (languagesResult.status === 'fulfilled') {
546
- contributions.languages = languagesResult.value;
547
- } else {
548
- this.logger.error(`Could not read '${rawPlugin.name}' contribution 'languages'.`, rawPlugin.contributes.languages, languagesResult.reason);
549
- }
550
- }
551
-
552
- if (rawPlugin.contributes.grammars) {
553
- if (grammarsResult.status === 'fulfilled') {
554
- contributions.grammars = grammarsResult.value;
555
- } else {
556
- this.logger.error(`Could not read '${rawPlugin.name}' contribution 'grammars'.`, rawPlugin.contributes.grammars, grammarsResult.reason);
557
- }
558
- }
559
-
560
- return contributions;
269
+ ctx.readGrammars = (grammars, pluginPath) => this.grammarsReader.readGrammars(
270
+ grammars,
271
+ pluginPath,
272
+ ctx
273
+ );
274
+ return ctx;
561
275
  }
562
276
 
563
277
  protected readTerminals(pck: PluginPackage): TerminalProfile[] | undefined {
564
- if (!pck?.contributes?.terminal?.profiles) {
565
- return undefined;
566
- }
567
- return pck.contributes.terminal.profiles.filter(profile => profile.id && profile.title);
278
+ return readTerminalsShared(pck);
568
279
  }
569
280
 
570
281
  protected readLocalizations(pck: PluginPackage): Localization[] | undefined {
571
- if (!pck.contributes || !pck.contributes.localizations) {
572
- return undefined;
573
- }
574
- return pck.contributes.localizations.map(e => this.readLocalization(e, pck.packagePath));
282
+ return readLocalizationsShared(pck, this.contributionCtx(pck));
575
283
  }
576
284
 
577
- protected readLocalization({ languageId, languageName, localizedLanguageName, translations }: PluginPackageLocalization, pluginPath: string): Localization {
578
- const local: Localization = {
579
- languageId,
580
- languageName,
581
- localizedLanguageName,
582
- translations: []
583
- };
584
- local.translations = translations.map(e => this.readTranslation(e, pluginPath));
585
- return local;
285
+ protected readLocalization(entry: PluginPackageLocalization, pluginPath: string): Localization {
286
+ return readLocalizationShared(entry, pluginPath, {
287
+ readTranslation: (translation, translationPath) => this.readTranslation(translation, translationPath),
288
+ });
586
289
  }
587
290
 
588
291
  protected readTranslation(packageTranslation: PluginPackageTranslation, pluginPath: string): Translation {
589
- const translation: Translation = {
590
- id: packageTranslation.id,
591
- path: packageTranslation.path
592
- };
593
- return translation;
292
+ return readTranslationShared(packageTranslation, pluginPath);
293
+ }
294
+
295
+ protected readWalkthroughs(pck: PluginPackage): WalkthroughContribution[] | undefined {
296
+ return readWalkthroughsShared(pck, this.contributionCtx(pck));
594
297
  }
595
298
 
596
- protected readCommand({ command, title, shortTitle, original, category, icon, enablement }: PluginPackageCommand, pck: PluginPackage): PluginCommand {
597
- const { themeIcon, iconUrl } = this.transformIconUrl(pck, icon) ?? {};
598
- return { command, title, shortTitle, originalTitle: original, category, iconUrl, themeIcon, enablement };
299
+ protected readCommand(command: PluginPackageCommand, pck: PluginPackage): PluginCommand {
300
+ return readCommandShared(this.contributionCtx(pck), command, pck);
599
301
  }
600
302
 
601
303
  protected transformIconUrl(plugin: PluginPackage, original?: IconUrl): { iconUrl?: IconUrl; themeIcon?: string } | undefined {
602
- if (original) {
603
- if (typeof original === 'string') {
604
- if (original.startsWith('$(')) {
605
- return { themeIcon: original };
606
- } else {
607
- return { iconUrl: this.toPluginUrl(plugin, original) };
608
- }
609
- } else {
610
- return {
611
- iconUrl: {
612
- light: this.toPluginUrl(plugin, original.light),
613
- dark: this.toPluginUrl(plugin, original.dark)
614
- }
615
- };
616
- }
617
- }
304
+ return transformIconUrlShared(this.contributionCtx(plugin), plugin, original);
618
305
  }
619
306
 
620
307
  protected toPluginUrl(pck: PluginPackage, relativePath: string): string {
621
- return PluginPackage.toPluginUrl(pck, relativePath);
308
+ return toPluginUrlShared(pck, relativePath);
622
309
  }
623
310
 
624
311
  protected readColors(pck: PluginPackage): ColorDefinition[] | undefined {
625
- if (!pck.contributes || !pck.contributes.colors) {
626
- return undefined;
627
- }
628
- const result: ColorDefinition[] = [];
629
- for (const contribution of pck.contributes.colors) {
630
- if (typeof contribution.id !== 'string' || contribution.id.length === 0) {
631
- this.logger.error("'configuration.colors.id' must be defined and can not be empty");
632
- continue;
633
- }
634
- if (!contribution.id.match(colorIdPattern)) {
635
- this.logger.error("'configuration.colors.id' must follow the word[.word]*");
636
- continue;
637
- }
638
- if (typeof contribution.description !== 'string' || contribution.id.length === 0) {
639
- this.logger.error("'configuration.colors.description' must be defined and can not be empty");
640
- continue;
641
- }
642
- const defaults = contribution.defaults;
643
- if (!defaults || typeof defaults !== 'object' || typeof defaults.light !== 'string' || typeof defaults.dark !== 'string' || typeof defaults.highContrast !== 'string') {
644
- this.logger.error("'configuration.colors.defaults' must be defined and must contain 'light', 'dark' and 'highContrast'");
645
- continue;
646
- }
647
- result.push({
648
- id: contribution.id,
649
- description: contribution.description,
650
- defaults: {
651
- light: defaults.light,
652
- dark: defaults.dark,
653
- hc: defaults.highContrast
654
- }
655
- });
656
- }
657
- return result;
312
+ return readColorsShared(this.contributionCtx(pck), pck);
658
313
  }
659
314
 
660
315
  protected readThemes(pck: PluginPackage): ThemeContribution[] | undefined {
661
- if (!pck.contributes || !pck.contributes.themes) {
662
- return undefined;
663
- }
664
- const result: ThemeContribution[] = [];
665
- for (const contribution of pck.contributes.themes) {
666
- if (contribution.path) {
667
- result.push({
668
- id: contribution.id,
669
- uri: this.pluginUriFactory.createUri(pck, contribution.path).toString(),
670
- description: contribution.description,
671
- label: contribution.label,
672
- uiTheme: contribution.uiTheme
673
- });
674
- }
675
- }
676
- return result;
316
+ return readThemesShared(this.contributionCtx(pck), pck);
677
317
  }
678
318
 
679
319
  protected readIconThemes(pck: PluginPackage): IconThemeContribution[] | undefined {
680
- if (!pck.contributes || !pck.contributes.iconThemes) {
681
- return undefined;
682
- }
683
- const result: IconThemeContribution[] = [];
684
- for (const contribution of pck.contributes.iconThemes) {
685
- if (typeof contribution.id !== 'string') {
686
- this.logger.error('Expected string in `contributes.iconThemes.id`. Provided value:', contribution.id);
687
- continue;
688
- }
689
- if (typeof contribution.path !== 'string') {
690
- this.logger.error('Expected string in `contributes.iconThemes.path`. Provided value:', contribution.path);
691
- continue;
692
- }
693
- result.push({
694
- id: contribution.id,
695
- uri: this.pluginUriFactory.createUri(pck, contribution.path).toString(),
696
- description: contribution.description,
697
- label: contribution.label,
698
- uiTheme: contribution.uiTheme
699
- });
700
- }
701
- return result;
320
+ return readIconThemesShared(this.contributionCtx(pck), pck);
702
321
  }
703
322
 
704
323
  protected readIcons(pck: PluginPackage): IconContribution[] | undefined {
705
- if (!pck.contributes || !pck.contributes.icons) {
706
- return undefined;
707
- }
708
- const result: IconContribution[] = [];
709
- const iconEntries = <PluginIconContribution>(<unknown>pck.contributes.icons);
710
- for (const id in iconEntries) {
711
- if (pck.contributes.icons.hasOwnProperty(id)) {
712
- if (!id.match(iconIdPattern)) {
713
- this.logger.error("'configuration.icons' keys represent the icon id and can only contain letter, digits and minuses. " +
714
- 'They need to consist of at least two segments in the form `component-iconname`.', 'extension: ', pck.name, 'icon id: ', id);
715
- return;
716
- }
717
- const iconContribution = iconEntries[id];
718
- if (typeof iconContribution.description !== 'string' || iconContribution.description['length'] === 0) {
719
- this.logger.error('configuration.icons.description must be defined and can not be empty, ', 'extension: ', pck.name, 'icon id: ', id);
720
- return;
721
- }
722
-
723
- const defaultIcon = iconContribution.default;
724
- if (typeof defaultIcon === 'string') {
725
- result.push({
726
- id,
727
- extensionId: pck.publisher + '.' + pck.name,
728
- description: iconContribution.description,
729
- defaults: { id: defaultIcon }
730
- });
731
- } else if (typeof defaultIcon === 'object' && typeof defaultIcon.fontPath === 'string' && typeof defaultIcon.fontCharacter === 'string') {
732
- const format = getFileExtension(defaultIcon.fontPath);
733
- if (['woff', 'woff2', 'ttf'].indexOf(format) === -1) {
734
- this.logger.warn("Expected `contributes.icons.default.fontPath` to have file extension 'woff', woff2' or 'ttf', is '{0}'.", format);
735
- return;
736
- }
737
-
738
- const iconFontLocation = this.pluginUriFactory.createUri(pck, defaultIcon.fontPath).toString();
739
- result.push({
740
- id,
741
- extensionId: pck.publisher + '.' + pck.name,
742
- description: iconContribution.description,
743
- defaults: {
744
- fontCharacter: defaultIcon.fontCharacter,
745
- location: iconFontLocation
746
- }
747
- });
748
- } else {
749
- this.logger.error("'configuration.icons.default' must be either a reference to the id of an other theme icon (string) or a icon definition (object) with ",
750
- 'properties `fontPath` and `fontCharacter`.');
751
- }
752
- }
753
- }
754
- return result;
324
+ return readIconsShared(this.contributionCtx(pck), pck);
755
325
  }
756
326
 
757
327
  protected readSnippets(pck: PluginPackage): SnippetContribution[] | undefined {
758
- if (!pck.contributes || !pck.contributes.snippets) {
759
- return undefined;
760
- }
761
- const result: SnippetContribution[] = [];
762
- for (const contribution of pck.contributes.snippets) {
763
- if (contribution.path) {
764
- result.push({
765
- language: contribution.language,
766
- source: pck.displayName || pck.name,
767
- uri: this.pluginUriFactory.createUri(pck, contribution.path).toString()
768
- });
769
- }
770
- }
771
- return result;
772
- }
773
-
774
- protected async readJson<T>(filePath: string): Promise<T | undefined> {
775
- const content = await this.readFile(filePath);
776
- return content ? jsoncparser.parse(content, undefined, { disallowComments: false }) : undefined;
777
- }
778
- protected async readFile(filePath: string): Promise<string> {
779
- try {
780
- const content = await fs.readFile(filePath, { encoding: 'utf8' });
781
- return content;
782
- } catch (e) {
783
- if (!isENOENT(e)) {
784
- this.logger.error(e);
785
- }
786
- return '';
787
- }
328
+ return readSnippetsShared(this.contributionCtx(pck), pck);
788
329
  }
789
330
 
790
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
791
- private readConfiguration(rawConfiguration: IConfigurationNode, pluginPath: string): PreferenceSchema | undefined {
792
- const { scope, overridable } = TheiaPluginScanner.getScope(rawConfiguration.scope);
793
- const schema: PreferenceSchema = {
794
- scope,
795
- defaultOverridable: overridable,
796
- title: rawConfiguration.title,
797
- properties: {}
798
- };
799
-
800
- if (rawConfiguration.properties) {
801
- for (const [key, property] of Object.entries(rawConfiguration.properties)) {
802
- const scopeInfo = TheiaPluginScanner.getScope(property.scope);
803
- const schemaProperty: PreferenceDataProperty = {
804
- ...property,
805
- scope: scopeInfo.scope,
806
- overridable: scopeInfo.overridable,
807
- default: property.default !== undefined ? property.default : deriveDefaultForType(property.type)
808
- };
809
-
810
- schema.properties[key] = schemaProperty;
811
- }
812
- }
813
- return schema;
331
+ protected readConfiguration(rawConfiguration: IConfigurationNode, pluginPath: string): PreferenceSchema | undefined {
332
+ return readConfigurationShared(rawConfiguration, pluginPath);
814
333
  }
815
334
 
816
- private readKeybinding(rawKeybinding: PluginPackageKeybinding): Keybinding {
817
- return {
818
- keybinding: rawKeybinding.key,
819
- command: rawKeybinding.command,
820
- when: rawKeybinding.when,
821
- mac: rawKeybinding.mac,
822
- linux: rawKeybinding.linux,
823
- win: rawKeybinding.win,
824
- args: rawKeybinding.args
825
- };
335
+ protected readKeybinding(rawKeybinding: PluginPackageKeybinding): Keybinding {
336
+ return readKeybindingShared(rawKeybinding);
826
337
  }
827
338
 
828
- private readCustomEditors(rawCustomEditors: PluginPackageCustomEditor[]): CustomEditor[] {
339
+ protected readCustomEditors(rawCustomEditors: PluginPackageCustomEditor[]): CustomEditor[] {
829
340
  return rawCustomEditors.map(rawCustomEditor => this.readCustomEditor(rawCustomEditor));
830
341
  }
831
342
 
832
- private readCustomEditor(rawCustomEditor: PluginPackageCustomEditor): CustomEditor {
833
- return {
834
- viewType: rawCustomEditor.viewType,
835
- displayName: rawCustomEditor.displayName,
836
- selector: rawCustomEditor.selector || [],
837
- priority: rawCustomEditor.priority || CustomEditorPriority.default
838
- };
343
+ protected readCustomEditor(rawCustomEditor: PluginPackageCustomEditor): CustomEditor {
344
+ return readCustomEditorShared(rawCustomEditor);
839
345
  }
840
346
 
841
- private readViewsContainers(rawViewsContainers: PluginPackageViewContainer[], pck: PluginPackage): ViewContainer[] {
842
- return rawViewsContainers.map(rawViewContainer => this.readViewContainer(rawViewContainer, pck));
347
+ protected readViewsContainers(rawViewsContainers: readonly PluginPackageViewContainer[], pck: PluginPackage): ViewContainer[] {
348
+ return rawViewsContainers
349
+ .map(rawViewContainer => this.readViewContainer(rawViewContainer, pck))
350
+ .filter((container): container is ViewContainer => container !== undefined);
843
351
  }
844
352
 
845
- private readViewContainer(rawViewContainer: PluginPackageViewContainer, pck: PluginPackage): ViewContainer {
846
- const themeIcon = rawViewContainer.icon.startsWith('$(') ? rawViewContainer.icon : undefined;
847
- const iconUrl = this.toPluginUrl(pck, rawViewContainer.icon);
848
- return {
849
- id: rawViewContainer.id,
850
- title: rawViewContainer.title,
851
- iconUrl,
852
- themeIcon,
853
- when: rawViewContainer.when,
854
- };
353
+ protected readViewContainer(rawViewContainer: PluginPackageViewContainer, pck: PluginPackage): ViewContainer | undefined {
354
+ return readViewContainerShared(this.contributionCtx(pck), rawViewContainer, pck);
855
355
  }
856
356
 
857
- private readViews(rawViews: PluginPackageView[]): View[] {
357
+ protected readViews(rawViews: PluginPackageView[]): View[] {
858
358
  return rawViews.map(rawView => this.readView(rawView));
859
359
  }
860
360
 
861
- private readView(rawView: PluginPackageView): View {
862
- const result: View = {
863
- id: rawView.id,
864
- name: rawView.name,
865
- when: rawView.when,
866
- type: rawView.type
867
- };
868
-
869
- return result;
361
+ protected readView(rawView: PluginPackageView): View {
362
+ return readViewShared(rawView);
870
363
  }
871
364
 
872
- private readViewsWelcome(rawViewsWelcome: PluginPackageViewWelcome[], rowViews: { [location: string]: PluginPackageView[]; } | undefined): ViewWelcome[] {
365
+ protected readViewsWelcome(rawViewsWelcome: readonly PluginPackageViewWelcome[], rowViews: { [location: string]: readonly PluginPackageView[]; } | undefined): ViewWelcome[] {
873
366
  return rawViewsWelcome.map(rawViewWelcome => this.readViewWelcome(rawViewWelcome, this.extractPluginViewsIds(rowViews)));
874
367
  }
875
368
 
876
- private readViewWelcome(rawViewWelcome: PluginPackageViewWelcome, pluginViewsIds: string[]): ViewWelcome {
877
- const result: ViewWelcome = {
878
- view: rawViewWelcome.view,
879
- content: rawViewWelcome.contents,
880
- when: rawViewWelcome.when,
881
- enablement: rawViewWelcome.enablement,
882
- // if the plugin contributes Welcome view to its own view - it will be ordered first
883
- order: pluginViewsIds.findIndex(v => v === rawViewWelcome.view) > -1 ? 0 : 1
884
- };
885
-
886
- return result;
369
+ protected readViewWelcome(rawViewWelcome: PluginPackageViewWelcome, pluginViewsIds: string[]): ViewWelcome {
370
+ return readViewWelcomeShared(rawViewWelcome, pluginViewsIds);
887
371
  }
888
372
 
889
- private extractPluginViewsIds(views: { [location: string]: PluginPackageView[] } | undefined): string[] {
890
- const pluginViewsIds: string[] = [];
891
- if (views) {
892
- for (const location of Object.keys(views)) {
893
- const viewsIds = views[location].map(view => view.id);
894
- pluginViewsIds.push(...viewsIds);
895
- };
896
- }
897
- return pluginViewsIds;
373
+ protected extractPluginViewsIds(views: { [location: string]: readonly PluginPackageView[] } | undefined): string[] {
374
+ return extractPluginViewsIdsShared(views);
898
375
  }
899
376
 
900
- private readMenus(rawMenus: PluginPackageMenu[]): Menu[] {
377
+ protected readMenus(rawMenus: readonly PluginPackageMenu[]): Menu[] {
901
378
  return rawMenus.map(rawMenu => this.readMenu(rawMenu));
902
379
  }
903
380
 
904
- private readMenu(rawMenu: PluginPackageMenu): Menu {
905
- const result: Menu = {
906
- command: rawMenu.command,
907
- submenu: rawMenu.submenu,
908
- alt: rawMenu.alt,
909
- group: rawMenu.group,
910
- when: rawMenu.when
911
- };
912
- return result;
381
+ protected readMenu(rawMenu: PluginPackageMenu): Menu {
382
+ return readMenuShared(rawMenu);
913
383
  }
914
384
 
915
- private async readLanguages(rawLanguages: PluginPackageLanguageContribution[], plugin: PluginPackage): Promise<LanguageContribution[]> {
385
+ protected async readLanguages(rawLanguages: readonly PluginPackageLanguageContribution[], plugin: PluginPackage): Promise<LanguageContribution[]> {
916
386
  return Promise.all(rawLanguages.map(language => this.readLanguage(language, plugin)));
917
387
  }
918
388
 
919
- private readSubmenus(rawSubmenus: PluginPackageSubmenu[], plugin: PluginPackage): Submenu[] {
389
+ protected readSubmenus(rawSubmenus: readonly PluginPackageSubmenu[], plugin: PluginPackage): Submenu[] {
920
390
  return rawSubmenus.map(submenu => this.readSubmenu(submenu, plugin));
921
391
  }
922
392
 
923
- private readSubmenu(rawSubmenu: PluginPackageSubmenu, plugin: PluginPackage): Submenu {
924
- const icon = this.transformIconUrl(plugin, rawSubmenu.icon);
925
- return {
926
- icon: icon?.iconUrl ?? icon?.themeIcon,
927
- id: rawSubmenu.id,
928
- label: rawSubmenu.label
929
- };
930
-
393
+ protected readSubmenu(rawSubmenu: PluginPackageSubmenu, plugin: PluginPackage): Submenu {
394
+ return readSubmenuShared(this.contributionCtx(plugin), rawSubmenu, plugin);
931
395
  }
932
396
 
933
- private async readLanguage(rawLang: PluginPackageLanguageContribution, plugin: PluginPackage): Promise<LanguageContribution> {
934
- // TODO: add validation to all parameters
935
- const icon = this.transformIconUrl(plugin, rawLang.icon);
936
- const result: LanguageContribution = {
937
- id: rawLang.id,
938
- aliases: rawLang.aliases,
939
- extensions: rawLang.extensions,
940
- filenamePatterns: rawLang.filenamePatterns,
941
- filenames: rawLang.filenames,
942
- firstLine: rawLang.firstLine,
943
- mimetypes: rawLang.mimetypes,
944
- icon: icon?.iconUrl ?? icon?.themeIcon
945
- };
946
- if (rawLang.configuration) {
947
- const rawConfiguration = await this.readJson<PluginPackageLanguageContributionConfiguration>(path.resolve(plugin.packagePath, rawLang.configuration));
948
- if (rawConfiguration) {
949
- const configuration: LanguageConfiguration = {
950
- brackets: rawConfiguration.brackets,
951
- comments: rawConfiguration.comments,
952
- folding: rawConfiguration.folding,
953
- wordPattern: rawConfiguration.wordPattern,
954
- autoClosingPairs: this.extractValidAutoClosingPairs(rawLang.id, rawConfiguration),
955
- indentationRules: rawConfiguration.indentationRules,
956
- surroundingPairs: this.extractValidSurroundingPairs(rawLang.id, rawConfiguration),
957
- onEnterRules: rawConfiguration.onEnterRules,
958
- };
959
- result.configuration = configuration;
960
- }
961
- }
962
- return result;
963
-
397
+ protected async readLanguage(rawLang: PluginPackageLanguageContribution, plugin: PluginPackage): Promise<LanguageContribution> {
398
+ return readLanguageShared(this.contributionCtx(plugin), rawLang, plugin);
964
399
  }
965
400
 
966
- private readDebuggers(rawDebuggers: PluginPackageDebuggersContribution[]): DebuggerContribution[] {
967
- return rawDebuggers.map(rawDebug => this.readDebugger(rawDebug));
401
+ protected readDebuggers(rawDebuggers: PluginPackageDebuggersContribution[]): DebuggerContribution[] {
402
+ return rawDebuggers.map(rawDebugger => this.readDebugger(rawDebugger));
968
403
  }
969
404
 
970
- private readDebugger(rawDebugger: PluginPackageDebuggersContribution): DebuggerContribution {
971
- const result: DebuggerContribution = {
972
- type: rawDebugger.type,
973
- label: rawDebugger.label,
974
- languages: rawDebugger.languages,
975
- enableBreakpointsFor: rawDebugger.enableBreakpointsFor,
976
- variables: rawDebugger.variables,
977
- adapterExecutableCommand: rawDebugger.adapterExecutableCommand,
978
- configurationSnippets: rawDebugger.configurationSnippets,
979
- win: rawDebugger.win,
980
- winx86: rawDebugger.winx86,
981
- windows: rawDebugger.windows,
982
- osx: rawDebugger.osx,
983
- linux: rawDebugger.linux,
984
- program: rawDebugger.program,
985
- args: rawDebugger.args,
986
- runtime: rawDebugger.runtime,
987
- runtimeArgs: rawDebugger.runtimeArgs,
988
- configurationAttributes: rawDebugger.configurationAttributes
989
- };
990
-
991
- return result;
405
+ protected readDebugger(rawDebugger: PluginPackageDebuggersContribution): DebuggerContribution {
406
+ return readDebuggerShared(rawDebugger);
992
407
  }
993
408
 
994
- private readTaskDefinition(pluginName: string, definitionContribution: PluginTaskDefinitionContribution): TaskDefinition {
995
- const propertyKeys = definitionContribution.properties ? Object.keys(definitionContribution.properties) : [];
996
- const schema = this.toSchema(definitionContribution);
997
- return {
998
- taskType: definitionContribution.type,
999
- source: pluginName,
1000
- properties: {
1001
- required: definitionContribution.required || [],
1002
- all: propertyKeys,
1003
- schema
1004
- }
1005
- };
409
+ protected readTaskDefinition(pluginName: string, definitionContribution: PluginTaskDefinitionContribution): TaskDefinition {
410
+ return readTaskDefinitionShared(pluginName, definitionContribution, {
411
+ toSchema: definition => this.toSchema(definition)
412
+ });
1006
413
  }
1007
414
 
1008
415
  protected toSchema(definition: PluginTaskDefinitionContribution): IJSONSchema {
1009
- const reconciliation: IJSONSchema = { ...definition, type: 'object' };
1010
- const schema = deepClone(reconciliation);
1011
- if (schema.properties === undefined) {
1012
- schema.properties = Object.create(null);
1013
- }
1014
- schema.type = 'object';
1015
- schema.properties!.type = { type: 'string', const: definition.type };
1016
- return schema;
416
+ return toSchemaShared(definition);
1017
417
  }
1018
418
 
1019
- private extractValidAutoClosingPairs(langId: string, configuration: PluginPackageLanguageContributionConfiguration): AutoClosingPairConditional[] | undefined {
1020
- const source = configuration.autoClosingPairs;
1021
- if (typeof source === 'undefined') {
1022
- return undefined;
1023
- }
1024
- if (!Array.isArray(source)) {
1025
- this.logger.warn(`[${langId}]: language configuration: expected \`autoClosingPairs\` to be an array.`);
1026
- return undefined;
1027
- }
1028
-
1029
- let result: AutoClosingPairConditional[] | undefined = undefined;
1030
- for (let i = 0, len = source.length; i < len; i++) {
1031
- const pair = source[i];
1032
- if (Array.isArray(pair)) {
1033
- if (!isCharacterPair(pair)) {
1034
- this.logger.warn(`[${langId}]: language configuration: expected \`autoClosingPairs[${i}]\` to be an array of two strings or an object.`);
1035
- continue;
1036
- }
1037
- result = result || [];
1038
- result.push({ open: pair[0], close: pair[1] });
1039
- } else {
1040
- if (!isObject(pair)) {
1041
- this.logger.warn(`[${langId}]: language configuration: expected \`autoClosingPairs[${i}]\` to be an array of two strings or an object.`);
1042
- continue;
1043
- }
1044
- if (typeof pair.open !== 'string') {
1045
- this.logger.warn(`[${langId}]: language configuration: expected \`autoClosingPairs[${i}].open\` to be a string.`);
1046
- continue;
1047
- }
1048
- if (typeof pair.close !== 'string') {
1049
- this.logger.warn(`[${langId}]: language configuration: expected \`autoClosingPairs[${i}].close\` to be a string.`);
1050
- continue;
1051
- }
1052
- if (typeof pair.notIn !== 'undefined') {
1053
- if (!isStringArray(pair.notIn)) {
1054
- this.logger.warn(`[${langId}]: language configuration: expected \`autoClosingPairs[${i}].notIn\` to be a string array.`);
1055
- continue;
1056
- }
1057
- }
1058
- result = result || [];
1059
- result.push({ open: pair.open, close: pair.close, notIn: pair.notIn });
1060
- }
1061
- }
1062
- return result;
419
+ protected async readJson<T>(filePath: string): Promise<T | undefined> {
420
+ const content = await this.readFile(filePath);
421
+ return content ? jsoncparser.parse(content, undefined, { disallowComments: false }) : undefined;
1063
422
  }
1064
423
 
1065
- private extractValidSurroundingPairs(langId: string, configuration: PluginPackageLanguageContributionConfiguration): AutoClosingPair[] | undefined {
1066
- const source = configuration.surroundingPairs;
1067
- if (typeof source === 'undefined') {
1068
- return undefined;
1069
- }
1070
- if (!Array.isArray(source)) {
1071
- this.logger.warn(`[${langId}]: language configuration: expected \`surroundingPairs\` to be an array.`);
1072
- return undefined;
1073
- }
1074
-
1075
- let result: AutoClosingPair[] | undefined = undefined;
1076
- for (let i = 0, len = source.length; i < len; i++) {
1077
- const pair = source[i];
1078
- if (Array.isArray(pair)) {
1079
- if (!isCharacterPair(pair)) {
1080
- this.logger.warn(`[${langId}]: language configuration: expected \`surroundingPairs[${i}]\` to be an array of two strings or an object.`);
1081
- continue;
1082
- }
1083
- result = result || [];
1084
- result.push({ open: pair[0], close: pair[1] });
1085
- } else {
1086
- if (!isObject(pair)) {
1087
- this.logger.warn(`[${langId}]: language configuration: expected \`surroundingPairs[${i}]\` to be an array of two strings or an object.`);
1088
- continue;
1089
- }
1090
- if (typeof pair.open !== 'string') {
1091
- this.logger.warn(`[${langId}]: language configuration: expected \`surroundingPairs[${i}].open\` to be a string.`);
1092
- continue;
1093
- }
1094
- if (typeof pair.close !== 'string') {
1095
- this.logger.warn(`[${langId}]: language configuration: expected \`surroundingPairs[${i}].close\` to be a string.`);
1096
- continue;
1097
- }
1098
- result = result || [];
1099
- result.push({ open: pair.open, close: pair.close });
424
+ protected async readFile(filePath: string): Promise<string> {
425
+ try {
426
+ const content = await fs.readFile(filePath, { encoding: 'utf8' });
427
+ return content;
428
+ } catch (e) {
429
+ if (!isENOENT(e)) {
430
+ this.logger.error(e);
1100
431
  }
432
+ return '';
1101
433
  }
1102
- return result;
1103
434
  }
1104
-
1105
- }
1106
-
1107
- function isCharacterPair(something: CharacterPair): boolean {
1108
- return (
1109
- isStringArray(something)
1110
- && something.length === 2
1111
- );
1112
435
  }