Skip to content

Commit 750a135

Browse files
committed
Postpone user settings validation against schema
1 parent cf5e66c commit 750a135

File tree

1 file changed

+98
-65
lines changed

1 file changed

+98
-65
lines changed

packages/jupyterlab-lsp/src/settings.ts

Lines changed: 98 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ export class SettingsUIManager {
116116
}
117117
);
118118
}
119+
this._canonical = null;
120+
this._original = null;
119121
this._validationAttempt = 0;
120122
this._lastValidation = null;
121123
this._lastUserServerSettings = null;
@@ -132,8 +134,6 @@ export class SettingsUIManager {
132134
* is performed on settingRegistry with regard to pluginId.
133135
*/
134136
async setupSchemaForUI(pluginId: string): Promise<void> {
135-
let canonical: ISettingRegistry.ISchema | null;
136-
let original: ISettingRegistry.ISchema | null = null;
137137
const languageServerManager = this.options.languageServerManager;
138138
/**
139139
* Populate the plugin's schema defaults.
@@ -234,87 +234,44 @@ export class SettingsUIManager {
234234
schema.properties!.language_servers.properties = knownServersConfig;
235235
schema.properties!.language_servers.default = defaults;
236236

237-
const lastValidation = this._lastValidation;
238-
// do not re-validate if neither schema, nor user settings changed
239-
if (
240-
lastValidation === null ||
241-
lastValidation.rawUserSettings !== plugin.raw ||
242-
!JSONExt.deepEqual(lastValidation.schema, schema)
243-
) {
244-
// test if we can apply the schema without causing validation error
245-
// (is the configuration held by the user compatible with the schema?)
246-
this._validationAttempt += 1;
247-
// the validator will parse raw plugin data into this object;
248-
// we do not do anything with those right now.
249-
const parsedData = { composite: {}, user: {} };
250-
const validationErrors =
251-
this.options.settingRegistry.validator.validateData(
252-
{
253-
// the plugin schema is cached so we have to provide a dummy ID.
254-
id: `lsp-validation-attempt-${this._validationAttempt}`,
255-
raw: plugin.raw,
256-
data: parsedData,
257-
version: plugin.version,
258-
schema: schema
259-
},
260-
true
261-
);
262-
263-
if (validationErrors) {
264-
console.error(
265-
'LSP server settings validation failed; graphical interface for settings will run in schema-free mode; errors:',
266-
validationErrors
267-
);
268-
this._validationErrors = validationErrors;
269-
if (!original) {
270-
console.error(
271-
'Original language servers schema not available to restore non-transformed values.'
272-
);
273-
} else {
274-
if (!original.properties!.language_servers.properties) {
275-
delete schema.properties!.language_servers.properties;
276-
}
277-
if (!original.properties!.language_servers.default) {
278-
delete schema.properties!.language_servers.default;
279-
}
280-
}
281-
}
282-
283-
this._lastValidation = {
284-
rawUserSettings: plugin.raw,
285-
schema: schema
286-
};
287-
}
237+
this._validateSchemaLater(plugin, schema).catch(this.console.warn);
288238

289239
this._defaults = defaults;
290240
};
291241

292242
// Transform the plugin object to return different schema than the default.
293243
this.options.settingRegistry.transform(pluginId, {
294244
fetch: plugin => {
295-
// Profiling data:
245+
// Profiling data (earlier version):
296246
// Initial fetch: 61-64 ms
297247
// Subsequent without change: <1ms
298248
// Session change: 642 ms.
299-
// 91% spent on `validateData()`
300-
// 10% in addSchema().
249+
// 91% spent on `validateData()` of which 10% in addSchema().
301250
// 1.8% spent on `deepCopy()`
302251
// 1.79% spend on other tasks in `populate()`
303-
304-
if (!original) {
305-
original = JSONExt.deepCopy(plugin.schema);
252+
// There is a limit on the transformation time, and failing to transform
253+
// in the default 1 second means that no settigns whatsoever are available.
254+
// Therefore validation in `populate()` was moved into an async function;
255+
// this means that we need to trigger re-load of settings
256+
// if there validation errors.
257+
258+
// Only store the original schema the first time.
259+
if (!this._original) {
260+
this._original = JSONExt.deepCopy(plugin.schema);
306261
}
307-
// Only override the canonical schema the first time.
308-
if (!canonical) {
309-
canonical = JSONExt.deepCopy(plugin.schema);
310-
populate(plugin, canonical);
262+
// Only override the canonical schema the first time (or after reset).
263+
if (!this._canonical) {
264+
this._canonical = JSONExt.deepCopy(plugin.schema);
265+
populate(plugin, this._canonical);
311266
}
312267

313268
return {
314269
data: plugin.data,
315270
id: plugin.id,
316271
raw: plugin.raw,
317-
schema: canonical,
272+
schema: this._validationErrors.length
273+
? this._original
274+
: this._canonical,
318275
version: plugin.version
319276
};
320277
},
@@ -375,11 +332,85 @@ export class SettingsUIManager {
375332
// note: has to be after transform is called for the first time to avoid
376333
// race condition, see https://github.com/jupyterlab/jupyterlab/issues/12978
377334
languageServerManager.sessionsChanged.connect(async () => {
378-
canonical = null;
335+
this._canonical = null;
379336
await this.options.settingRegistry.reload(pluginId);
380337
});
381338
}
382339

340+
private _wasPreviouslyValidated(
341+
plugin: ISettingRegistry.IPlugin,
342+
schema: ISettingRegistry.ISchema
343+
) {
344+
return (
345+
this._lastValidation !== null &&
346+
this._lastValidation.rawUserSettings === plugin.raw &&
347+
JSONExt.deepEqual(this._lastValidation.schema, schema)
348+
);
349+
}
350+
351+
/**
352+
* Validate user settings from plugin against provided schema,
353+
* asynchronously to avoid blocking the main thread.
354+
* Stores validation reult in `this._validationErrors`.
355+
*/
356+
private async _validateSchemaLater(
357+
plugin: ISettingRegistry.IPlugin,
358+
schema: ISettingRegistry.ISchema
359+
) {
360+
// do not re-validate if neither schema, nor user settings changed
361+
if (this._wasPreviouslyValidated(plugin, schema)) {
362+
return;
363+
}
364+
// test if we can apply the schema without causing validation error
365+
// (is the configuration held by the user compatible with the schema?)
366+
this._validationAttempt += 1;
367+
// the validator will parse raw plugin data into this object;
368+
// we do not do anything with those right now.
369+
const parsedData = { composite: {}, user: {} };
370+
const validationErrors =
371+
this.options.settingRegistry.validator.validateData(
372+
{
373+
// the plugin schema is cached so we have to provide a dummy ID;
374+
// can be simplified once https://github.com/jupyterlab/jupyterlab/issues/12978 is fixed.
375+
id: `lsp-validation-attempt-${this._validationAttempt}`,
376+
raw: plugin.raw,
377+
data: parsedData,
378+
version: plugin.version,
379+
schema: schema
380+
},
381+
true
382+
);
383+
384+
this._lastValidation = {
385+
rawUserSettings: plugin.raw,
386+
schema: schema
387+
};
388+
389+
if (validationErrors) {
390+
console.error(
391+
'LSP server settings validation failed; graphical interface for settings will run in schema-free mode; errors:',
392+
validationErrors
393+
);
394+
this._validationErrors = validationErrors;
395+
if (!this._original) {
396+
console.error(
397+
'Original language servers schema not available to restore non-transformed values.'
398+
);
399+
} else {
400+
if (!this._original.properties!.language_servers.properties) {
401+
delete schema.properties!.language_servers.properties;
402+
}
403+
if (!this._original.properties!.language_servers.default) {
404+
delete schema.properties!.language_servers.default;
405+
}
406+
}
407+
408+
// Reload settings to use non-restrictive schema; this requires fixing
409+
// https://github.com/jupyterlab/jupyterlab/issues/12978 upstream to work.
410+
await this.options.settingRegistry.reload(plugin.id);
411+
}
412+
}
413+
383414
private async _warnConflicts(conflicts: SettingsMergeConflicts) {
384415
showDialog({
385416
body: renderCollapseConflicts({
@@ -454,4 +485,6 @@ export class SettingsUIManager {
454485
private _lastValidation: IValidationData | null;
455486
private _lastUserServerSettings: LanguageServerSettings | null;
456487
private _lastUserServerSettingsDoted: LanguageServerSettings | null;
488+
private _canonical: ISettingRegistry.ISchema | null;
489+
private _original: ISettingRegistry.ISchema | null;
457490
}

0 commit comments

Comments
 (0)