1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 | import debounce = require('p-debounce');
|
20 | import { injectable, inject } from 'inversify';
|
21 | import { JSONExt, JSONValue } from '@phosphor/coreutils';
|
22 | import URI from '../../common/uri';
|
23 | import { Disposable, DisposableCollection, Emitter, Event, isObject } from '../../common';
|
24 | import { Deferred } from '../../common/promise-util';
|
25 | import { PreferenceScope } from './preference-scope';
|
26 | import { PreferenceLanguageOverrideService } from './preference-language-override-service';
|
27 |
|
28 | export interface PreferenceProviderDataChange {
|
29 | |
30 |
|
31 |
|
32 | readonly preferenceName: string;
|
33 | |
34 |
|
35 |
|
36 | readonly newValue?: any;
|
37 | |
38 |
|
39 |
|
40 | readonly oldValue?: any;
|
41 | |
42 |
|
43 |
|
44 | readonly scope: PreferenceScope;
|
45 | |
46 |
|
47 |
|
48 | readonly domain?: string[];
|
49 | }
|
50 |
|
51 | export namespace PreferenceProviderDataChange {
|
52 | export function affects(change: PreferenceProviderDataChange, resourceUri?: string): boolean {
|
53 | const resourcePath = resourceUri && new URI(resourceUri).path;
|
54 | const domain = change.domain;
|
55 | return !resourcePath || !domain || domain.some(uri => new URI(uri).path.relativity(resourcePath) >= 0);
|
56 | }
|
57 | }
|
58 |
|
59 | export interface PreferenceProviderDataChanges {
|
60 | [preferenceName: string]: PreferenceProviderDataChange;
|
61 | }
|
62 |
|
63 | export interface PreferenceResolveResult<T> {
|
64 | configUri?: URI
|
65 | value?: T
|
66 | }
|
67 |
|
68 |
|
69 |
|
70 |
|
71 |
|
72 | @injectable()
|
73 | export abstract class PreferenceProvider implements Disposable {
|
74 |
|
75 | @inject(PreferenceLanguageOverrideService) protected readonly preferenceOverrideService: PreferenceLanguageOverrideService;
|
76 |
|
77 | protected readonly onDidPreferencesChangedEmitter = new Emitter<PreferenceProviderDataChanges>();
|
78 | readonly onDidPreferencesChanged: Event<PreferenceProviderDataChanges> = this.onDidPreferencesChangedEmitter.event;
|
79 |
|
80 | protected readonly toDispose = new DisposableCollection();
|
81 |
|
82 | protected readonly _ready = new Deferred<void>();
|
83 |
|
84 | constructor() {
|
85 | this.toDispose.push(this.onDidPreferencesChangedEmitter);
|
86 | }
|
87 |
|
88 | dispose(): void {
|
89 | this.toDispose.dispose();
|
90 | }
|
91 |
|
92 | protected deferredChanges: PreferenceProviderDataChanges | undefined;
|
93 |
|
94 | |
95 |
|
96 |
|
97 |
|
98 | protected emitPreferencesChangedEvent(changes: PreferenceProviderDataChanges | PreferenceProviderDataChange[]): Promise<boolean> {
|
99 | if (Array.isArray(changes)) {
|
100 | for (const change of changes) {
|
101 | this.mergePreferenceProviderDataChange(change);
|
102 | }
|
103 | } else {
|
104 | for (const preferenceName of Object.keys(changes)) {
|
105 | this.mergePreferenceProviderDataChange(changes[preferenceName]);
|
106 | }
|
107 | }
|
108 | return this.fireDidPreferencesChanged();
|
109 | }
|
110 |
|
111 | protected mergePreferenceProviderDataChange(change: PreferenceProviderDataChange): void {
|
112 | if (!this.deferredChanges) {
|
113 | this.deferredChanges = {};
|
114 | }
|
115 | const current = this.deferredChanges[change.preferenceName];
|
116 | const { newValue, scope, domain } = change;
|
117 | if (!current) {
|
118 |
|
119 | this.deferredChanges[change.preferenceName] = change;
|
120 | } else if (current.oldValue === newValue) {
|
121 |
|
122 | delete this.deferredChanges[change.preferenceName];
|
123 | } else {
|
124 |
|
125 | Object.assign(current, { newValue, scope, domain });
|
126 | }
|
127 | }
|
128 |
|
129 | protected fireDidPreferencesChanged = debounce(() => {
|
130 | const changes = this.deferredChanges;
|
131 | this.deferredChanges = undefined;
|
132 | if (changes && Object.keys(changes).length) {
|
133 | this.onDidPreferencesChangedEmitter.fire(changes);
|
134 | return true;
|
135 | }
|
136 | return false;
|
137 | }, 0);
|
138 |
|
139 | |
140 |
|
141 |
|
142 |
|
143 |
|
144 |
|
145 |
|
146 |
|
147 |
|
148 | get<T>(preferenceName: string, resourceUri?: string): T | undefined {
|
149 | return this.resolve<T>(preferenceName, resourceUri).value;
|
150 | }
|
151 |
|
152 | |
153 |
|
154 |
|
155 |
|
156 |
|
157 |
|
158 |
|
159 |
|
160 |
|
161 |
|
162 | resolve<T>(preferenceName: string, resourceUri?: string): PreferenceResolveResult<T> {
|
163 | const value = this.getPreferences(resourceUri)[preferenceName];
|
164 | if (value !== undefined) {
|
165 | return {
|
166 | value,
|
167 | configUri: this.getConfigUri(resourceUri)
|
168 | };
|
169 | }
|
170 | return {};
|
171 | }
|
172 |
|
173 | abstract getPreferences(resourceUri?: string): { [p: string]: any };
|
174 |
|
175 | |
176 |
|
177 |
|
178 |
|
179 |
|
180 |
|
181 |
|
182 |
|
183 |
|
184 |
|
185 |
|
186 | abstract setPreference(key: string, value: any, resourceUri?: string): Promise<boolean>;
|
187 |
|
188 | |
189 |
|
190 |
|
191 |
|
192 | get ready(): Promise<void> {
|
193 | return this._ready.promise;
|
194 | }
|
195 |
|
196 | |
197 |
|
198 |
|
199 |
|
200 |
|
201 | getDomain(): string[] | undefined {
|
202 | return undefined;
|
203 | }
|
204 |
|
205 | |
206 |
|
207 |
|
208 |
|
209 |
|
210 |
|
211 |
|
212 | getConfigUri(resourceUri?: string, sectionName?: string): URI | undefined {
|
213 | return undefined;
|
214 | }
|
215 |
|
216 | |
217 |
|
218 |
|
219 |
|
220 |
|
221 |
|
222 |
|
223 | getContainingConfigUri?(resourceUri?: string, sectionName?: string): URI | undefined;
|
224 |
|
225 | static merge(source: JSONValue | undefined, target: JSONValue): JSONValue {
|
226 | if (source === undefined || !JSONExt.isObject(source)) {
|
227 | return JSONExt.deepCopy(target);
|
228 | }
|
229 | if (JSONExt.isPrimitive(target)) {
|
230 | return {};
|
231 | }
|
232 | for (const key of Object.keys(target)) {
|
233 | const value = (target as any)[key];
|
234 | if (key in source) {
|
235 | if (JSONExt.isObject(source[key]) && JSONExt.isObject(value)) {
|
236 | this.merge(source[key], value);
|
237 | continue;
|
238 | } else if (JSONExt.isArray(source[key]) && JSONExt.isArray(value)) {
|
239 | source[key] = [...JSONExt.deepCopy(source[key] as any), ...JSONExt.deepCopy(value)];
|
240 | continue;
|
241 | }
|
242 | }
|
243 | source[key] = JSONExt.deepCopy(value);
|
244 | }
|
245 | return source;
|
246 | }
|
247 |
|
248 | |
249 |
|
250 |
|
251 | static deepEqual(a: JSONValue | undefined, b: JSONValue | undefined): boolean {
|
252 | if (a === b) { return true; }
|
253 | if (a === undefined || b === undefined) { return false; }
|
254 | return JSONExt.deepEqual(a, b);
|
255 | }
|
256 |
|
257 | protected getParsedContent(jsonData: any): { [key: string]: any } {
|
258 | const preferences: { [key: string]: any } = {};
|
259 | if (!isObject(jsonData)) {
|
260 | return preferences;
|
261 | }
|
262 | for (const [preferenceName, preferenceValue] of Object.entries(jsonData)) {
|
263 | if (this.preferenceOverrideService.testOverrideValue(preferenceName, preferenceValue)) {
|
264 | for (const [overriddenPreferenceName, overriddenValue] of Object.entries(preferenceValue)) {
|
265 | preferences[`${preferenceName}.${overriddenPreferenceName}`] = overriddenValue;
|
266 | }
|
267 | } else {
|
268 | preferences[preferenceName] = preferenceValue;
|
269 | }
|
270 | }
|
271 | return preferences;
|
272 | }
|
273 |
|
274 | canHandleScope(scope: PreferenceScope): boolean {
|
275 | return true;
|
276 | }
|
277 | }
|