mirror of
https://github.com/microsoft/vscode.git
synced 2026-02-15 07:28:05 +00:00
Fixes #114348: Allow onEnterRules to be defined in the language configuration file
This commit is contained in:
2
src/vs/vscode.proposed.d.ts
vendored
2
src/vs/vscode.proposed.d.ts
vendored
@@ -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
|
||||
};
|
||||
|
||||
if (indentationRules.indentNextLinePattern) {
|
||||
result.indentNextLinePattern = this._parseRegex(indentationRules.indentNextLinePattern);
|
||||
}
|
||||
if (indentationRules.unIndentedLinePattern) {
|
||||
result.unIndentedLinePattern = this._parseRegex(indentationRules.unIndentedLinePattern);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
} catch (error) {
|
||||
// Malformed regexes are ignored
|
||||
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;
|
||||
}
|
||||
|
||||
return null;
|
||||
const result: IndentationRule = {
|
||||
increaseIndentPattern: increaseIndentPattern,
|
||||
decreaseIndentPattern: decreaseIndentPattern
|
||||
};
|
||||
|
||||
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 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user