Path: blob/main/src/vs/workbench/contrib/codeEditor/common/languageConfigurationExtensionPoint.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as nls from '../../../../nls.js';6import { ParseError, parse, getNodeType } from '../../../../base/common/json.js';7import { IJSONSchema } from '../../../../base/common/jsonSchema.js';8import * as types from '../../../../base/common/types.js';9import { URI } from '../../../../base/common/uri.js';10import { CharacterPair, CommentRule, EnterAction, ExplicitLanguageConfiguration, FoldingMarkers, FoldingRules, IAutoClosingPair, IAutoClosingPairConditional, IndentAction, IndentationRule, OnEnterRule } from '../../../../editor/common/languages/languageConfiguration.js';11import { ILanguageConfigurationService } from '../../../../editor/common/languages/languageConfigurationRegistry.js';12import { ILanguageService } from '../../../../editor/common/languages/language.js';13import { Extensions, IJSONContributionRegistry } from '../../../../platform/jsonschemas/common/jsonContributionRegistry.js';14import { Registry } from '../../../../platform/registry/common/platform.js';15import { IExtensionService } from '../../../services/extensions/common/extensions.js';16import { getParseErrorMessage } from '../../../../base/common/jsonErrorMessages.js';17import { IExtensionResourceLoaderService } from '../../../../platform/extensionResourceLoader/common/extensionResourceLoader.js';18import { hash } from '../../../../base/common/hash.js';19import { Disposable } from '../../../../base/common/lifecycle.js';2021interface IRegExp {22pattern: string;23flags?: string;24}2526interface IIndentationRules {27decreaseIndentPattern: string | IRegExp;28increaseIndentPattern: string | IRegExp;29indentNextLinePattern?: string | IRegExp;30unIndentedLinePattern?: string | IRegExp;31}3233interface IEnterAction {34indent: 'none' | 'indent' | 'indentOutdent' | 'outdent';35appendText?: string;36removeText?: number;37}3839interface IOnEnterRule {40beforeText: string | IRegExp;41afterText?: string | IRegExp;42previousLineText?: string | IRegExp;43action: IEnterAction;44}4546/**47* Serialized form of a language configuration48*/49export interface ILanguageConfiguration {50comments?: CommentRule;51brackets?: CharacterPair[];52autoClosingPairs?: Array<CharacterPair | IAutoClosingPairConditional>;53surroundingPairs?: Array<CharacterPair | IAutoClosingPair>;54colorizedBracketPairs?: Array<CharacterPair>;55wordPattern?: string | IRegExp;56indentationRules?: IIndentationRules;57folding?: {58offSide?: boolean;59markers?: {60start?: string | IRegExp;61end?: string | IRegExp;62};63};64autoCloseBefore?: string;65onEnterRules?: IOnEnterRule[];66}6768function isStringArr(something: string[] | null): something is string[] {69if (!Array.isArray(something)) {70return false;71}72for (let i = 0, len = something.length; i < len; i++) {73if (typeof something[i] !== 'string') {74return false;75}76}77return true;7879}8081function isCharacterPair(something: CharacterPair | null): boolean {82return (83isStringArr(something)84&& something.length === 285);86}8788export class LanguageConfigurationFileHandler extends Disposable {8990/**91* A map from language id to a hash computed from the config files locations.92*/93private readonly _done = new Map<string, number>();9495constructor(96@ILanguageService private readonly _languageService: ILanguageService,97@IExtensionResourceLoaderService private readonly _extensionResourceLoaderService: IExtensionResourceLoaderService,98@IExtensionService private readonly _extensionService: IExtensionService,99@ILanguageConfigurationService private readonly _languageConfigurationService: ILanguageConfigurationService,100) {101super();102103this._register(this._languageService.onDidRequestBasicLanguageFeatures(async (languageIdentifier) => {104// Modes can be instantiated before the extension points have finished registering105this._extensionService.whenInstalledExtensionsRegistered().then(() => {106this._loadConfigurationsForMode(languageIdentifier);107});108}));109this._register(this._languageService.onDidChange(() => {110// reload language configurations as necessary111for (const [languageId] of this._done) {112this._loadConfigurationsForMode(languageId);113}114}));115}116117private async _loadConfigurationsForMode(languageId: string): Promise<void> {118const configurationFiles = this._languageService.getConfigurationFiles(languageId);119const configurationHash = hash(configurationFiles.map(uri => uri.toString()));120121if (this._done.get(languageId) === configurationHash) {122return;123}124this._done.set(languageId, configurationHash);125126const configs = await Promise.all(configurationFiles.map(configFile => this._readConfigFile(configFile)));127for (const config of configs) {128this._handleConfig(languageId, config);129}130}131132private async _readConfigFile(configFileLocation: URI): Promise<ILanguageConfiguration> {133try {134const contents = await this._extensionResourceLoaderService.readExtensionResource(configFileLocation);135const errors: ParseError[] = [];136let configuration = <ILanguageConfiguration>parse(contents, errors);137if (errors.length) {138console.error(nls.localize('parseErrors', "Errors parsing {0}: {1}", configFileLocation.toString(), errors.map(e => (`[${e.offset}, ${e.length}] ${getParseErrorMessage(e.error)}`)).join('\n')));139}140if (getNodeType(configuration) !== 'object') {141console.error(nls.localize('formatError', "{0}: Invalid format, JSON object expected.", configFileLocation.toString()));142configuration = {};143}144return configuration;145} catch (err) {146console.error(err);147return {};148}149}150151private static _extractValidCommentRule(languageId: string, configuration: ILanguageConfiguration): CommentRule | undefined {152const source = configuration.comments;153if (typeof source === 'undefined') {154return undefined;155}156if (!types.isObject(source)) {157console.warn(`[${languageId}]: language configuration: expected \`comments\` to be an object.`);158return undefined;159}160161let result: CommentRule | undefined = undefined;162if (typeof source.lineComment !== 'undefined') {163if (typeof source.lineComment === 'string') {164result = result || {};165result.lineComment = source.lineComment;166} else if (types.isObject(source.lineComment)) {167const lineCommentObj = source.lineComment as any;168if (typeof lineCommentObj.comment === 'string') {169result = result || {};170result.lineComment = {171comment: lineCommentObj.comment,172noIndent: lineCommentObj.noIndent173};174} else {175console.warn(`[${languageId}]: language configuration: expected \`comments.lineComment.comment\` to be a string.`);176}177} else {178console.warn(`[${languageId}]: language configuration: expected \`comments.lineComment\` to be a string or an object with comment property.`);179}180}181if (typeof source.blockComment !== 'undefined') {182if (!isCharacterPair(source.blockComment)) {183console.warn(`[${languageId}]: language configuration: expected \`comments.blockComment\` to be an array of two strings.`);184} else {185result = result || {};186result.blockComment = source.blockComment;187}188}189return result;190}191192private static _extractValidBrackets(languageId: string, configuration: ILanguageConfiguration): CharacterPair[] | undefined {193const source = configuration.brackets;194if (typeof source === 'undefined') {195return undefined;196}197if (!Array.isArray(source)) {198console.warn(`[${languageId}]: language configuration: expected \`brackets\` to be an array.`);199return undefined;200}201202let result: CharacterPair[] | undefined = undefined;203for (let i = 0, len = source.length; i < len; i++) {204const pair = source[i];205if (!isCharacterPair(pair)) {206console.warn(`[${languageId}]: language configuration: expected \`brackets[${i}]\` to be an array of two strings.`);207continue;208}209210result = result || [];211result.push(pair);212}213return result;214}215216private static _extractValidAutoClosingPairs(languageId: string, configuration: ILanguageConfiguration): IAutoClosingPairConditional[] | undefined {217const source = configuration.autoClosingPairs;218if (typeof source === 'undefined') {219return undefined;220}221if (!Array.isArray(source)) {222console.warn(`[${languageId}]: language configuration: expected \`autoClosingPairs\` to be an array.`);223return undefined;224}225226let result: IAutoClosingPairConditional[] | undefined = undefined;227for (let i = 0, len = source.length; i < len; i++) {228const pair = source[i];229if (Array.isArray(pair)) {230if (!isCharacterPair(pair)) {231console.warn(`[${languageId}]: language configuration: expected \`autoClosingPairs[${i}]\` to be an array of two strings or an object.`);232continue;233}234result = result || [];235result.push({ open: pair[0], close: pair[1] });236} else {237if (!types.isObject(pair)) {238console.warn(`[${languageId}]: language configuration: expected \`autoClosingPairs[${i}]\` to be an array of two strings or an object.`);239continue;240}241if (typeof pair.open !== 'string') {242console.warn(`[${languageId}]: language configuration: expected \`autoClosingPairs[${i}].open\` to be a string.`);243continue;244}245if (typeof pair.close !== 'string') {246console.warn(`[${languageId}]: language configuration: expected \`autoClosingPairs[${i}].close\` to be a string.`);247continue;248}249if (typeof pair.notIn !== 'undefined') {250if (!isStringArr(pair.notIn)) {251console.warn(`[${languageId}]: language configuration: expected \`autoClosingPairs[${i}].notIn\` to be a string array.`);252continue;253}254}255result = result || [];256result.push({ open: pair.open, close: pair.close, notIn: pair.notIn });257}258}259return result;260}261262private static _extractValidSurroundingPairs(languageId: string, configuration: ILanguageConfiguration): IAutoClosingPair[] | undefined {263const source = configuration.surroundingPairs;264if (typeof source === 'undefined') {265return undefined;266}267if (!Array.isArray(source)) {268console.warn(`[${languageId}]: language configuration: expected \`surroundingPairs\` to be an array.`);269return undefined;270}271272let result: IAutoClosingPair[] | undefined = undefined;273for (let i = 0, len = source.length; i < len; i++) {274const pair = source[i];275if (Array.isArray(pair)) {276if (!isCharacterPair(pair)) {277console.warn(`[${languageId}]: language configuration: expected \`surroundingPairs[${i}]\` to be an array of two strings or an object.`);278continue;279}280result = result || [];281result.push({ open: pair[0], close: pair[1] });282} else {283if (!types.isObject(pair)) {284console.warn(`[${languageId}]: language configuration: expected \`surroundingPairs[${i}]\` to be an array of two strings or an object.`);285continue;286}287if (typeof pair.open !== 'string') {288console.warn(`[${languageId}]: language configuration: expected \`surroundingPairs[${i}].open\` to be a string.`);289continue;290}291if (typeof pair.close !== 'string') {292console.warn(`[${languageId}]: language configuration: expected \`surroundingPairs[${i}].close\` to be a string.`);293continue;294}295result = result || [];296result.push({ open: pair.open, close: pair.close });297}298}299return result;300}301302private static _extractValidColorizedBracketPairs(languageId: string, configuration: ILanguageConfiguration): CharacterPair[] | undefined {303const source = configuration.colorizedBracketPairs;304if (typeof source === 'undefined') {305return undefined;306}307if (!Array.isArray(source)) {308console.warn(`[${languageId}]: language configuration: expected \`colorizedBracketPairs\` to be an array.`);309return undefined;310}311312const result: CharacterPair[] = [];313for (let i = 0, len = source.length; i < len; i++) {314const pair = source[i];315if (!isCharacterPair(pair)) {316console.warn(`[${languageId}]: language configuration: expected \`colorizedBracketPairs[${i}]\` to be an array of two strings.`);317continue;318}319result.push([pair[0], pair[1]]);320321}322return result;323}324325private static _extractValidOnEnterRules(languageId: string, configuration: ILanguageConfiguration): OnEnterRule[] | undefined {326const source = configuration.onEnterRules;327if (typeof source === 'undefined') {328return undefined;329}330if (!Array.isArray(source)) {331console.warn(`[${languageId}]: language configuration: expected \`onEnterRules\` to be an array.`);332return undefined;333}334335let result: OnEnterRule[] | undefined = undefined;336for (let i = 0, len = source.length; i < len; i++) {337const onEnterRule = source[i];338if (!types.isObject(onEnterRule)) {339console.warn(`[${languageId}]: language configuration: expected \`onEnterRules[${i}]\` to be an object.`);340continue;341}342if (!types.isObject(onEnterRule.action)) {343console.warn(`[${languageId}]: language configuration: expected \`onEnterRules[${i}].action\` to be an object.`);344continue;345}346let indentAction: IndentAction;347if (onEnterRule.action.indent === 'none') {348indentAction = IndentAction.None;349} else if (onEnterRule.action.indent === 'indent') {350indentAction = IndentAction.Indent;351} else if (onEnterRule.action.indent === 'indentOutdent') {352indentAction = IndentAction.IndentOutdent;353} else if (onEnterRule.action.indent === 'outdent') {354indentAction = IndentAction.Outdent;355} else {356console.warn(`[${languageId}]: language configuration: expected \`onEnterRules[${i}].action.indent\` to be 'none', 'indent', 'indentOutdent' or 'outdent'.`);357continue;358}359const action: EnterAction = { indentAction };360if (onEnterRule.action.appendText) {361if (typeof onEnterRule.action.appendText === 'string') {362action.appendText = onEnterRule.action.appendText;363} else {364console.warn(`[${languageId}]: language configuration: expected \`onEnterRules[${i}].action.appendText\` to be undefined or a string.`);365}366}367if (onEnterRule.action.removeText) {368if (typeof onEnterRule.action.removeText === 'number') {369action.removeText = onEnterRule.action.removeText;370} else {371console.warn(`[${languageId}]: language configuration: expected \`onEnterRules[${i}].action.removeText\` to be undefined or a number.`);372}373}374const beforeText = this._parseRegex(languageId, `onEnterRules[${i}].beforeText`, onEnterRule.beforeText);375if (!beforeText) {376continue;377}378const resultingOnEnterRule: OnEnterRule = { beforeText, action };379if (onEnterRule.afterText) {380const afterText = this._parseRegex(languageId, `onEnterRules[${i}].afterText`, onEnterRule.afterText);381if (afterText) {382resultingOnEnterRule.afterText = afterText;383}384}385if (onEnterRule.previousLineText) {386const previousLineText = this._parseRegex(languageId, `onEnterRules[${i}].previousLineText`, onEnterRule.previousLineText);387if (previousLineText) {388resultingOnEnterRule.previousLineText = previousLineText;389}390}391result = result || [];392result.push(resultingOnEnterRule);393}394395return result;396}397398public static extractValidConfig(languageId: string, configuration: ILanguageConfiguration): ExplicitLanguageConfiguration {399400const comments = this._extractValidCommentRule(languageId, configuration);401const brackets = this._extractValidBrackets(languageId, configuration);402const autoClosingPairs = this._extractValidAutoClosingPairs(languageId, configuration);403const surroundingPairs = this._extractValidSurroundingPairs(languageId, configuration);404const colorizedBracketPairs = this._extractValidColorizedBracketPairs(languageId, configuration);405const autoCloseBefore = (typeof configuration.autoCloseBefore === 'string' ? configuration.autoCloseBefore : undefined);406const wordPattern = (configuration.wordPattern ? this._parseRegex(languageId, `wordPattern`, configuration.wordPattern) : undefined);407const indentationRules = (configuration.indentationRules ? this._mapIndentationRules(languageId, configuration.indentationRules) : undefined);408let folding: FoldingRules | undefined = undefined;409if (configuration.folding) {410const rawMarkers = configuration.folding.markers;411const startMarker = (rawMarkers && rawMarkers.start ? this._parseRegex(languageId, `folding.markers.start`, rawMarkers.start) : undefined);412const endMarker = (rawMarkers && rawMarkers.end ? this._parseRegex(languageId, `folding.markers.end`, rawMarkers.end) : undefined);413const markers: FoldingMarkers | undefined = (startMarker && endMarker ? { start: startMarker, end: endMarker } : undefined);414folding = {415offSide: configuration.folding.offSide,416markers417};418}419const onEnterRules = this._extractValidOnEnterRules(languageId, configuration);420421const richEditConfig: ExplicitLanguageConfiguration = {422comments,423brackets,424wordPattern,425indentationRules,426onEnterRules,427autoClosingPairs,428surroundingPairs,429colorizedBracketPairs,430autoCloseBefore,431folding,432__electricCharacterSupport: undefined,433};434return richEditConfig;435}436437private _handleConfig(languageId: string, configuration: ILanguageConfiguration): void {438const richEditConfig = LanguageConfigurationFileHandler.extractValidConfig(languageId, configuration);439this._languageConfigurationService.register(languageId, richEditConfig, 50);440}441442private static _parseRegex(languageId: string, confPath: string, value: string | IRegExp): RegExp | undefined {443if (typeof value === 'string') {444try {445return new RegExp(value, '');446} catch (err) {447console.warn(`[${languageId}]: Invalid regular expression in \`${confPath}\`: `, err);448return undefined;449}450}451if (types.isObject(value)) {452if (typeof value.pattern !== 'string') {453console.warn(`[${languageId}]: language configuration: expected \`${confPath}.pattern\` to be a string.`);454return undefined;455}456if (typeof value.flags !== 'undefined' && typeof value.flags !== 'string') {457console.warn(`[${languageId}]: language configuration: expected \`${confPath}.flags\` to be a string.`);458return undefined;459}460try {461return new RegExp(value.pattern, value.flags);462} catch (err) {463console.warn(`[${languageId}]: Invalid regular expression in \`${confPath}\`: `, err);464return undefined;465}466}467console.warn(`[${languageId}]: language configuration: expected \`${confPath}\` to be a string or an object.`);468return undefined;469}470471private static _mapIndentationRules(languageId: string, indentationRules: IIndentationRules): IndentationRule | undefined {472const increaseIndentPattern = this._parseRegex(languageId, `indentationRules.increaseIndentPattern`, indentationRules.increaseIndentPattern);473if (!increaseIndentPattern) {474return undefined;475}476const decreaseIndentPattern = this._parseRegex(languageId, `indentationRules.decreaseIndentPattern`, indentationRules.decreaseIndentPattern);477if (!decreaseIndentPattern) {478return undefined;479}480481const result: IndentationRule = {482increaseIndentPattern: increaseIndentPattern,483decreaseIndentPattern: decreaseIndentPattern484};485486if (indentationRules.indentNextLinePattern) {487result.indentNextLinePattern = this._parseRegex(languageId, `indentationRules.indentNextLinePattern`, indentationRules.indentNextLinePattern);488}489if (indentationRules.unIndentedLinePattern) {490result.unIndentedLinePattern = this._parseRegex(languageId, `indentationRules.unIndentedLinePattern`, indentationRules.unIndentedLinePattern);491}492493return result;494}495}496497const schemaId = 'vscode://schemas/language-configuration';498const schema: IJSONSchema = {499allowComments: true,500allowTrailingCommas: true,501default: {502comments: {503blockComment: ['/*', '*/'],504lineComment: '//'505},506brackets: [['(', ')'], ['[', ']'], ['{', '}']],507autoClosingPairs: [['(', ')'], ['[', ']'], ['{', '}']],508surroundingPairs: [['(', ')'], ['[', ']'], ['{', '}']]509},510definitions: {511openBracket: {512type: 'string',513description: nls.localize('schema.openBracket', 'The opening bracket character or string sequence.')514},515closeBracket: {516type: 'string',517description: nls.localize('schema.closeBracket', 'The closing bracket character or string sequence.')518},519bracketPair: {520type: 'array',521items: [{522$ref: '#/definitions/openBracket'523}, {524$ref: '#/definitions/closeBracket'525}]526}527},528properties: {529comments: {530default: {531blockComment: ['/*', '*/'],532lineComment: { comment: '//', noIndent: false }533},534description: nls.localize('schema.comments', 'Defines the comment symbols'),535type: 'object',536properties: {537blockComment: {538type: 'array',539description: nls.localize('schema.blockComments', 'Defines how block comments are marked.'),540items: [{541type: 'string',542description: nls.localize('schema.blockComment.begin', 'The character sequence that starts a block comment.')543}, {544type: 'string',545description: nls.localize('schema.blockComment.end', 'The character sequence that ends a block comment.')546}]547},548lineComment: {549type: 'object',550description: nls.localize('schema.lineComment.object', 'Configuration for line comments.'),551properties: {552comment: {553type: 'string',554description: nls.localize('schema.lineComment.comment', 'The character sequence that starts a line comment.')555},556noIndent: {557type: 'boolean',558description: nls.localize('schema.lineComment.noIndent', 'Whether the comment token should not be indented and placed at the first column. Defaults to false.'),559default: false560}561},562required: ['comment'],563additionalProperties: false564}565}566},567brackets: {568default: [['(', ')'], ['[', ']'], ['{', '}']],569markdownDescription: nls.localize('schema.brackets', 'Defines the bracket symbols that increase or decrease the indentation. When bracket pair colorization is enabled and {0} is not defined, this also defines the bracket pairs that are colorized by their nesting level.', '\`colorizedBracketPairs\`'),570type: 'array',571items: {572$ref: '#/definitions/bracketPair'573}574},575colorizedBracketPairs: {576default: [['(', ')'], ['[', ']'], ['{', '}']],577markdownDescription: nls.localize('schema.colorizedBracketPairs', 'Defines the bracket pairs that are colorized by their nesting level if bracket pair colorization is enabled. Any brackets included here that are not included in {0} will be automatically included in {0}.', '\`brackets\`'),578type: 'array',579items: {580$ref: '#/definitions/bracketPair'581}582},583autoClosingPairs: {584default: [['(', ')'], ['[', ']'], ['{', '}']],585description: nls.localize('schema.autoClosingPairs', 'Defines the bracket pairs. When a opening bracket is entered, the closing bracket is inserted automatically.'),586type: 'array',587items: {588oneOf: [{589$ref: '#/definitions/bracketPair'590}, {591type: 'object',592properties: {593open: {594$ref: '#/definitions/openBracket'595},596close: {597$ref: '#/definitions/closeBracket'598},599notIn: {600type: 'array',601description: nls.localize('schema.autoClosingPairs.notIn', 'Defines a list of scopes where the auto pairs are disabled.'),602items: {603enum: ['string', 'comment']604}605}606}607}]608}609},610autoCloseBefore: {611default: ';:.,=}])> \n\t',612description: nls.localize('schema.autoCloseBefore', 'Defines what characters must be after the cursor in order for bracket or quote autoclosing to occur when using the \'languageDefined\' autoclosing setting. This is typically the set of characters which can not start an expression.'),613type: 'string',614},615surroundingPairs: {616default: [['(', ')'], ['[', ']'], ['{', '}']],617description: nls.localize('schema.surroundingPairs', 'Defines the bracket pairs that can be used to surround a selected string.'),618type: 'array',619items: {620oneOf: [{621$ref: '#/definitions/bracketPair'622}, {623type: 'object',624properties: {625open: {626$ref: '#/definitions/openBracket'627},628close: {629$ref: '#/definitions/closeBracket'630}631}632}]633}634},635wordPattern: {636default: '',637description: nls.localize('schema.wordPattern', 'Defines what is considered to be a word in the programming language.'),638type: ['string', 'object'],639properties: {640pattern: {641type: 'string',642description: nls.localize('schema.wordPattern.pattern', 'The RegExp pattern used to match words.'),643default: '',644},645flags: {646type: 'string',647description: nls.localize('schema.wordPattern.flags', 'The RegExp flags used to match words.'),648default: 'g',649pattern: '^([gimuy]+)$',650patternErrorMessage: nls.localize('schema.wordPattern.flags.errorMessage', 'Must match the pattern `/^([gimuy]+)$/`.')651}652}653},654indentationRules: {655default: {656increaseIndentPattern: '',657decreaseIndentPattern: ''658},659description: nls.localize('schema.indentationRules', 'The language\'s indentation settings.'),660type: 'object',661properties: {662increaseIndentPattern: {663type: ['string', 'object'],664description: nls.localize('schema.indentationRules.increaseIndentPattern', 'If a line matches this pattern, then all the lines after it should be indented once (until another rule matches).'),665properties: {666pattern: {667type: 'string',668description: nls.localize('schema.indentationRules.increaseIndentPattern.pattern', 'The RegExp pattern for increaseIndentPattern.'),669default: '',670},671flags: {672type: 'string',673description: nls.localize('schema.indentationRules.increaseIndentPattern.flags', 'The RegExp flags for increaseIndentPattern.'),674default: '',675pattern: '^([gimuy]+)$',676patternErrorMessage: nls.localize('schema.indentationRules.increaseIndentPattern.errorMessage', 'Must match the pattern `/^([gimuy]+)$/`.')677}678}679},680decreaseIndentPattern: {681type: ['string', 'object'],682description: nls.localize('schema.indentationRules.decreaseIndentPattern', 'If a line matches this pattern, then all the lines after it should be unindented once (until another rule matches).'),683properties: {684pattern: {685type: 'string',686description: nls.localize('schema.indentationRules.decreaseIndentPattern.pattern', 'The RegExp pattern for decreaseIndentPattern.'),687default: '',688},689flags: {690type: 'string',691description: nls.localize('schema.indentationRules.decreaseIndentPattern.flags', 'The RegExp flags for decreaseIndentPattern.'),692default: '',693pattern: '^([gimuy]+)$',694patternErrorMessage: nls.localize('schema.indentationRules.decreaseIndentPattern.errorMessage', 'Must match the pattern `/^([gimuy]+)$/`.')695}696}697},698indentNextLinePattern: {699type: ['string', 'object'],700description: nls.localize('schema.indentationRules.indentNextLinePattern', 'If a line matches this pattern, then **only the next line** after it should be indented once.'),701properties: {702pattern: {703type: 'string',704description: nls.localize('schema.indentationRules.indentNextLinePattern.pattern', 'The RegExp pattern for indentNextLinePattern.'),705default: '',706},707flags: {708type: 'string',709description: nls.localize('schema.indentationRules.indentNextLinePattern.flags', 'The RegExp flags for indentNextLinePattern.'),710default: '',711pattern: '^([gimuy]+)$',712patternErrorMessage: nls.localize('schema.indentationRules.indentNextLinePattern.errorMessage', 'Must match the pattern `/^([gimuy]+)$/`.')713}714}715},716unIndentedLinePattern: {717type: ['string', 'object'],718description: nls.localize('schema.indentationRules.unIndentedLinePattern', 'If a line matches this pattern, then its indentation should not be changed and it should not be evaluated against the other rules.'),719properties: {720pattern: {721type: 'string',722description: nls.localize('schema.indentationRules.unIndentedLinePattern.pattern', 'The RegExp pattern for unIndentedLinePattern.'),723default: '',724},725flags: {726type: 'string',727description: nls.localize('schema.indentationRules.unIndentedLinePattern.flags', 'The RegExp flags for unIndentedLinePattern.'),728default: '',729pattern: '^([gimuy]+)$',730patternErrorMessage: nls.localize('schema.indentationRules.unIndentedLinePattern.errorMessage', 'Must match the pattern `/^([gimuy]+)$/`.')731}732}733}734}735},736folding: {737type: 'object',738description: nls.localize('schema.folding', 'The language\'s folding settings.'),739properties: {740offSide: {741type: 'boolean',742description: nls.localize('schema.folding.offSide', 'A language adheres to the off-side rule if blocks in that language are expressed by their indentation. If set, empty lines belong to the subsequent block.'),743},744markers: {745type: 'object',746description: nls.localize('schema.folding.markers', 'Language specific folding markers such as \'#region\' and \'#endregion\'. The start and end regexes will be tested against the contents of all lines and must be designed efficiently'),747properties: {748start: {749type: 'string',750description: nls.localize('schema.folding.markers.start', 'The RegExp pattern for the start marker. The regexp must start with \'^\'.')751},752end: {753type: 'string',754description: nls.localize('schema.folding.markers.end', 'The RegExp pattern for the end marker. The regexp must start with \'^\'.')755},756}757}758}759},760onEnterRules: {761type: 'array',762description: nls.localize('schema.onEnterRules', 'The language\'s rules to be evaluated when pressing Enter.'),763items: {764type: 'object',765description: nls.localize('schema.onEnterRules', 'The language\'s rules to be evaluated when pressing Enter.'),766required: ['beforeText', 'action'],767properties: {768beforeText: {769type: ['string', 'object'],770description: nls.localize('schema.onEnterRules.beforeText', 'This rule will only execute if the text before the cursor matches this regular expression.'),771properties: {772pattern: {773type: 'string',774description: nls.localize('schema.onEnterRules.beforeText.pattern', 'The RegExp pattern for beforeText.'),775default: '',776},777flags: {778type: 'string',779description: nls.localize('schema.onEnterRules.beforeText.flags', 'The RegExp flags for beforeText.'),780default: '',781pattern: '^([gimuy]+)$',782patternErrorMessage: nls.localize('schema.onEnterRules.beforeText.errorMessage', 'Must match the pattern `/^([gimuy]+)$/`.')783}784}785},786afterText: {787type: ['string', 'object'],788description: nls.localize('schema.onEnterRules.afterText', 'This rule will only execute if the text after the cursor matches this regular expression.'),789properties: {790pattern: {791type: 'string',792description: nls.localize('schema.onEnterRules.afterText.pattern', 'The RegExp pattern for afterText.'),793default: '',794},795flags: {796type: 'string',797description: nls.localize('schema.onEnterRules.afterText.flags', 'The RegExp flags for afterText.'),798default: '',799pattern: '^([gimuy]+)$',800patternErrorMessage: nls.localize('schema.onEnterRules.afterText.errorMessage', 'Must match the pattern `/^([gimuy]+)$/`.')801}802}803},804previousLineText: {805type: ['string', 'object'],806description: nls.localize('schema.onEnterRules.previousLineText', 'This rule will only execute if the text above the line matches this regular expression.'),807properties: {808pattern: {809type: 'string',810description: nls.localize('schema.onEnterRules.previousLineText.pattern', 'The RegExp pattern for previousLineText.'),811default: '',812},813flags: {814type: 'string',815description: nls.localize('schema.onEnterRules.previousLineText.flags', 'The RegExp flags for previousLineText.'),816default: '',817pattern: '^([gimuy]+)$',818patternErrorMessage: nls.localize('schema.onEnterRules.previousLineText.errorMessage', 'Must match the pattern `/^([gimuy]+)$/`.')819}820}821},822action: {823type: ['string', 'object'],824description: nls.localize('schema.onEnterRules.action', 'The action to execute.'),825required: ['indent'],826default: { 'indent': 'indent' },827properties: {828indent: {829type: 'string',830description: nls.localize('schema.onEnterRules.action.indent', "Describe what to do with the indentation"),831default: 'indent',832enum: ['none', 'indent', 'indentOutdent', 'outdent'],833markdownEnumDescriptions: [834nls.localize('schema.onEnterRules.action.indent.none', "Insert new line and copy the previous line's indentation."),835nls.localize('schema.onEnterRules.action.indent.indent', "Insert new line and indent once (relative to the previous line's indentation)."),836nls.localize('schema.onEnterRules.action.indent.indentOutdent', "Insert two new lines:\n - the first one indented which will hold the cursor\n - the second one at the same indentation level"),837nls.localize('schema.onEnterRules.action.indent.outdent', "Insert new line and outdent once (relative to the previous line's indentation).")838]839},840appendText: {841type: 'string',842description: nls.localize('schema.onEnterRules.action.appendText', 'Describes text to be appended after the new line and after the indentation.'),843default: '',844},845removeText: {846type: 'number',847description: nls.localize('schema.onEnterRules.action.removeText', 'Describes the number of characters to remove from the new line\'s indentation.'),848default: 0,849}850}851}852}853}854}855856}857};858const schemaRegistry = Registry.as<IJSONContributionRegistry>(Extensions.JSONContribution);859schemaRegistry.registerSchema(schemaId, schema);860861862