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