Support additionalProperties
This commit is contained in:
parent
fe91c2b910
commit
3431da2b80
|
@ -49,8 +49,8 @@
|
|||
},
|
||||
"emmet.includeLanguages": {
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
".*": { "type": "string" }
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": {},
|
||||
"markdownDescription": "%emmetIncludeLanguages%"
|
||||
|
|
|
@ -238,9 +238,9 @@ configurationRegistry.registerConfiguration({
|
|||
[FILES_ASSOCIATIONS_CONFIG]: {
|
||||
'type': 'object',
|
||||
'markdownDescription': nls.localize('associations', "Configure file associations to languages (e.g. `\"*.extension\": \"html\"`). These have precedence over the default associations of the languages installed."),
|
||||
'patternProperties': {
|
||||
'.*': { type: 'string' },
|
||||
},
|
||||
'additionalProperties': {
|
||||
'type': 'string'
|
||||
}
|
||||
},
|
||||
'files.encoding': {
|
||||
'type': 'string',
|
||||
|
|
|
@ -93,67 +93,81 @@ function getObjectDisplayValue(element: SettingsTreeSettingElement): IObjectData
|
|||
{ ...element.defaultValue, ...element.scopeValue } :
|
||||
element.defaultValue;
|
||||
|
||||
let items: IObjectDataItem[] = [];
|
||||
const items: IObjectDataItem[] = [];
|
||||
|
||||
if (element.setting.objectProperties) {
|
||||
const wellDefinedKeys = Object.keys(element.setting.objectProperties);
|
||||
const keyOptions = wellDefinedKeys.map(value => ({ value }));
|
||||
const { objectProperties, objectPatternProperties, objectAdditionalProperties } = element.setting;
|
||||
|
||||
items = items.concat(
|
||||
wellDefinedKeys
|
||||
.filter(key => !!data[key])
|
||||
.map(key => {
|
||||
const valueEnumOptions = getEnumOptionsFromSchema(element.setting.objectProperties![key]);
|
||||
|
||||
return {
|
||||
key: {
|
||||
type: 'enum',
|
||||
data: key,
|
||||
options: keyOptions,
|
||||
},
|
||||
value: {
|
||||
type: valueEnumOptions.length > 0 ? 'enum' : 'string',
|
||||
data: data[key],
|
||||
options: valueEnumOptions,
|
||||
},
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (element.setting.objectPatternProperties) {
|
||||
const patternsAndSchemas = Object
|
||||
.entries(element.setting.objectPatternProperties)
|
||||
.map(([pattern, schema]) => ({
|
||||
pattern: new RegExp(pattern),
|
||||
schema
|
||||
}));
|
||||
|
||||
const keysWithSchema = Object.keys(data)
|
||||
.filter(key => !!data[key] && !(key in (element.setting.objectProperties ?? {})))
|
||||
.map(key => {
|
||||
const patternAndSchema = patternsAndSchemas.find(({ pattern }) => pattern.test(key));
|
||||
return patternAndSchema
|
||||
? { key, schema: patternAndSchema.schema }
|
||||
: undefined;
|
||||
})
|
||||
.filter(isDefined);
|
||||
|
||||
items = items.concat(keysWithSchema.map(({ key, schema }) => {
|
||||
const valueEnumOptions = getEnumOptionsFromSchema(schema);
|
||||
|
||||
return {
|
||||
key: { type: 'string', data: key },
|
||||
value: {
|
||||
type: valueEnumOptions.length > 0 ? 'enum' : 'string',
|
||||
data: data[key],
|
||||
options: valueEnumOptions,
|
||||
}
|
||||
};
|
||||
const patternsAndSchemas = Object
|
||||
.entries(objectPatternProperties ?? {})
|
||||
.map(([pattern, schema]) => ({
|
||||
pattern: new RegExp(pattern),
|
||||
schema
|
||||
}));
|
||||
}
|
||||
|
||||
// TODO @9at8: What should we do if properties don't match?
|
||||
const allKeys = new Set<string>(Object.keys(data).concat(Object.keys(objectProperties ?? {})));
|
||||
const wellDefinedKeys: string[] = [];
|
||||
const patternKeysWithSchema = new Map<string, IJSONSchema>();
|
||||
const additionalKeys: string[] = [];
|
||||
const additionalValueEnums = getEnumOptionsFromSchema(
|
||||
typeof objectAdditionalProperties === 'boolean'
|
||||
? {}
|
||||
: objectAdditionalProperties ?? {}
|
||||
);
|
||||
|
||||
// copy the keys into appropriate buckets
|
||||
allKeys.forEach(key => {
|
||||
if (key in (objectProperties ?? {})) {
|
||||
wellDefinedKeys.push(key);
|
||||
return;
|
||||
}
|
||||
|
||||
const schema = patternsAndSchemas.find(({ pattern }) => pattern.test(key))?.schema;
|
||||
|
||||
if (isDefined(schema)) {
|
||||
patternKeysWithSchema.set(key, schema);
|
||||
} else {
|
||||
additionalKeys.push(key);
|
||||
}
|
||||
});
|
||||
|
||||
wellDefinedKeys.forEach(key => {
|
||||
const valueEnumOptions = getEnumOptionsFromSchema(objectProperties![key]);
|
||||
items.push({
|
||||
key: {
|
||||
type: 'enum',
|
||||
data: key,
|
||||
options: wellDefinedKeys.map(value => ({ value })),
|
||||
},
|
||||
value: {
|
||||
type: valueEnumOptions.length > 0 ? 'enum' : 'string',
|
||||
data: data[key],
|
||||
options: valueEnumOptions,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
patternKeysWithSchema.forEach((schema, key) => {
|
||||
const valueEnumOptions = getEnumOptionsFromSchema(schema);
|
||||
items.push({
|
||||
key: { type: 'string', data: key },
|
||||
value: {
|
||||
type: valueEnumOptions.length > 0 ? 'enum' : 'string',
|
||||
data: data[key],
|
||||
options: valueEnumOptions,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
additionalKeys.forEach(key => {
|
||||
items.push({
|
||||
key: { type: 'string', data: key },
|
||||
value: {
|
||||
type: additionalValueEnums.length > 0 ? 'enum' : 'string',
|
||||
data: data[key],
|
||||
options: additionalValueEnums,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return items;
|
||||
}
|
||||
|
@ -984,20 +998,9 @@ export class SettingObjectRenderer extends AbstractSettingRenderer implements IT
|
|||
newValue[e.item.key.data] = e.item.value.data;
|
||||
}
|
||||
|
||||
function sortKeys<T extends object>(obj: T) {
|
||||
const sortedKeys = Object.keys(obj)
|
||||
.sort((a, b) => a.localeCompare(b)) as Array<keyof T>;
|
||||
|
||||
const retVal: Partial<T> = {};
|
||||
for (const key of sortedKeys) {
|
||||
retVal[key] = obj[key];
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
this._onDidChangeSetting.fire({
|
||||
key: template.context.setting.key,
|
||||
value: Object.keys(newValue).length === 0 ? undefined : sortKeys(newValue),
|
||||
value: Object.keys(newValue).length === 0 ? undefined : newValue,
|
||||
type: template.context.valueType
|
||||
});
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ import { MODIFIED_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/pr
|
|||
import { IExtensionSetting, ISearchResult, ISetting, SettingValueType } from 'vs/workbench/services/preferences/common/preferences';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { FOLDER_SCOPES, WORKSPACE_SCOPES, REMOTE_MACHINE_SCOPES, LOCAL_MACHINE_SCOPES } from 'vs/workbench/services/configuration/common/configuration';
|
||||
import { IJSONSchemaMap } from 'vs/base/common/jsonSchema';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
|
||||
export const ONLINE_SERVICES_SETTING_TAG = 'usesOnlineServices';
|
||||
|
||||
|
@ -468,21 +468,41 @@ export function isExcludeSetting(setting: ISetting): boolean {
|
|||
setting.key === 'files.watcherExclude';
|
||||
}
|
||||
|
||||
function isObjectRenderableSchemaMap(schemaMap: IJSONSchemaMap): boolean {
|
||||
return Object.values(schemaMap).every(({ type }) => type === 'string');
|
||||
function isObjectRenderableSchema({ type }: IJSONSchema): boolean {
|
||||
return type === 'string';
|
||||
}
|
||||
|
||||
function isObjectSetting(setting: ISetting): boolean {
|
||||
if (setting.type !== 'object') {
|
||||
function isObjectSetting({
|
||||
type,
|
||||
objectProperties,
|
||||
objectPatternProperties,
|
||||
objectAdditionalProperties
|
||||
}: ISetting): boolean {
|
||||
if (type !== 'object') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isUndefinedOrNull(setting.objectProperties) && isUndefinedOrNull(setting.objectPatternProperties)) {
|
||||
// object can have any shape
|
||||
if (
|
||||
isUndefinedOrNull(objectProperties) &&
|
||||
isUndefinedOrNull(objectPatternProperties) &&
|
||||
isUndefinedOrNull(objectAdditionalProperties)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isObjectRenderableSchemaMap(setting.objectProperties ?? {})
|
||||
&& isObjectRenderableSchemaMap(setting.objectPatternProperties ?? {});
|
||||
// object additional properties allow it to have any shape
|
||||
if (objectAdditionalProperties === true) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Object.values(objectProperties ?? {}).every(isObjectRenderableSchema) &&
|
||||
Object.values(objectPatternProperties ?? {}).every(isObjectRenderableSchema) &&
|
||||
(
|
||||
typeof objectAdditionalProperties === 'object'
|
||||
? isObjectRenderableSchema(objectAdditionalProperties)
|
||||
: true
|
||||
);
|
||||
}
|
||||
|
||||
function settingTypeEnumRenderable(_type: string | string[]) {
|
||||
|
|
|
@ -7,7 +7,7 @@ import { IStringDictionary } from 'vs/base/common/collections';
|
|||
import { Event } from 'vs/base/common/event';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
import { IJSONSchemaMap } from 'vs/base/common/jsonSchema';
|
||||
import { IJSONSchemaMap, IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { localize } from 'vs/nls';
|
||||
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
|
||||
|
@ -68,6 +68,7 @@ export interface ISetting {
|
|||
arrayItemType?: string;
|
||||
objectProperties?: IJSONSchemaMap,
|
||||
objectPatternProperties?: IJSONSchemaMap,
|
||||
objectAdditionalProperties?: boolean | IJSONSchema,
|
||||
enum?: string[];
|
||||
enumDescriptions?: string[];
|
||||
enumDescriptionsAreMarkdown?: boolean;
|
||||
|
|
|
@ -622,6 +622,7 @@ export class DefaultSettings extends Disposable {
|
|||
|
||||
const objectProperties = prop.type === 'object' ? prop.properties : undefined;
|
||||
const objectPatternProperties = prop.type === 'object' ? prop.patternProperties : undefined;
|
||||
const objectAdditionalProperties = prop.type === 'object' ? prop.additionalProperties : undefined;
|
||||
|
||||
result.push({
|
||||
key,
|
||||
|
@ -638,6 +639,7 @@ export class DefaultSettings extends Disposable {
|
|||
arrayItemType: listItemType,
|
||||
objectProperties,
|
||||
objectPatternProperties,
|
||||
objectAdditionalProperties,
|
||||
enum: prop.enum,
|
||||
enumDescriptions: prop.enumDescriptions || prop.markdownEnumDescriptions,
|
||||
enumDescriptionsAreMarkdown: !prop.enumDescriptions,
|
||||
|
|
Loading…
Reference in a new issue