Fixes #114348: Allow `onEnterRules` to be defined in the language configuration file

上级 390dac56
......@@ -884,7 +884,7 @@ declare module 'vscode' {
//#region https://github.com/microsoft/vscode/issues/58440
export interface OnEnterRule {
/**
* This rule will only execute if the text above the this line matches this regular expression.
* This rule will only execute if the text above the line matches this regular expression.
*/
oneLineAboveText?: RegExp;
}
......
......@@ -9,7 +9,7 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema';
import * as types from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
import { LanguageIdentifier } from 'vs/editor/common/modes';
import { CharacterPair, CommentRule, FoldingRules, IAutoClosingPair, IAutoClosingPairConditional, IndentationRule, LanguageConfiguration } from 'vs/editor/common/modes/languageConfiguration';
import { CharacterPair, CommentRule, EnterAction, FoldingRules, IAutoClosingPair, IAutoClosingPairConditional, IndentAction, IndentationRule, LanguageConfiguration, OnEnterRule } from 'vs/editor/common/modes/languageConfiguration';
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
import { IModeService } from 'vs/editor/common/services/modeService';
import { Extensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
......@@ -31,6 +31,19 @@ interface IIndentationRules {
unIndentedLinePattern?: string | IRegExp;
}
interface IEnterAction {
indent: 'none' | 'indent' | 'indentOutdent' | 'outdent';
appendText?: string;
removeText?: number;
}
interface IOnEnterRule {
beforeText: string | IRegExp;
afterText?: string | IRegExp;
oneLineAboveText?: string | IRegExp;
action: IEnterAction;
}
interface ILanguageConfiguration {
comments?: CommentRule;
brackets?: CharacterPair[];
......@@ -40,6 +53,7 @@ interface ILanguageConfiguration {
indentationRules?: IIndentationRules;
folding?: FoldingRules;
autoCloseBefore?: string;
onEnterRules?: IOnEnterRule[];
}
function isStringArr(something: string[] | null): something is string[] {
......@@ -93,7 +107,7 @@ export class LanguageConfigurationFileHandler {
}
this._done[languageIdentifier.id] = true;
let configurationFiles = this._modeService.getConfigurationFiles(languageIdentifier.language);
const configurationFiles = this._modeService.getConfigurationFiles(languageIdentifier.language);
configurationFiles.forEach((configFileLocation) => this._handleConfigFile(languageIdentifier, configFileLocation));
}
......@@ -254,18 +268,82 @@ export class LanguageConfigurationFileHandler {
return result;
}
// private _mapCharacterPairs(pairs: Array<CharacterPair | IAutoClosingPairConditional>): IAutoClosingPairConditional[] {
// return pairs.map(pair => {
// if (Array.isArray(pair)) {
// return { open: pair[0], close: pair[1] };
// }
// return <IAutoClosingPairConditional>pair;
// });
// }
private _extractValidOnEnterRules(languageIdentifier: LanguageIdentifier, configuration: ILanguageConfiguration): OnEnterRule[] | null {
const source = configuration.onEnterRules;
if (typeof source === 'undefined') {
return null;
}
if (!Array.isArray(source)) {
console.warn(`[${languageIdentifier.language}]: language configuration: expected \`onEnterRules\` to be an array.`);
return null;
}
let result: OnEnterRule[] | null = null;
for (let i = 0, len = source.length; i < len; i++) {
const onEnterRule = source[i];
if (!types.isObject(onEnterRule)) {
console.warn(`[${languageIdentifier.language}]: language configuration: expected \`onEnterRules[${i}]\` to be an object.`);
continue;
}
if (!types.isObject(onEnterRule.action)) {
console.warn(`[${languageIdentifier.language}]: language configuration: expected \`onEnterRules[${i}].action\` to be an object.`);
continue;
}
let indentAction: IndentAction;
if (onEnterRule.action.indent === 'none') {
indentAction = IndentAction.None;
} else if (onEnterRule.action.indent === 'indent') {
indentAction = IndentAction.Indent;
} else if (onEnterRule.action.indent === 'indentOutdent') {
indentAction = IndentAction.IndentOutdent;
} else if (onEnterRule.action.indent === 'outdent') {
indentAction = IndentAction.Outdent;
} else {
console.warn(`[${languageIdentifier.language}]: language configuration: expected \`onEnterRules[${i}].action.indent\` to be 'none', 'indent', 'indentOutdent' or 'outdent'.`);
continue;
}
const action: EnterAction = { indentAction };
if (onEnterRule.action.appendText) {
if (typeof onEnterRule.action.appendText === 'string') {
action.appendText = onEnterRule.action.appendText;
} else {
console.warn(`[${languageIdentifier.language}]: language configuration: expected \`onEnterRules[${i}].action.appendText\` to be undefined or a string.`);
}
}
if (onEnterRule.action.removeText) {
if (typeof onEnterRule.action.removeText === 'number') {
action.removeText = onEnterRule.action.removeText;
} else {
console.warn(`[${languageIdentifier.language}]: language configuration: expected \`onEnterRules[${i}].action.removeText\` to be undefined or a number.`);
}
}
const beforeText = this._parseRegex(languageIdentifier, `onEnterRules[${i}].beforeText`, onEnterRule.beforeText);
if (!beforeText) {
continue;
}
const resultingOnEnterRule: OnEnterRule = { beforeText, action };
if (onEnterRule.afterText) {
const afterText = this._parseRegex(languageIdentifier, `onEnterRules[${i}].afterText`, onEnterRule.afterText);
if (afterText) {
resultingOnEnterRule.afterText = afterText;
}
}
if (onEnterRule.oneLineAboveText) {
const oneLineAboveText = this._parseRegex(languageIdentifier, `onEnterRules[${i}].oneLineAboveText`, onEnterRule.oneLineAboveText);
if (oneLineAboveText) {
resultingOnEnterRule.oneLineAboveText = oneLineAboveText;
}
}
result = result || [];
result.push(resultingOnEnterRule);
}
return result;
}
private _handleConfig(languageIdentifier: LanguageIdentifier, configuration: ILanguageConfiguration): void {
let richEditConfig: LanguageConfiguration = {};
const richEditConfig: LanguageConfiguration = {};
const comments = this._extractValidCommentRule(languageIdentifier, configuration);
if (comments) {
......@@ -293,25 +371,21 @@ export class LanguageConfigurationFileHandler {
}
if (configuration.wordPattern) {
try {
let wordPattern = this._parseRegex(configuration.wordPattern);
if (wordPattern) {
richEditConfig.wordPattern = wordPattern;
}
} catch (error) {
// Malformed regexes are ignored
const wordPattern = this._parseRegex(languageIdentifier, `wordPattern`, configuration.wordPattern);
if (wordPattern) {
richEditConfig.wordPattern = wordPattern;
}
}
if (configuration.indentationRules) {
let indentationRules = this._mapIndentationRules(configuration.indentationRules);
const indentationRules = this._mapIndentationRules(languageIdentifier, configuration.indentationRules);
if (indentationRules) {
richEditConfig.indentationRules = indentationRules;
}
}
if (configuration.folding) {
let markers = configuration.folding.markers;
const markers = configuration.folding.markers;
richEditConfig.folding = {
offSide: configuration.folding.offSide,
......@@ -319,44 +393,66 @@ export class LanguageConfigurationFileHandler {
};
}
const onEnterRules = this._extractValidOnEnterRules(languageIdentifier, configuration);
if (onEnterRules) {
richEditConfig.onEnterRules = onEnterRules;
}
LanguageConfigurationRegistry.register(languageIdentifier, richEditConfig);
}
private _parseRegex(value: string | IRegExp) {
private _parseRegex(languageIdentifier: LanguageIdentifier, confPath: string, value: string | IRegExp) {
if (typeof value === 'string') {
return new RegExp(value, '');
} else if (typeof value === 'object') {
return new RegExp(value.pattern, value.flags);
try {
return new RegExp(value, '');
} catch (err) {
console.warn(`[${languageIdentifier.language}]: Invalid regular expression in \`${confPath}\`: `, err);
return null;
}
}
if (types.isObject(value)) {
if (typeof value.pattern !== 'string') {
console.warn(`[${languageIdentifier.language}]: language configuration: expected \`${confPath}.pattern\` to be a string.`);
return null;
}
if (typeof value.flags !== 'undefined' && typeof value.flags !== 'string') {
console.warn(`[${languageIdentifier.language}]: language configuration: expected \`${confPath}.flags\` to be a string.`);
return null;
}
try {
return new RegExp(value.pattern, value.flags);
} catch (err) {
console.warn(`[${languageIdentifier.language}]: Invalid regular expression in \`${confPath}\`: `, err);
return null;
}
}
console.warn(`[${languageIdentifier.language}]: language configuration: expected \`${confPath}\` to be a string or an object.`);
return null;
}
private _mapIndentationRules(indentationRules: IIndentationRules): IndentationRule | null {
try {
let increaseIndentPattern = this._parseRegex(indentationRules.increaseIndentPattern);
let decreaseIndentPattern = this._parseRegex(indentationRules.decreaseIndentPattern);
if (increaseIndentPattern && decreaseIndentPattern) {
let result: IndentationRule = {
increaseIndentPattern: increaseIndentPattern,
decreaseIndentPattern: decreaseIndentPattern
};
private _mapIndentationRules(languageIdentifier: LanguageIdentifier, indentationRules: IIndentationRules): IndentationRule | null {
const increaseIndentPattern = this._parseRegex(languageIdentifier, `indentationRules.increaseIndentPattern`, indentationRules.increaseIndentPattern);
if (!increaseIndentPattern) {
return null;
}
const decreaseIndentPattern = this._parseRegex(languageIdentifier, `indentationRules.decreaseIndentPattern`, indentationRules.decreaseIndentPattern);
if (!decreaseIndentPattern) {
return null;
}
if (indentationRules.indentNextLinePattern) {
result.indentNextLinePattern = this._parseRegex(indentationRules.indentNextLinePattern);
}
if (indentationRules.unIndentedLinePattern) {
result.unIndentedLinePattern = this._parseRegex(indentationRules.unIndentedLinePattern);
}
const result: IndentationRule = {
increaseIndentPattern: increaseIndentPattern,
decreaseIndentPattern: decreaseIndentPattern
};
return result;
}
} catch (error) {
// Malformed regexes are ignored
if (indentationRules.indentNextLinePattern) {
result.indentNextLinePattern = this._parseRegex(languageIdentifier, `indentationRules.indentNextLinePattern`, indentationRules.indentNextLinePattern);
}
if (indentationRules.unIndentedLinePattern) {
result.unIndentedLinePattern = this._parseRegex(languageIdentifier, `indentationRules.unIndentedLinePattern`, indentationRules.unIndentedLinePattern);
}
return null;
return result;
}
}
......@@ -601,6 +697,100 @@ const schema: IJSONSchema = {
}
}
}
},
onEnterRules: {
type: 'array',
items: {
type: 'object',
description: nls.localize('schema.onEnterRules', 'The language\'s rules to be evaluated when pressing Enter.'),
required: ['beforeText', 'action'],
properties: {
beforeText: {
type: ['string', 'object'],
description: nls.localize('schema.onEnterRules.beforeText', 'This rule will only execute if the text before the cursor matches this regular expression.'),
properties: {
pattern: {
type: 'string',
description: nls.localize('schema.onEnterRules.beforeText.pattern', 'The RegExp pattern for beforeText.'),
default: '',
},
flags: {
type: 'string',
description: nls.localize('schema.onEnterRules.beforeText.flags', 'The RegExp flags for beforeText.'),
default: '',
pattern: '^([gimuy]+)$',
patternErrorMessage: nls.localize('schema.onEnterRules.beforeText.errorMessage', 'Must match the pattern `/^([gimuy]+)$/`.')
}
}
},
afterText: {
type: ['string', 'object'],
description: nls.localize('schema.onEnterRules.afterText', 'This rule will only execute if the text after the cursor matches this regular expression.'),
properties: {
pattern: {
type: 'string',
description: nls.localize('schema.onEnterRules.afterText.pattern', 'The RegExp pattern for afterText.'),
default: '',
},
flags: {
type: 'string',
description: nls.localize('schema.onEnterRules.afterText.flags', 'The RegExp flags for afterText.'),
default: '',
pattern: '^([gimuy]+)$',
patternErrorMessage: nls.localize('schema.onEnterRules.afterText.errorMessage', 'Must match the pattern `/^([gimuy]+)$/`.')
}
}
},
oneLineAboveText: {
type: ['string', 'object'],
description: nls.localize('schema.onEnterRules.oneLineAboveText', 'This rule will only execute if the text above the line matches this regular expression.'),
properties: {
pattern: {
type: 'string',
description: nls.localize('schema.onEnterRules.oneLineAboveText.pattern', 'The RegExp pattern for oneLineAboveText.'),
default: '',
},
flags: {
type: 'string',
description: nls.localize('schema.onEnterRules.oneLineAboveText.flags', 'The RegExp flags for oneLineAboveText.'),
default: '',
pattern: '^([gimuy]+)$',
patternErrorMessage: nls.localize('schema.onEnterRules.oneLineAboveText.errorMessage', 'Must match the pattern `/^([gimuy]+)$/`.')
}
}
},
action: {
type: ['string', 'object'],
description: nls.localize('schema.onEnterRules.action', 'The action to execute.'),
required: ['indent'],
default: { 'indent': 'indent' },
properties: {
indent: {
type: 'string',
description: nls.localize('schema.onEnterRules.action.indent', "Describe what to do with the indentation"),
default: 'indent',
enum: ['none', 'indent', 'indentOutdent', 'outdent'],
markdownEnumDescriptions: [
nls.localize('schema.onEnterRules.action.indent.none', "Insert new line and copy the previous line's indentation."),
nls.localize('schema.onEnterRules.action.indent.indent', "Insert new line and indent once (relative to the previous line's indentation)."),
nls.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"),
nls.localize('schema.onEnterRules.action.indent.outdent', "Insert new line and outdent once (relative to the previous line's indentation).")
]
},
appendText: {
type: 'string',
description: nls.localize('schema.onEnterRules.action.appendText', 'Describes text to be appended after the new line and after the indentation.'),
default: '',
},
removeText: {
type: 'number',
description: nls.localize('schema.onEnterRules.action.removeText', 'Describes the number of characters to remove from the new line\'s indentation.'),
default: 0,
}
}
}
}
}
}
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册