@opensumi/ide-preferences 2.21.13 → 2.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/lib/browser/abstract-resource-preference-provider.d.ts +1 -0
  2. package/lib/browser/abstract-resource-preference-provider.d.ts.map +1 -1
  3. package/lib/browser/abstract-resource-preference-provider.js +22 -3
  4. package/lib/browser/abstract-resource-preference-provider.js.map +1 -1
  5. package/lib/browser/folder-file-preference-provider.d.ts +24 -0
  6. package/lib/browser/folder-file-preference-provider.d.ts.map +1 -0
  7. package/lib/browser/{folder-preference-provider.js → folder-file-preference-provider.js} +12 -14
  8. package/lib/browser/folder-file-preference-provider.js.map +1 -0
  9. package/lib/browser/folders-preferences-provider.d.ts +8 -8
  10. package/lib/browser/folders-preferences-provider.d.ts.map +1 -1
  11. package/lib/browser/folders-preferences-provider.js +7 -7
  12. package/lib/browser/folders-preferences-provider.js.map +1 -1
  13. package/lib/browser/index.d.ts.map +1 -1
  14. package/lib/browser/index.js +8 -8
  15. package/lib/browser/index.js.map +1 -1
  16. package/lib/browser/preference-contribution.d.ts.map +1 -1
  17. package/lib/browser/preference-contribution.js +3 -3
  18. package/lib/browser/preference-contribution.js.map +1 -1
  19. package/lib/browser/preference-settings.service.d.ts +22 -10
  20. package/lib/browser/preference-settings.service.d.ts.map +1 -1
  21. package/lib/browser/preference-settings.service.js +284 -101
  22. package/lib/browser/preference-settings.service.js.map +1 -1
  23. package/lib/browser/preference-widgets.js.map +1 -1
  24. package/lib/browser/preferenceItem.view.d.ts +4 -3
  25. package/lib/browser/preferenceItem.view.d.ts.map +1 -1
  26. package/lib/browser/preferenceItem.view.js +125 -62
  27. package/lib/browser/preferenceItem.view.js.map +1 -1
  28. package/lib/browser/preferences.module.less +100 -60
  29. package/lib/browser/preferences.view.d.ts +3 -17
  30. package/lib/browser/preferences.view.d.ts.map +1 -1
  31. package/lib/browser/preferences.view.js +194 -112
  32. package/lib/browser/preferences.view.js.map +1 -1
  33. package/lib/browser/user-preference-provider.js.map +1 -1
  34. package/lib/browser/userstorage/user-storage.contribution.js.map +1 -1
  35. package/lib/browser/userstorage/user-storage.service.js +7 -7
  36. package/lib/browser/userstorage/user-storage.service.js.map +1 -1
  37. package/lib/browser/workspace-file-preference-provider.d.ts +1 -1
  38. package/lib/browser/workspace-file-preference-provider.d.ts.map +1 -1
  39. package/lib/browser/workspace-file-preference-provider.js.map +1 -1
  40. package/lib/browser/workspace-preference-provider.js.map +1 -1
  41. package/lib/common/preference-id.d.ts +3 -0
  42. package/lib/common/preference-id.d.ts.map +1 -1
  43. package/lib/common/preference-id.js +4 -1
  44. package/lib/common/preference-id.js.map +1 -1
  45. package/lib/common/preference.d.ts +1 -0
  46. package/lib/common/preference.d.ts.map +1 -1
  47. package/lib/common/preference.js +26 -4
  48. package/lib/common/preference.js.map +1 -1
  49. package/lib/common/types.d.ts +18 -3
  50. package/lib/common/types.d.ts.map +1 -1
  51. package/lib/common/user-storage.d.ts +1 -1
  52. package/lib/common/user-storage.d.ts.map +1 -1
  53. package/package.json +12 -11
  54. package/src/browser/abstract-resource-preference-provider.ts +357 -0
  55. package/src/browser/folder-file-preference-provider.ts +56 -0
  56. package/src/browser/folders-preferences-provider.ts +262 -0
  57. package/src/browser/index.ts +125 -0
  58. package/src/browser/preference-contribution.ts +432 -0
  59. package/src/browser/preference-settings.service.ts +810 -0
  60. package/src/browser/preference-widgets.ts +368 -0
  61. package/src/browser/preferenceItem.view.tsx +787 -0
  62. package/src/browser/preferences.module.less +345 -0
  63. package/src/browser/preferences.view.tsx +388 -0
  64. package/src/browser/user-preference-provider.ts +18 -0
  65. package/src/browser/userstorage/index.ts +2 -0
  66. package/src/browser/userstorage/user-storage.contribution.ts +19 -0
  67. package/src/browser/userstorage/user-storage.service.ts +192 -0
  68. package/src/browser/workspace-file-preference-provider.ts +50 -0
  69. package/src/browser/workspace-preference-provider.ts +129 -0
  70. package/src/common/commands.ts +5 -0
  71. package/src/common/index.ts +4 -0
  72. package/src/common/preference-id.ts +11 -0
  73. package/src/common/preference.ts +51 -0
  74. package/src/common/types.ts +63 -0
  75. package/src/common/user-storage.ts +6 -0
  76. package/src/index.ts +1 -0
  77. package/lib/browser/folder-preference-provider.d.ts +0 -26
  78. package/lib/browser/folder-preference-provider.d.ts.map +0 -1
  79. package/lib/browser/folder-preference-provider.js.map +0 -1
  80. package/lib/browser/index.less +0 -36
@@ -0,0 +1,357 @@
1
+ import * as jsoncparser from 'jsonc-parser';
2
+
3
+ import { Injectable, Autowired } from '@opensumi/di';
4
+ import {
5
+ JSONUtils,
6
+ URI,
7
+ Disposable,
8
+ isUndefined,
9
+ PreferenceProviderDataChanges,
10
+ ILogger,
11
+ IResolvedPreferences,
12
+ Throttler,
13
+ FileChange,
14
+ Schemes,
15
+ AppConfig,
16
+ VSCODE_WORKSPACE_CONFIGURATION_DIR_NAME,
17
+ } from '@opensumi/ide-core-browser';
18
+ import {
19
+ PreferenceProvider,
20
+ PreferenceSchemaProvider,
21
+ PreferenceScope,
22
+ PreferenceProviderDataChange,
23
+ PreferenceConfigurations,
24
+ } from '@opensumi/ide-core-browser';
25
+ import { IFileServiceClient } from '@opensumi/ide-file-service';
26
+
27
+ import { IPreferenceTask } from '../common';
28
+
29
+ // vscode 对语言的setting是根据这种格式来的
30
+ // "[json]": { "editor.formatter": "xxxx" }
31
+ // 对其进行兼容
32
+ const OVERRIDE_PROPERTY = '\\[(.*)\\]$';
33
+ export const OVERRIDE_PROPERTY_PATTERN = new RegExp(OVERRIDE_PROPERTY);
34
+
35
+ @Injectable()
36
+ export abstract class AbstractResourcePreferenceProvider extends PreferenceProvider {
37
+ protected preferences: IResolvedPreferences = {
38
+ default: {},
39
+ languageSpecific: {},
40
+ };
41
+
42
+ @Autowired(PreferenceSchemaProvider)
43
+ protected readonly schemaProvider: PreferenceSchemaProvider;
44
+
45
+ @Autowired(PreferenceConfigurations)
46
+ protected readonly configurations: PreferenceConfigurations;
47
+
48
+ @Autowired(IFileServiceClient)
49
+ protected readonly fileSystem: IFileServiceClient;
50
+
51
+ @Autowired(AppConfig)
52
+ private appConfig: AppConfig;
53
+
54
+ @Autowired(ILogger)
55
+ private logger: ILogger;
56
+
57
+ private preferenceThrottler: Throttler = new Throttler();
58
+ private preferenceTasks: IPreferenceTask[] = [];
59
+
60
+ constructor() {
61
+ super();
62
+ this.listen();
63
+ }
64
+
65
+ protected listen() {
66
+ if (this.fileSystem.handlesScheme(Schemes.file) && this.fileSystem.handlesScheme(Schemes.userStorage)) {
67
+ this.init();
68
+ } else {
69
+ const disposable = this.fileSystem.onFileProviderChanged((scheme: string[]) => {
70
+ if (this.fileSystem.handlesScheme(Schemes.file) && this.fileSystem.handlesScheme(Schemes.userStorage)) {
71
+ this.init();
72
+ disposable.dispose();
73
+ }
74
+ });
75
+ }
76
+ }
77
+
78
+ protected async init(): Promise<void> {
79
+ // 尝试读取preferences初始内容
80
+ this.readPreferences()
81
+ .then(() => this._ready.resolve())
82
+ .catch(() => this._ready.resolve());
83
+
84
+ const uri = this.getUri();
85
+ const watcher = await this.fileSystem.watchFileChanges(uri.parent);
86
+ // 配置文件改变时,重新读取配置
87
+ this.toDispose.push(watcher);
88
+ watcher.onFilesChanged((e: FileChange[]) => {
89
+ const effected = e.find((file) => file.uri.startsWith(uri.parent.toString()));
90
+ if (effected) {
91
+ return this.readPreferences();
92
+ }
93
+ });
94
+ this.toDispose.push(Disposable.create(() => this.reset()));
95
+ }
96
+
97
+ protected abstract getUri(): URI;
98
+ protected abstract getScope(): PreferenceScope;
99
+
100
+ getConfigUri(): URI;
101
+ getConfigUri(resourceUri: string | undefined): URI | undefined;
102
+ getConfigUri(resourceUri?: string): URI | undefined {
103
+ if (!resourceUri) {
104
+ return this.getUri();
105
+ }
106
+ // 获取configUri不需要等待配置读取完应该就可以读取
107
+ return this.contains(resourceUri) ? this.getUri() : undefined;
108
+ }
109
+
110
+ contains(resourceUri: string | undefined): boolean {
111
+ if (!resourceUri) {
112
+ return true;
113
+ }
114
+ const domain = this.getDomain();
115
+ if (!domain) {
116
+ return true;
117
+ }
118
+ const resourcePath = new URI(resourceUri).path;
119
+ return domain.some((uri) => new URI(uri).path.relativity(resourcePath) >= 0);
120
+ }
121
+
122
+ getPreferences(resourceUri?: string, language?: string) {
123
+ return this.loaded && this.contains(resourceUri) ? this.getOnePreference(language) : undefined;
124
+ }
125
+
126
+ getLanguagePreferences(resourceUri?: string) {
127
+ return this.loaded && this.contains(resourceUri) ? this.preferences.languageSpecific : undefined;
128
+ }
129
+
130
+ getOnePreference(language?: string): { [key: string]: any } {
131
+ if (language) {
132
+ return this.preferences.languageSpecific[language] || {};
133
+ } else {
134
+ return this.preferences.default;
135
+ }
136
+ }
137
+
138
+ async resolvePreferenceTasks(tasks: IPreferenceTask[]) {
139
+ const uri = this.getUri();
140
+ // 读取配置时同时更新一下资源信息,防止写入时对异步写入情况的错误判断
141
+ this.resource = this.fileSystem.getFileStat(uri.toString());
142
+ let resource = await this.resource;
143
+ let content = ((await this.readContents()) || '').trim();
144
+
145
+ // 将多次配置修改合并为一次文件内容变更
146
+ for (const task of tasks) {
147
+ const { path, value } = task;
148
+ if ((!content || path.length === 0) && isUndefined(value)) {
149
+ continue;
150
+ }
151
+ const formattingOptions = { tabSize: 2, insertSpaces: true, eol: '' };
152
+ const edits = jsoncparser.modify(content, path, value, { formattingOptions });
153
+ content = jsoncparser.applyEdits(content, edits);
154
+ }
155
+
156
+ try {
157
+ if (!resource) {
158
+ // 当资源不存在又需要写入数据时,创建对应文件
159
+ resource = await this.fileSystem.createFile(uri.toString());
160
+ }
161
+ await this.fileSystem.setContent(resource, content);
162
+ await this.readPreferences(content);
163
+ return true;
164
+ } catch (e) {
165
+ this.logger.error(`${e.toString()}`);
166
+ return false;
167
+ }
168
+ }
169
+
170
+ /**
171
+ * 配置变更队列处理函数
172
+ */
173
+ doSetPreferenceTask() {
174
+ const tasks = this.preferenceTasks.slice(0);
175
+ this.preferenceTasks = [];
176
+ return this.resolvePreferenceTasks(tasks);
177
+ }
178
+
179
+ async doSetPreference(key: string, value: any, resourceUri?: string, language?: string): Promise<boolean> {
180
+ if (!this.contains(resourceUri)) {
181
+ return false;
182
+ }
183
+ const path = this.getPath(key, language);
184
+ if (!path) {
185
+ return false;
186
+ }
187
+ // 这里将每次配置变更的参数构造为一个 IPreferenceTask
188
+ this.preferenceTasks.push({ path, key, value });
189
+ return await this.preferenceThrottler.queue<boolean>(this.doSetPreferenceTask.bind(this));
190
+ }
191
+
192
+ protected getPath(preferenceName: string, language?: string): string[] | undefined {
193
+ if (language) {
194
+ return [`[${language}]`, preferenceName];
195
+ }
196
+ return [preferenceName];
197
+ }
198
+
199
+ protected loaded = false;
200
+
201
+ protected async readPreferences(content?: string): Promise<void> {
202
+ const newContent = content || (await this.readContents());
203
+ this.loaded = !isUndefined(newContent);
204
+ const newPrefs = newContent ? this.getParsedContent(newContent) : { default: {}, languageSpecific: {} };
205
+ this.handlePreferenceChanges(newPrefs);
206
+ }
207
+
208
+ protected async readContents(): Promise<string | undefined> {
209
+ const uri = this.getUri();
210
+ try {
211
+ const { content } = await this.fileSystem.readFile(uri.toString());
212
+ return content.toString();
213
+ } catch (e) {
214
+ if (this.appConfig.useVSCodeWorkspaceConfiguration) {
215
+ // 当获取不到工作区配置时,在 `useVSCodeWorkspaceConfiguration` 开启的情况下,尝试获取 `.vscode` 下配置作为默认值
216
+ if (uri.scheme === Schemes.file) {
217
+ const vscodeConfigurationUri = uri.parent.parent
218
+ .resolve(VSCODE_WORKSPACE_CONFIGURATION_DIR_NAME)
219
+ .resolve(uri.displayName);
220
+ try {
221
+ const { content } = await this.fileSystem.readFile(vscodeConfigurationUri.toString());
222
+ return content.toString();
223
+ } catch (e) {
224
+ return undefined;
225
+ }
226
+ }
227
+ }
228
+ return undefined;
229
+ }
230
+ }
231
+
232
+ protected getParsedContent(content: string): IResolvedPreferences {
233
+ const jsonData = this.parse(content);
234
+
235
+ const preferences: IResolvedPreferences = {
236
+ default: {},
237
+ languageSpecific: {},
238
+ };
239
+ if (typeof jsonData !== 'object') {
240
+ return preferences;
241
+ }
242
+ for (const preferenceName of Object.keys(jsonData)) {
243
+ const preferenceValue = jsonData[preferenceName];
244
+ // 这里由于插件的schema注册较晚,在第一次获取配置时会校验不通过导致取不到值,读取暂时去掉校验逻辑
245
+ if (OVERRIDE_PROPERTY_PATTERN.test(preferenceName)) {
246
+ const language = preferenceName.match(OVERRIDE_PROPERTY_PATTERN)![1];
247
+ preferences.languageSpecific[language] = preferences.languageSpecific[language] || {};
248
+ // eslint-disable-next-line guard-for-in
249
+ for (const overriddenPreferenceName in preferenceValue) {
250
+ const overriddenValue = preferenceValue[overriddenPreferenceName];
251
+ preferences.languageSpecific[language][`${overriddenPreferenceName}`] = overriddenValue;
252
+ }
253
+ } else {
254
+ preferences.default[preferenceName] = preferenceValue;
255
+ }
256
+ }
257
+ return preferences;
258
+ }
259
+
260
+ protected validate(preferenceName: string, preferenceValue: any): boolean {
261
+ if (this.configurations.getPath(this.getUri()) !== this.configurations.getPaths()[0]) {
262
+ return true;
263
+ }
264
+ return isUndefined(preferenceValue) || this.schemaProvider.validate(preferenceName, preferenceValue).valid;
265
+ }
266
+
267
+ protected parse(content: string): any {
268
+ content = content.trim();
269
+ if (!content) {
270
+ return undefined;
271
+ }
272
+ const strippedContent = jsoncparser.stripComments(content);
273
+ return jsoncparser.parse(strippedContent);
274
+ }
275
+
276
+ protected handlePreferenceChanges(newPrefs: IResolvedPreferences): void {
277
+ const oldPrefs = Object.assign({}, this.preferences);
278
+ this.preferences = newPrefs;
279
+ const changes: PreferenceProviderDataChanges = this.collectChanges(this.preferences, oldPrefs);
280
+
281
+ if (Object.keys(changes.default).length > 0 || Object.keys(changes.languageSpecific).length > 0) {
282
+ this.emitPreferencesChangedEvent(changes);
283
+ }
284
+ }
285
+
286
+ protected reset(): void {
287
+ const preferences = this.preferences;
288
+ this.preferences = { default: {}, languageSpecific: {} };
289
+ const changes: PreferenceProviderDataChanges = this.collectChanges(this.preferences, preferences);
290
+
291
+ if (Object.keys(changes.default).length > 0 || Object.keys(changes.languageSpecific).length > 0) {
292
+ this.emitPreferencesChangedEvent(changes);
293
+ }
294
+ }
295
+
296
+ private collectChanges(newPref: IResolvedPreferences, oldPref: IResolvedPreferences): PreferenceProviderDataChanges {
297
+ const changes: PreferenceProviderDataChanges = {
298
+ default: this.collectOneChanges(newPref.default, oldPref.default),
299
+ languageSpecific: {},
300
+ };
301
+ const languages = new Set<string>([
302
+ ...Object.keys(newPref.languageSpecific),
303
+ ...Object.keys(oldPref.languageSpecific),
304
+ ]);
305
+ for (const language of languages) {
306
+ const languageChange = this.collectOneChanges(
307
+ newPref.languageSpecific[language],
308
+ oldPref.languageSpecific[language],
309
+ );
310
+ if (Object.keys(languageChange).length > 0) {
311
+ changes.languageSpecific[language] = languageChange;
312
+ }
313
+ }
314
+ return changes;
315
+ }
316
+
317
+ private collectOneChanges(
318
+ newPref: { [name: string]: any },
319
+ oldPref: { [name: string]: any },
320
+ ): { [preferenceName: string]: PreferenceProviderDataChange } {
321
+ const keys = new Set([...Object.keys(oldPref || {}), ...Object.keys(newPref || {})]);
322
+ const changes: { [preferenceName: string]: PreferenceProviderDataChange } = {};
323
+ const uri = this.getUri();
324
+
325
+ for (const prefName of keys) {
326
+ const oldValue = oldPref[prefName];
327
+ const newValue = newPref[prefName];
328
+ const schemaProperties = this.schemaProvider.getCombinedSchema().properties[prefName];
329
+ if (schemaProperties) {
330
+ const scope = schemaProperties.scope;
331
+ // do not emit the change event if the change is made out of the defined preference scope
332
+ if (!this.schemaProvider.isValidInScope(prefName, this.getScope())) {
333
+ this.logger.warn(
334
+ `Preference ${prefName} in ${uri} can only be defined in scopes: ${PreferenceScope.getScopeNames(
335
+ scope,
336
+ ).join(', ')}.`,
337
+ );
338
+ continue;
339
+ }
340
+ }
341
+ if (
342
+ (isUndefined(newValue) && oldValue !== newValue) ||
343
+ (isUndefined(oldValue) && newValue !== oldValue) || // JSONUtils.deepEqual() does not support handling `undefined`
344
+ !JSONUtils.deepEqual(oldValue, newValue)
345
+ ) {
346
+ changes[prefName] = {
347
+ preferenceName: prefName,
348
+ newValue,
349
+ oldValue,
350
+ scope: this.getScope(),
351
+ domain: this.getDomain(),
352
+ };
353
+ }
354
+ }
355
+ return changes;
356
+ }
357
+ }
@@ -0,0 +1,56 @@
1
+ import { Autowired, Injectable } from '@opensumi/di';
2
+ import { URI, PreferenceScope } from '@opensumi/ide-core-browser';
3
+ import { FileStat } from '@opensumi/ide-file-service';
4
+ import { IWorkspaceService } from '@opensumi/ide-workspace/lib/common';
5
+
6
+ import { AbstractResourcePreferenceProvider } from './abstract-resource-preference-provider';
7
+
8
+ export const FolderFilePreferenceProviderFactory = Symbol('FolderFilePreferenceProviderFactory');
9
+ export type FolderFilePreferenceProviderFactory = (
10
+ options: FolderFilePreferenceProviderOptions,
11
+ ) => FolderFilePreferenceProvider;
12
+
13
+ export const FolderFilePreferenceProviderOptions = Symbol('FolderFilePreferenceProviderOptions');
14
+ export interface FolderFilePreferenceProviderOptions {
15
+ readonly folder: FileStat;
16
+ readonly configUri: URI;
17
+ }
18
+
19
+ /**
20
+ * 配置文件夹比如 `.sumi` 内可以存在多个这样的文件, 比如: settings.json, launch.json, tasks.json 等
21
+ */
22
+ @Injectable()
23
+ export class FolderFilePreferenceProvider extends AbstractResourcePreferenceProvider {
24
+ // 与 `launch.json` 等其他配置文件不同,options 会有所差异
25
+ @Autowired(FolderFilePreferenceProviderOptions)
26
+ protected readonly options: FolderFilePreferenceProviderOptions;
27
+
28
+ @Autowired(IWorkspaceService)
29
+ protected readonly workspaceService: IWorkspaceService;
30
+
31
+ // 缓存目录URI
32
+ private _folderUri: URI;
33
+
34
+ get folderUri(): URI {
35
+ if (!this._folderUri) {
36
+ this._folderUri = new URI(this.options.folder.uri);
37
+ }
38
+ return this._folderUri;
39
+ }
40
+
41
+ public getUri(): URI {
42
+ return this.options.configUri;
43
+ }
44
+
45
+ protected getScope(): PreferenceScope {
46
+ // 当在非工作区场景下时,采用PreferenceScope.Workspace作为配置作用域
47
+ if (!this.workspaceService.isMultiRootWorkspaceOpened) {
48
+ return PreferenceScope.Workspace;
49
+ }
50
+ return PreferenceScope.Folder;
51
+ }
52
+
53
+ getDomain(): string[] {
54
+ return [this.folderUri.toString()];
55
+ }
56
+ }
@@ -0,0 +1,262 @@
1
+ import { Autowired, Injectable } from '@opensumi/di';
2
+ import {
3
+ URI,
4
+ PreferenceProvider,
5
+ PreferenceResolveResult,
6
+ PreferenceConfigurations,
7
+ ILogger,
8
+ } from '@opensumi/ide-core-browser';
9
+ import { IFileServiceClient } from '@opensumi/ide-file-service/lib/common/file-service-client';
10
+ import { IWorkspaceService } from '@opensumi/ide-workspace';
11
+
12
+ import {
13
+ FolderFilePreferenceProvider,
14
+ FolderFilePreferenceProviderFactory,
15
+ FolderFilePreferenceProviderOptions,
16
+ } from './folder-file-preference-provider';
17
+
18
+ @Injectable()
19
+ export class FoldersPreferencesProvider extends PreferenceProvider {
20
+ @Autowired(FolderFilePreferenceProviderFactory)
21
+ protected readonly FolderFilePreferenceProviderFactory: FolderFilePreferenceProviderFactory;
22
+
23
+ @Autowired(PreferenceConfigurations)
24
+ protected readonly configurations: PreferenceConfigurations;
25
+
26
+ @Autowired(IWorkspaceService)
27
+ protected readonly workspaceService: IWorkspaceService;
28
+
29
+ @Autowired(IFileServiceClient)
30
+ protected readonly fileSystem: IFileServiceClient;
31
+
32
+ @Autowired(ILogger)
33
+ protected readonly logger: ILogger;
34
+
35
+ protected readonly providers = new Map<string, FolderFilePreferenceProvider>();
36
+
37
+ constructor() {
38
+ super();
39
+ this.init();
40
+ }
41
+
42
+ protected async init(): Promise<void> {
43
+ await this.workspaceService.roots;
44
+
45
+ this.updateProviders();
46
+ this.workspaceService.onWorkspaceChanged(() => this.updateProviders());
47
+
48
+ const readyPromises: Promise<void>[] = [];
49
+ for (const provider of this.providers.values()) {
50
+ readyPromises.push(provider.ready.catch((e) => this.logger.error(e)));
51
+ }
52
+ if (readyPromises.length > 0) {
53
+ Promise.all(readyPromises).then(() => this._ready.resolve());
54
+ } else {
55
+ this._ready.resolve();
56
+ }
57
+ }
58
+
59
+ /**
60
+ * 根据当前工作区文件结构重新初始化配置项获取逻辑
61
+ *
62
+ * @protected
63
+ * @memberof FoldersPreferencesProvider
64
+ */
65
+ protected updateProviders(): void {
66
+ const roots = this.workspaceService.tryGetRoots();
67
+ const toDelete = new Set(this.providers.keys());
68
+ for (const folder of roots) {
69
+ // 这里根据当前工作区的根目录分别创建 `setting.json`, `launch.json`, `task.json` 等文件的 FolderPreferenceProvider
70
+ const folderUri = new URI(folder.uri);
71
+ for (const configPath of this.configurations.getPaths()) {
72
+ for (const configName of [...this.configurations.getSectionNames(), this.configurations.getConfigName()]) {
73
+ const configUri = this.configurations.createUri(folderUri, configPath, configName);
74
+ const key = configUri.toString();
75
+ toDelete.delete(key);
76
+ if (!this.providers.has(key)) {
77
+ const provider = this.createProvider({ folder, configUri });
78
+ this.providers.set(key, provider);
79
+ }
80
+ }
81
+ }
82
+ }
83
+
84
+ // 去重,移除旧的配置文件Provider
85
+ for (const key of toDelete) {
86
+ const provider = this.providers.get(key);
87
+ if (provider) {
88
+ this.providers.delete(key);
89
+ provider.dispose();
90
+ }
91
+ }
92
+ }
93
+
94
+ getConfigUri(resourceUri?: string): URI | undefined {
95
+ for (const provider of this.getFolderProviders(resourceUri)) {
96
+ const configUri = provider.getConfigUri(resourceUri);
97
+ if (this.configurations.isConfigUri(configUri)) {
98
+ return configUri;
99
+ }
100
+ }
101
+ return undefined;
102
+ }
103
+
104
+ getContainingConfigUri(resourceUri?: string): URI | undefined {
105
+ for (const provider of this.getFolderProviders(resourceUri)) {
106
+ const configUri = provider.getConfigUri();
107
+ if (this.configurations.isConfigUri(configUri) && provider.contains(resourceUri)) {
108
+ return configUri;
109
+ }
110
+ }
111
+ return undefined;
112
+ }
113
+
114
+ getDomain(): string[] {
115
+ return this.workspaceService.tryGetRoots().map((root) => root.uri);
116
+ }
117
+
118
+ doResolve<T>(preferenceName: string, resourceUri?: string, language?: string): PreferenceResolveResult<T> {
119
+ const result: PreferenceResolveResult<T> = {};
120
+ const groups = this.groupProvidersByConfigName(resourceUri);
121
+ for (const group of groups.values()) {
122
+ for (const provider of group) {
123
+ const { value, configUri } = provider.resolve<T>(preferenceName, resourceUri, language);
124
+ if (configUri && value !== undefined) {
125
+ result.configUri = configUri;
126
+ result.value = PreferenceProvider.merge(result.value as any, value as any) as any;
127
+ break;
128
+ }
129
+ }
130
+ }
131
+ return result;
132
+ }
133
+
134
+ getPreferences(resourceUri?: string, language?: string) {
135
+ let result;
136
+ const groups = this.groupProvidersByConfigName(resourceUri);
137
+ for (const group of groups.values()) {
138
+ for (const provider of group) {
139
+ if (provider.getConfigUri(resourceUri)) {
140
+ const preferences = provider.getPreferences(undefined, language);
141
+ if (preferences) {
142
+ result = PreferenceProvider.merge(result, preferences) as any;
143
+ }
144
+ break;
145
+ }
146
+ }
147
+ }
148
+ return result;
149
+ }
150
+
151
+ getLanguagePreferences(resourceUri?: string) {
152
+ let result;
153
+ const groups = this.groupProvidersByConfigName(resourceUri);
154
+ for (const group of groups.values()) {
155
+ for (const provider of group) {
156
+ if (provider.getConfigUri(resourceUri)) {
157
+ const preferences = provider.getLanguagePreferences();
158
+ if (preferences) {
159
+ result = PreferenceProvider.merge(result, preferences) as any;
160
+ }
161
+ break;
162
+ }
163
+ }
164
+ }
165
+ return result;
166
+ }
167
+
168
+ /**
169
+ * 根据传入的 preferenceName 对配置项进行值的更新
170
+ * 这里一个配置项,如 launch.json 可能对应多个 Provider,故需要遍历进行配置设置
171
+ *
172
+ * @param {string} preferenceName 配置项
173
+ * @param {*} value 值
174
+ * @param {string} [resourceUri] 资源路径
175
+ * @param {string} [language] 语言标识符
176
+ * @returns {Promise<boolean>}
177
+ * @memberof FoldersPreferencesProvider
178
+ */
179
+ async doSetPreference(preferenceName: string, value: any, resourceUri?: string, language?: string): Promise<boolean> {
180
+ const sectionName = preferenceName.split('.', 1)[0];
181
+ const configName = this.configurations.isSectionName(sectionName)
182
+ ? sectionName
183
+ : this.configurations.getConfigName();
184
+
185
+ const providers = this.getFolderProviders(resourceUri);
186
+ let configPath: string | undefined;
187
+
188
+ for (const provider of providers) {
189
+ if (configPath === undefined) {
190
+ const configUri = provider.getConfigUri(resourceUri);
191
+ if (configUri) {
192
+ configPath = this.configurations.getPath(configUri);
193
+ }
194
+ }
195
+ if (this.configurations.getName(provider.getConfigUri()) === configName) {
196
+ if (provider.getConfigUri(resourceUri)) {
197
+ // 当存在配置文件路径时,尝试执行设置配置
198
+ if (await provider.setPreference(preferenceName, value, resourceUri, language)) {
199
+ return true;
200
+ }
201
+ } else if (this.configurations.getPath(provider.getConfigUri()) === configPath) {
202
+ // 当存在配置文件父路径与当前传入的resourceUri所指定的配置文件父路径一致时,尝试执行设置配置
203
+ if (await provider.setPreference(preferenceName, value, resourceUri, language)) {
204
+ return true;
205
+ }
206
+ } else {
207
+ // 为不存在配置文件的配置尝试执行一次配置修改
208
+ if (await provider.setPreference(preferenceName, value, resourceUri, language)) {
209
+ return true;
210
+ }
211
+ }
212
+ }
213
+ }
214
+ return false;
215
+ }
216
+
217
+ protected groupProvidersByConfigName(resourceUri?: string): Map<string, FolderFilePreferenceProvider[]> {
218
+ const groups = new Map<string, FolderFilePreferenceProvider[]>();
219
+ const providers = this.getFolderProviders(resourceUri);
220
+ for (const configName of [this.configurations.getConfigName(), ...this.configurations.getSectionNames()]) {
221
+ const group: any[] = [];
222
+ for (const provider of providers) {
223
+ if (this.configurations.getName(provider.getConfigUri()) === configName) {
224
+ group.push(provider);
225
+ }
226
+ }
227
+ groups.set(configName, group);
228
+ }
229
+ return groups;
230
+ }
231
+
232
+ protected getFolderProviders(resourceUri?: string): FolderFilePreferenceProvider[] {
233
+ if (!resourceUri) {
234
+ return [];
235
+ }
236
+ const resourcePath = new URI(resourceUri).path;
237
+ let folder: Readonly<{ relativity: number; uri?: string }> = { relativity: Number.MAX_SAFE_INTEGER };
238
+ const providers = new Map<string, FolderFilePreferenceProvider[]>();
239
+ for (const provider of this.providers.values()) {
240
+ const uri = provider.folderUri.toString();
241
+ const folderProviders = providers.get(uri) || [];
242
+ folderProviders.push(provider);
243
+ providers.set(uri, folderProviders);
244
+ const relativity = provider.folderUri.path.relativity(resourcePath);
245
+ if (relativity >= 0 && folder.relativity > relativity) {
246
+ folder = { relativity, uri };
247
+ }
248
+ }
249
+ return (folder.uri && providers.get(folder.uri)) || [];
250
+ }
251
+
252
+ protected createProvider(options: FolderFilePreferenceProviderOptions): FolderFilePreferenceProvider {
253
+ const provider = this.FolderFilePreferenceProviderFactory(options);
254
+ this.toDispose.push(provider);
255
+ this.toDispose.push(
256
+ provider.onDidPreferencesChanged((change) => {
257
+ this.emitPreferencesChangedEvent(change);
258
+ }),
259
+ );
260
+ return provider;
261
+ }
262
+ }