Merge from master

This commit is contained in:
Raj Musuku
2019-02-21 17:56:04 -08:00
parent 5a146e34fa
commit 666ae11639
11482 changed files with 119352 additions and 255574 deletions

View File

@@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { TPromise } from 'vs/base/common/winjs.base';
import { distinct } from 'vs/base/common/arrays';
import * as strings from 'vs/base/common/strings';
import { OperatingSystem, language, LANGUAGE_DEFAULT } from 'vs/base/common/platform';
@@ -15,7 +14,6 @@ import { AriaLabelProvider, UserSettingsLabelProvider, UILabelProvider, Modifier
import { MenuRegistry, ILocalizedString, ICommandAction } from 'vs/platform/actions/common/actions';
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions';
import { EditorModel } from 'vs/workbench/common/editor';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem';
import { KeybindingResolver } from 'vs/platform/keybinding/common/keybindingResolver';
@@ -80,8 +78,7 @@ export class KeybindingsEditorModel extends EditorModel {
constructor(
os: OperatingSystem,
@IKeybindingService private keybindingsService: IKeybindingService,
@IExtensionService private extensionService: IExtensionService
@IKeybindingService private keybindingsService: IKeybindingService
) {
super();
this.modifierLabels = {
@@ -157,35 +154,32 @@ export class KeybindingsEditorModel extends EditorModel {
}
private splitKeybindingWords(wordsSeparatedBySpaces: string[]): string[] {
const result = [];
const result: string[] = [];
for (const word of wordsSeparatedBySpaces) {
result.push(...word.split('+').filter(w => !!w));
}
return result;
}
public resolve(editorActionsLabels: { [id: string]: string; }): TPromise<EditorModel> {
return this.extensionService.whenInstalledExtensionsRegistered()
.then(() => {
const workbenchActionsRegistry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
public resolve(editorActionsLabels: { [id: string]: string; }): Thenable<EditorModel> {
const workbenchActionsRegistry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
this._keybindingItemsSortedByPrecedence = [];
const boundCommands: Map<string, boolean> = new Map<string, boolean>();
for (const keybinding of this.keybindingsService.getKeybindings()) {
if (keybinding.command) { // Skip keybindings without commands
this._keybindingItemsSortedByPrecedence.push(KeybindingsEditorModel.toKeybindingEntry(keybinding.command, keybinding, workbenchActionsRegistry, editorActionsLabels));
boundCommands.set(keybinding.command, true);
}
}
this._keybindingItemsSortedByPrecedence = [];
const boundCommands: Map<string, boolean> = new Map<string, boolean>();
for (const keybinding of this.keybindingsService.getKeybindings()) {
if (keybinding.command) { // Skip keybindings without commands
this._keybindingItemsSortedByPrecedence.push(KeybindingsEditorModel.toKeybindingEntry(keybinding.command, keybinding, workbenchActionsRegistry, editorActionsLabels));
boundCommands.set(keybinding.command, true);
}
}
const commandsWithDefaultKeybindings = this.keybindingsService.getDefaultKeybindings().map(keybinding => keybinding.command);
for (const command of KeybindingResolver.getAllUnboundCommands(boundCommands)) {
const keybindingItem = new ResolvedKeybindingItem(null, command, null, null, commandsWithDefaultKeybindings.indexOf(command) === -1);
this._keybindingItemsSortedByPrecedence.push(KeybindingsEditorModel.toKeybindingEntry(command, keybindingItem, workbenchActionsRegistry, editorActionsLabels));
}
this._keybindingItems = this._keybindingItemsSortedByPrecedence.slice(0).sort((a, b) => KeybindingsEditorModel.compareKeybindingData(a, b));
return this;
});
const commandsWithDefaultKeybindings = this.keybindingsService.getDefaultKeybindings().map(keybinding => keybinding.command);
for (const command of KeybindingResolver.getAllUnboundCommands(boundCommands)) {
const keybindingItem = new ResolvedKeybindingItem(null, command, null, null, commandsWithDefaultKeybindings.indexOf(command) === -1);
this._keybindingItemsSortedByPrecedence.push(KeybindingsEditorModel.toKeybindingEntry(command, keybindingItem, workbenchActionsRegistry, editorActionsLabels));
}
this._keybindingItems = this._keybindingItemsSortedByPrecedence.slice(0).sort((a, b) => KeybindingsEditorModel.compareKeybindingData(a, b));
return Promise.resolve(this);
}
private static getId(keybindingItem: IKeybindingItem): string {
@@ -254,12 +248,12 @@ export class KeybindingsEditorModel extends EditorModel {
class KeybindingItemMatches {
public readonly commandIdMatches: IMatch[] = null;
public readonly commandLabelMatches: IMatch[] = null;
public readonly commandDefaultLabelMatches: IMatch[] = null;
public readonly sourceMatches: IMatch[] = null;
public readonly whenMatches: IMatch[] = null;
public readonly keybindingMatches: KeybindingMatches = null;
public readonly commandIdMatches: IMatch[] | null = null;
public readonly commandLabelMatches: IMatch[] | null = null;
public readonly commandDefaultLabelMatches: IMatch[] | null = null;
public readonly sourceMatches: IMatch[] | null = null;
public readonly whenMatches: IMatch[] | null = null;
public readonly keybindingMatches: KeybindingMatches | null = null;
constructor(private modifierLabels: ModifierLabels, keybindingItem: IKeybindingItem, searchValue: string, words: string[], keybindingWords: string[], completeMatch: boolean) {
if (!completeMatch) {
@@ -304,7 +298,7 @@ class KeybindingItemMatches {
private matchesKeybinding(keybinding: ResolvedKeybinding, searchValue: string, words: string[], completeMatch: boolean): KeybindingMatches {
const [firstPart, chordPart] = keybinding.getParts();
if (strings.compareIgnoreCase(searchValue, keybinding.getAriaLabel()) === 0 || strings.compareIgnoreCase(searchValue, keybinding.getLabel()) === 0) {
if (strings.compareIgnoreCase(searchValue, keybinding.getUserSettingsLabel()) === 0 || strings.compareIgnoreCase(searchValue, keybinding.getAriaLabel()) === 0 || strings.compareIgnoreCase(searchValue, keybinding.getLabel()) === 0) {
return {
firstPart: this.createCompleteMatch(firstPart),
chordPart: this.createCompleteMatch(chordPart)
@@ -314,9 +308,9 @@ class KeybindingItemMatches {
let firstPartMatch: KeybindingMatch = {};
let chordPartMatch: KeybindingMatch = {};
const matchedWords = [];
let firstPartMatchedWords = [];
let chordPartMatchedWords = [];
const matchedWords: number[] = [];
let firstPartMatchedWords: number[] = [];
let chordPartMatchedWords: number[] = [];
let matchFirstPart = true;
for (let index = 0; index < words.length; index++) {
const word = words[index];

View File

@@ -3,21 +3,35 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import URI from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IEditorOptions } from 'vs/platform/editor/common/editor';
import { IEditor } from 'vs/workbench/common/editor';
import { ITextModel } from 'vs/editor/common/model';
import { IRange } from 'vs/editor/common/core/range';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { join } from 'vs/base/common/paths';
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { Event } from 'vs/base/common/event';
import { IStringDictionary } from 'vs/base/common/collections';
import { ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { Event } from 'vs/base/common/event';
import { join } from 'vs/base/common/paths';
import { URI } from 'vs/base/common/uri';
import { IRange } from 'vs/editor/common/core/range';
import { ITextModel } from 'vs/editor/common/model';
import { localize } from 'vs/nls';
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import { IEditorOptions } from 'vs/platform/editor/common/editor';
import { ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { EditorOptions, IEditor } from 'vs/workbench/common/editor';
import { IEditorGroup } from 'vs/workbench/services/group/common/editorGroupsService';
import { Settings2EditorModel } from 'vs/workbench/services/preferences/common/preferencesModels';
export enum SettingValueType {
Null = 'null',
Enum = 'enum',
String = 'string',
Integer = 'integer',
Number = 'number',
Boolean = 'boolean',
Exclude = 'exclude',
Complex = 'complex',
NullableInteger = 'nullable-integer',
NullableNumber = 'nullable-number'
}
export interface ISettingsGroup {
id: string;
@@ -41,15 +55,19 @@ export interface ISetting {
value: any;
valueRange: IRange;
description: string[];
descriptionIsMarkdown: boolean;
descriptionRanges: IRange[];
overrides?: ISetting[];
overrideOf?: ISetting;
deprecationMessage?: string;
// TODO@roblou maybe need new type and new EditorModel for GUI editor instead of ISetting which is used for text settings editor
scope?: ConfigurationScope;
type?: string | string[];
enum?: string[];
enumDescriptions?: string[];
enumDescriptionsAreMarkdown?: boolean;
tags?: string[];
validator?: (value: any) => string;
}
export interface IExtensionSetting extends ISetting {
@@ -116,7 +134,7 @@ export interface IFilterMetadata {
}
export interface IPreferencesEditorModel<T> {
uri: URI;
uri?: URI;
getPreference(key: string): T;
dispose(): void;
}
@@ -132,6 +150,45 @@ export interface ISettingsEditorModel extends IPreferencesEditorModel<ISetting>
updateResultGroup(id: string, resultGroup: ISearchResultGroup): IFilterResult;
}
export interface ISettingsEditorOptions extends IEditorOptions {
target?: ConfigurationTarget;
folderUri?: URI;
query?: string;
}
/**
* TODO Why do we need this class?
*/
export class SettingsEditorOptions extends EditorOptions implements ISettingsEditorOptions {
target?: ConfigurationTarget;
folderUri?: URI;
query?: string;
static create(settings: ISettingsEditorOptions): SettingsEditorOptions {
if (!settings) {
return null;
}
const options = new SettingsEditorOptions();
options.target = settings.target;
options.folderUri = settings.folderUri;
options.query = settings.query;
// IEditorOptions
options.preserveFocus = settings.preserveFocus;
options.forceReload = settings.forceReload;
options.revealIfVisible = settings.revealIfVisible;
options.revealIfOpened = settings.revealIfOpened;
options.pinned = settings.pinned;
options.index = settings.index;
options.inactive = settings.inactive;
return options;
}
}
export interface IKeybindingsEditorModel<T> extends IPreferencesEditorModel<T> {
}
@@ -144,18 +201,18 @@ export interface IPreferencesService {
workspaceSettingsResource: URI;
getFolderSettingsResource(resource: URI): URI;
resolveModel(uri: URI): TPromise<ITextModel>;
createPreferencesEditorModel<T>(uri: URI): TPromise<IPreferencesEditorModel<T>>;
resolveModel(uri: URI): Thenable<ITextModel>;
createPreferencesEditorModel<T>(uri: URI): Thenable<IPreferencesEditorModel<T>>;
createSettings2EditorModel(): Settings2EditorModel; // TODO
openRawDefaultSettings(): TPromise<IEditor>;
openSettings(): TPromise<IEditor>;
openSettings2(): TPromise<IEditor>;
openGlobalSettings(options?: IEditorOptions, group?: IEditorGroup): TPromise<IEditor>;
openWorkspaceSettings(options?: IEditorOptions, group?: IEditorGroup): TPromise<IEditor>;
openFolderSettings(folder: URI, options?: IEditorOptions, group?: IEditorGroup): TPromise<IEditor>;
switchSettings(target: ConfigurationTarget, resource: URI): TPromise<void>;
openGlobalKeybindingSettings(textual: boolean): TPromise<void>;
openDefaultKeybindingsFile(): TPromise<IEditor>;
openRawDefaultSettings(): Thenable<IEditor>;
openSettings(jsonEditor?: boolean): Thenable<IEditor>;
openGlobalSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Thenable<IEditor>;
openWorkspaceSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Thenable<IEditor>;
openFolderSettings(folder: URI, jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Thenable<IEditor>;
switchSettings(target: ConfigurationTarget, resource: URI, jsonEditor?: boolean): Thenable<void>;
openGlobalKeybindingSettings(textual: boolean): Thenable<void>;
openDefaultKeybindingsFile(): Thenable<IEditor>;
configureSettingsForLanguage(language: string): void;
}

View File

@@ -4,8 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { OS } from 'vs/base/common/platform';
import URI from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base';
import { URI } from 'vs/base/common/uri';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import * as nls from 'vs/nls';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -13,8 +12,8 @@ import { EditorInput, SideBySideEditorInput, Verbosity } from 'vs/workbench/comm
import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
import { IHashService } from 'vs/workbench/services/hash/common/hashService';
import { KeybindingsEditorModel } from 'vs/workbench/services/preferences/common/keybindingsEditorModel';
import { IPreferencesService } from './preferences';
import { DefaultSettingsEditorModel } from './preferencesModels';
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
import { Settings2EditorModel } from 'vs/workbench/services/preferences/common/preferencesModels';
export class PreferencesEditorInput extends SideBySideEditorInput {
public static readonly ID: string = 'workbench.editorinputs.preferencesEditorInput';
@@ -70,8 +69,8 @@ export class KeybindingsEditorInput extends EditorInput {
return nls.localize('keybindingsInputName', "Keyboard Shortcuts");
}
resolve(): TPromise<KeybindingsEditorModel> {
return TPromise.as(this.keybindingsModel);
resolve(): Thenable<KeybindingsEditorModel> {
return Promise.resolve(this.keybindingsModel);
}
matches(otherInput: any): boolean {
@@ -82,11 +81,22 @@ export class KeybindingsEditorInput extends EditorInput {
export class SettingsEditor2Input extends EditorInput {
public static readonly ID: string = 'workbench.input.settings2';
private readonly _settingsModel: Settings2EditorModel;
private resource: URI = URI.from({
scheme: 'vscode-settings',
path: `settingseditor`
});
constructor(
@IPreferencesService private preferencesService: IPreferencesService
@IPreferencesService _preferencesService: IPreferencesService,
) {
super();
this._settingsModel = _preferencesService.createSettings2EditorModel();
}
matches(otherInput: any): boolean {
return otherInput instanceof SettingsEditor2Input;
}
getTypeId(): string {
@@ -94,14 +104,14 @@ export class SettingsEditor2Input extends EditorInput {
}
getName(): string {
return nls.localize('settingsEditor2InputName', "Settings (Preview)");
return nls.localize('settingsEditor2InputName', "Settings");
}
resolve(): TPromise<DefaultSettingsEditorModel> {
return <TPromise<DefaultSettingsEditorModel>>this.preferencesService.createPreferencesEditorModel(URI.parse('vscode://defaultsettings/0/settings.json'));
resolve(): Thenable<Settings2EditorModel> {
return Promise.resolve(this._settingsModel);
}
matches(otherInput: any): boolean {
return otherInput instanceof SettingsEditor2Input;
public getResource(): URI {
return this.resource;
}
}

View File

@@ -3,20 +3,20 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { flatten, tail } from 'vs/base/common/arrays';
import { flatten, tail, find } from 'vs/base/common/arrays';
import { IStringDictionary } from 'vs/base/common/collections';
import { Emitter, Event } from 'vs/base/common/event';
import { JSONVisitor, visit } from 'vs/base/common/json';
import { Disposable, IReference } from 'vs/base/common/lifecycle';
import * as map from 'vs/base/common/map';
import { assign } from 'vs/base/common/objects';
import URI from 'vs/base/common/uri';
import { URI } from 'vs/base/common/uri';
import { IRange, Range } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
import { IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/model';
import { ITextEditorModel } from 'vs/editor/common/services/resolverService';
import * as nls from 'vs/nls';
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ConfigurationScope, Extensions, IConfigurationNode, IConfigurationPropertySchema, IConfigurationRegistry, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { Registry } from 'vs/platform/registry/common/platform';
@@ -204,11 +204,52 @@ export class SettingsEditorModel extends AbstractSettingsModel implements ISetti
}
}
export class Settings2EditorModel extends AbstractSettingsModel implements ISettingsEditorModel {
private readonly _onDidChangeGroups: Emitter<void> = this._register(new Emitter<void>());
readonly onDidChangeGroups: Event<void> = this._onDidChangeGroups.event;
private dirty = false;
constructor(
private _defaultSettings: DefaultSettings,
@IConfigurationService configurationService: IConfigurationService,
) {
super();
configurationService.onDidChangeConfiguration(e => {
if (e.source === ConfigurationTarget.DEFAULT) {
this.dirty = true;
this._onDidChangeGroups.fire();
}
});
}
protected get filterGroups(): ISettingsGroup[] {
// Don't filter "commonly used"
return this.settingsGroups.slice(1);
}
public get settingsGroups(): ISettingsGroup[] {
const groups = this._defaultSettings.getSettingsGroups(this.dirty);
this.dirty = false;
return groups;
}
public findValueMatches(filter: string, setting: ISetting): IRange[] {
// TODO @roblou
return [];
}
protected update(): IFilterResult {
throw new Error('Not supported');
}
}
function parse(model: ITextModel, isSettingsProperty: (currentProperty: string, previousParents: string[]) => boolean): ISettingsGroup[] {
const settings: ISetting[] = [];
let overrideSetting: ISetting = null;
let overrideSetting: ISetting | null = null;
let currentProperty: string = null;
let currentProperty: string | null = null;
let currentParent: any = [];
let previousParents: any[] = [];
let settingsPropertyIndex: number = -1;
@@ -267,6 +308,7 @@ function parse(model: ITextModel, isSettingsProperty: (currentProperty: string,
let settingStartPosition = model.getPositionAt(offset);
const setting: ISetting = {
description: [],
descriptionIsMarkdown: false,
key: name,
keyRange: {
startLineNumber: settingStartPosition.lineNumber,
@@ -409,45 +451,53 @@ export class DefaultSettings extends Disposable {
super();
}
get content(): string {
if (!this._content) {
this.parse();
getContent(forceUpdate = false): string {
if (!this._content || forceUpdate) {
this._content = this.toContent(true, this.getSettingsGroups(forceUpdate));
}
return this._content;
}
get settingsGroups(): ISettingsGroup[] {
if (!this._allSettingsGroups) {
this.parse();
getSettingsGroups(forceUpdate = false): ISettingsGroup[] {
if (!this._allSettingsGroups || forceUpdate) {
this._allSettingsGroups = this.parse();
}
return this._allSettingsGroups;
}
parse(): string {
private parse(): ISettingsGroup[] {
const settingsGroups = this.getRegisteredGroups();
this.initAllSettingsMap(settingsGroups);
const mostCommonlyUsed = this.getMostCommonlyUsedSettings(settingsGroups);
this._allSettingsGroups = [mostCommonlyUsed, ...settingsGroups];
this._content = this.toContent(true, this._allSettingsGroups);
return this._content;
return [mostCommonlyUsed, ...settingsGroups];
}
get raw(): string {
if (!DefaultSettings._RAW) {
DefaultSettings._RAW = this.toContent(false, this.getRegisteredGroups());
}
return DefaultSettings._RAW;
}
getSettingByName(name: string): ISetting {
return this._settingsByName && this._settingsByName.get(name);
return DefaultSettings._RAW;
}
private getRegisteredGroups(): ISettingsGroup[] {
const configurations = Registry.as<IConfigurationRegistry>(Extensions.Configuration).getConfigurations().slice();
return this.removeEmptySettingsGroups(configurations.sort(this.compareConfigurationNodes)
const groups = this.removeEmptySettingsGroups(configurations.sort(this.compareConfigurationNodes)
.reduce((result, config, index, array) => this.parseConfig(config, result, array), []));
return this.sortGroups(groups);
}
private sortGroups(groups: ISettingsGroup[]): ISettingsGroup[] {
groups.forEach(group => {
group.sections.forEach(section => {
section.settings.sort((a, b) => a.key.localeCompare(b.key));
});
});
return groups;
}
private initAllSettingsMap(allSettingsGroups: ISettingsGroup[]): void {
@@ -472,6 +522,7 @@ export class DefaultSettings extends Disposable {
range: null,
valueRange: null,
overrides: [],
scope: ConfigurationScope.RESOURCE,
type: setting.type,
enum: setting.enum,
enumDescriptions: setting.enumDescriptions
@@ -497,14 +548,14 @@ export class DefaultSettings extends Disposable {
seenSettings = seenSettings ? seenSettings : {};
let title = config.title;
if (!title) {
const configWithTitleAndSameId = configurations.filter(c => c.id === config.id && c.title)[0];
const configWithTitleAndSameId = find(configurations, c => (c.id === config.id) && c.title);
if (configWithTitleAndSameId) {
title = configWithTitleAndSameId.title;
}
}
if (title) {
if (!settingsGroup) {
settingsGroup = result.filter(g => g.title === title)[0];
settingsGroup = find(result, g => g.title === title);
if (!settingsGroup) {
settingsGroup = { sections: [{ settings: [] }], id: config.id, title: title, titleRange: null, range: null, contributedByExtension: !!config.contributedByExtension };
result.push(settingsGroup);
@@ -526,7 +577,6 @@ export class DefaultSettings extends Disposable {
}
}
if (configurationSettings.length) {
configurationSettings.sort((a, b) => a.key.localeCompare(b.key));
settingsGroup.sections[settingsGroup.sections.length - 1].settings = configurationSettings;
}
}
@@ -537,7 +587,7 @@ export class DefaultSettings extends Disposable {
}
private removeEmptySettingsGroups(settingsGroups: ISettingsGroup[]): ISettingsGroup[] {
const result = [];
const result: ISettingsGroup[] = [];
for (const settingsGroup of settingsGroups) {
settingsGroup.sections = settingsGroup.sections.filter(section => section.settings.length > 0);
if (settingsGroup.sections.length) {
@@ -551,23 +601,28 @@ export class DefaultSettings extends Disposable {
let result: ISetting[] = [];
for (let key in settingsObject) {
const prop = settingsObject[key];
if (!prop.deprecationMessage && this.matchesScope(prop)) {
if (this.matchesScope(prop)) {
const value = prop.default;
const description = (prop.description || '').split('\n');
const description = (prop.description || prop.markdownDescription || '').split('\n');
const overrides = OVERRIDE_PROPERTY_PATTERN.test(key) ? this.parseOverrideSettings(prop.default) : [];
result.push({
key,
value,
description,
descriptionIsMarkdown: !prop.description,
range: null,
keyRange: null,
valueRange: null,
descriptionRanges: [],
overrides,
scope: prop.scope,
type: prop.type,
enum: prop.enum,
enumDescriptions: prop.enumDescriptions,
tags: prop.tags
enumDescriptions: prop.enumDescriptions || prop.markdownEnumDescriptions,
enumDescriptionsAreMarkdown: !prop.enumDescriptions,
tags: prop.tags,
deprecationMessage: prop.deprecationMessage,
validator: createValidator(prop)
});
}
}
@@ -575,7 +630,17 @@ export class DefaultSettings extends Disposable {
}
private parseOverrideSettings(overrideSettings: any): ISetting[] {
return Object.keys(overrideSettings).map((key) => ({ key, value: overrideSettings[key], description: [], range: null, keyRange: null, valueRange: null, descriptionRanges: [], overrides: [] }));
return Object.keys(overrideSettings).map((key) => ({
key,
value: overrideSettings[key],
description: [],
descriptionIsMarkdown: false,
range: null,
keyRange: null,
valueRange: null,
descriptionRanges: [],
overrides: []
}));
}
private matchesScope(property: IConfigurationNode): boolean {
@@ -648,7 +713,7 @@ export class DefaultSettingsEditorModel extends AbstractSettingsModel implements
}
public get settingsGroups(): ISettingsGroup[] {
return this.defaultSettings.settingsGroups;
return this.defaultSettings.getSettingsGroups();
}
protected get filterGroups(): ISettingsGroup[] {
@@ -657,6 +722,10 @@ export class DefaultSettingsEditorModel extends AbstractSettingsModel implements
}
protected update(): IFilterResult {
if (this._model.isDisposed()) {
return null;
}
// Grab current result groups, only render non-empty groups
const resultGroups = map
.values(this._currentResultGroups)
@@ -752,8 +821,9 @@ export class DefaultSettingsEditorModel extends AbstractSettingsModel implements
}
private copySetting(setting: ISetting): ISetting {
return <ISetting>{
return {
description: setting.description,
scope: setting.scope,
type: setting.type,
enum: setting.enum,
enumDescriptions: setting.enumDescriptions,
@@ -762,7 +832,12 @@ export class DefaultSettingsEditorModel extends AbstractSettingsModel implements
range: setting.range,
overrides: [],
overrideOf: setting.overrideOf,
tags: setting.tags
tags: setting.tags,
deprecationMessage: setting.deprecationMessage,
keyRange: undefined,
valueRange: undefined,
descriptionIsMarkdown: undefined,
descriptionRanges: undefined
};
}
@@ -839,7 +914,7 @@ class SettingsContentBuilder {
private _pushGroup(group: ISettingsGroup): ISetting {
const indent = ' ';
let lastSetting: ISetting = null;
let lastSetting: ISetting | null = null;
let groupStart = this.lineCountWithOffset + 1;
for (const section of group.sections) {
if (section.title) {
@@ -869,16 +944,16 @@ class SettingsContentBuilder {
this.pushSettingDescription(setting, indent);
let preValueConent = indent;
let preValueContent = indent;
const keyString = JSON.stringify(setting.key);
preValueConent += keyString;
setting.keyRange = { startLineNumber: this.lineCountWithOffset + 1, startColumn: preValueConent.indexOf(setting.key) + 1, endLineNumber: this.lineCountWithOffset + 1, endColumn: setting.key.length };
preValueContent += keyString;
setting.keyRange = { startLineNumber: this.lineCountWithOffset + 1, startColumn: preValueContent.indexOf(setting.key) + 1, endLineNumber: this.lineCountWithOffset + 1, endColumn: setting.key.length };
preValueConent += ': ';
preValueContent += ': ';
const valueStart = this.lineCountWithOffset + 1;
this.pushValue(setting, preValueConent, indent);
this.pushValue(setting, preValueContent, indent);
setting.valueRange = { startLineNumber: valueStart, startColumn: preValueConent.length + 1, endLineNumber: this.lineCountWithOffset, endColumn: this.lastLine.length + 1 };
setting.valueRange = { startLineNumber: valueStart, startColumn: preValueContent.length + 1, endLineNumber: this.lineCountWithOffset, endColumn: this.lastLine.length + 1 };
this._contentByLines[this._contentByLines.length - 1] += ',';
this._contentByLines.push('');
setting.range = { startLineNumber: settingStart, startColumn: 1, endLineNumber: this.lineCountWithOffset, endColumn: this.lastLine.length };
@@ -889,8 +964,7 @@ class SettingsContentBuilder {
setting.descriptionRanges = [];
const descriptionPreValue = indent + '// ';
for (let line of setting.description) {
// Remove setting link tag
for (let line of (setting.deprecationMessage ? [setting.deprecationMessage, ...setting.description] : setting.description)) {
line = fixSettingLink(line);
this._contentByLines.push(descriptionPreValue + line);
@@ -899,7 +973,7 @@ class SettingsContentBuilder {
if (setting.enumDescriptions && setting.enumDescriptions.some(desc => !!desc)) {
setting.enumDescriptions.forEach((desc, i) => {
const displayEnum = escapeInvisibleChars(setting.enum[i]);
const displayEnum = escapeInvisibleChars(String(setting.enum[i]));
const line = desc ?
`${displayEnum}: ${fixSettingLink(desc)}` :
displayEnum;
@@ -943,6 +1017,112 @@ class SettingsContentBuilder {
}
}
export function createValidator(prop: IConfigurationPropertySchema): ((value: any) => string) | null {
return value => {
let exclusiveMax: number | undefined;
let exclusiveMin: number | undefined;
if (typeof prop.exclusiveMaximum === 'boolean') {
exclusiveMax = prop.exclusiveMaximum ? prop.maximum : undefined;
} else {
exclusiveMax = prop.exclusiveMaximum;
}
if (typeof prop.exclusiveMinimum === 'boolean') {
exclusiveMin = prop.exclusiveMinimum ? prop.minimum : undefined;
} else {
exclusiveMin = prop.exclusiveMinimum;
}
let patternRegex: RegExp | undefined;
if (typeof prop.pattern === 'string') {
patternRegex = new RegExp(prop.pattern);
}
const type = Array.isArray(prop.type) ? prop.type : [prop.type];
const canBeType = (t: string) => type.indexOf(t) > -1;
const isNullable = canBeType('null');
const isNumeric = (canBeType('number') || canBeType('integer')) && (type.length === 1 || type.length === 2 && isNullable);
const isIntegral = (canBeType('integer')) && (type.length === 1 || type.length === 2 && isNullable);
type Validator<T> = { enabled: boolean, isValid: (value: T) => boolean; message: string };
let numericValidations: Validator<number>[] = isNumeric ? [
{
enabled: exclusiveMax !== undefined && (prop.maximum === undefined || exclusiveMax <= prop.maximum),
isValid: (value => value < exclusiveMax),
message: nls.localize('validations.exclusiveMax', "Value must be strictly less than {0}.", exclusiveMax)
},
{
enabled: exclusiveMin !== undefined && (prop.minimum === undefined || exclusiveMin >= prop.minimum),
isValid: (value => value > exclusiveMin),
message: nls.localize('validations.exclusiveMin', "Value must be strictly greater than {0}.", exclusiveMin)
},
{
enabled: prop.maximum !== undefined && (exclusiveMax === undefined || exclusiveMax > prop.maximum),
isValid: (value => value <= prop.maximum),
message: nls.localize('validations.max', "Value must be less than or equal to {0}.", prop.maximum)
},
{
enabled: prop.minimum !== undefined && (exclusiveMin === undefined || exclusiveMin < prop.minimum),
isValid: (value => value >= prop.minimum),
message: nls.localize('validations.min', "Value must be greater than or equal to {0}.", prop.minimum)
},
{
enabled: prop.multipleOf !== undefined,
isValid: (value => value % prop.multipleOf === 0),
message: nls.localize('validations.multipleOf', "Value must be a multiple of {0}.", prop.multipleOf)
},
{
enabled: isIntegral,
isValid: (value => value % 1 === 0),
message: nls.localize('validations.expectedInteger', "Value must be an integer.")
},
].filter(validation => validation.enabled) : [];
let stringValidations: Validator<string>[] = [
{
enabled: prop.maxLength !== undefined,
isValid: (value => value.length <= prop.maxLength),
message: nls.localize('validations.maxLength', "Value must be {0} or fewer characters long.", prop.maxLength)
},
{
enabled: prop.minLength !== undefined,
isValid: (value => value.length >= prop.minLength),
message: nls.localize('validations.minLength', "Value must be {0} or more characters long.", prop.minLength)
},
{
enabled: patternRegex !== undefined,
isValid: (value => patternRegex.test(value)),
message: prop.patternErrorMessage || nls.localize('validations.regex', "Value must match regex `{0}`.", prop.pattern)
},
].filter(validation => validation.enabled);
if (prop.type === 'string' && stringValidations.length === 0) { return null; }
if (isNullable && value === '') { return ''; }
let errors: string[] = [];
if (isNumeric) {
if (value === '' || isNaN(+value)) {
errors.push(nls.localize('validations.expectedNumeric', "Value must be a number."));
} else {
errors.push(...numericValidations.filter(validator => !validator.isValid(+value)).map(validator => validator.message));
}
}
if (prop.type === 'string') {
errors.push(...stringValidations.filter(validator => !validator.isValid('' + value)).map(validator => validator.message));
}
if (errors.length) {
return prop.errorMessage ? [prop.errorMessage, ...errors].join(' ') : errors.join(' ');
}
return '';
};
}
function escapeInvisibleChars(enumValue: string): string {
return enumValue && enumValue
.replace(/\n/g, '\\n')
@@ -950,7 +1130,7 @@ function escapeInvisibleChars(enumValue: string): string {
}
export function defaultKeybindingsContents(keybindingService: IKeybindingService): string {
const defaultsHeader = '// ' + nls.localize('defaultKeybindingsHeader', "Overwrite key bindings by placing them into your key bindings file.");
const defaultsHeader = '// ' + nls.localize('defaultKeybindingsHeader', "Override key bindings by placing them into your key bindings file.");
return defaultsHeader + '\n' + keybindingService.getDefaultKeybindingsContent();
}