Path: blob/main/src/vs/editor/common/languages/languageConfiguration.ts
3294 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 { CharCode } from '../../../base/common/charCode.js';6import { StandardTokenType } from '../encodedTokenAttributes.js';7import { ScopedLineTokens } from './supports.js';89/**10* Configuration for line comments.11*/12export interface LineCommentConfig {13/**14* The line comment token, like `//`15*/16comment: string;17/**18* Whether the comment token should not be indented and placed at the first column.19* Defaults to false.20*/21noIndent?: boolean;22}2324/**25* Describes how comments for a language work.26*/27export interface CommentRule {28/**29* The line comment token, like `// this is a comment`.30* Can be a string or an object with comment and optional noIndent properties.31*/32lineComment?: string | LineCommentConfig | null;33/**34* The block comment character pair, like `/* block comment */`35*/36blockComment?: CharacterPair | null;37}3839/**40* The language configuration interface defines the contract between extensions and41* various editor features, like automatic bracket insertion, automatic indentation etc.42*/43export interface LanguageConfiguration {44/**45* The language's comment settings.46*/47comments?: CommentRule;48/**49* The language's brackets.50* This configuration implicitly affects pressing Enter around these brackets.51*/52brackets?: CharacterPair[];53/**54* The language's word definition.55* If the language supports Unicode identifiers (e.g. JavaScript), it is preferable56* to provide a word definition that uses exclusion of known separators.57* e.g.: A regex that matches anything except known separators (and dot is allowed to occur in a floating point number):58* /(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g59*/60wordPattern?: RegExp;61/**62* The language's indentation settings.63*/64indentationRules?: IndentationRule;65/**66* The language's rules to be evaluated when pressing Enter.67*/68onEnterRules?: OnEnterRule[];69/**70* The language's auto closing pairs. The 'close' character is automatically inserted with the71* 'open' character is typed. If not set, the configured brackets will be used.72*/73autoClosingPairs?: IAutoClosingPairConditional[];74/**75* The language's surrounding pairs. When the 'open' character is typed on a selection, the76* selected string is surrounded by the open and close characters. If not set, the autoclosing pairs77* settings will be used.78*/79surroundingPairs?: IAutoClosingPair[];80/**81* Defines a list of bracket pairs that are colorized depending on their nesting level.82* If not set, the configured brackets will be used.83*/84colorizedBracketPairs?: CharacterPair[];85/**86* Defines what characters must be after the cursor for bracket or quote autoclosing to occur when using the \'languageDefined\' autoclosing setting.87*88* This is typically the set of characters which can not start an expression, such as whitespace, closing brackets, non-unary operators, etc.89*/90autoCloseBefore?: string;9192/**93* The language's folding rules.94*/95folding?: FoldingRules;9697/**98* **Deprecated** Do not use.99*100* @deprecated Will be replaced by a better API soon.101*/102__electricCharacterSupport?: {103docComment?: IDocComment;104};105}106107/**108* @internal109*/110type OrUndefined<T> = { [P in keyof T]: T[P] | undefined };111112/**113* @internal114*/115export type ExplicitLanguageConfiguration = OrUndefined<Required<LanguageConfiguration>>;116117/**118* Describes indentation rules for a language.119*/120export interface IndentationRule {121/**122* If a line matches this pattern, then all the lines after it should be unindented once (until another rule matches).123*/124decreaseIndentPattern: RegExp;125/**126* If a line matches this pattern, then all the lines after it should be indented once (until another rule matches).127*/128increaseIndentPattern: RegExp;129/**130* If a line matches this pattern, then **only the next line** after it should be indented once.131*/132indentNextLinePattern?: RegExp | null;133/**134* If a line matches this pattern, then its indentation should not be changed and it should not be evaluated against the other rules.135*/136unIndentedLinePattern?: RegExp | null;137138}139140/**141* Describes language specific folding markers such as '#region' and '#endregion'.142* The start and end regexes will be tested against the contents of all lines and must be designed efficiently:143* - the regex should start with '^'144* - regexp flags (i, g) are ignored145*/146export interface FoldingMarkers {147start: RegExp;148end: RegExp;149}150151/**152* Describes folding rules for a language.153*/154export interface FoldingRules {155/**156* Used by the indentation based strategy to decide whether empty lines belong to the previous or the next block.157* A language adheres to the off-side rule if blocks in that language are expressed by their indentation.158* See [wikipedia](https://en.wikipedia.org/wiki/Off-side_rule) for more information.159* If not set, `false` is used and empty lines belong to the previous block.160*/161offSide?: boolean;162163/**164* Region markers used by the language.165*/166markers?: FoldingMarkers;167}168169/**170* Describes a rule to be evaluated when pressing Enter.171*/172export interface OnEnterRule {173/**174* This rule will only execute if the text before the cursor matches this regular expression.175*/176beforeText: RegExp;177/**178* This rule will only execute if the text after the cursor matches this regular expression.179*/180afterText?: RegExp;181/**182* This rule will only execute if the text above the this line matches this regular expression.183*/184previousLineText?: RegExp;185/**186* The action to execute.187*/188action: EnterAction;189}190191/**192* Definition of documentation comments (e.g. Javadoc/JSdoc)193*/194export interface IDocComment {195/**196* The string that starts a doc comment (e.g. '/**')197*/198open: string;199/**200* The string that appears on the last line and closes the doc comment (e.g. ' * /').201*/202close?: string;203}204205/**206* A tuple of two characters, like a pair of207* opening and closing brackets.208*/209export type CharacterPair = [string, string];210211export interface IAutoClosingPair {212open: string;213close: string;214}215216export interface IAutoClosingPairConditional extends IAutoClosingPair {217notIn?: string[];218}219220/**221* Describes what to do with the indentation when pressing Enter.222*/223export enum IndentAction {224/**225* Insert new line and copy the previous line's indentation.226*/227None = 0,228/**229* Insert new line and indent once (relative to the previous line's indentation).230*/231Indent = 1,232/**233* Insert two new lines:234* - the first one indented which will hold the cursor235* - the second one at the same indentation level236*/237IndentOutdent = 2,238/**239* Insert new line and outdent once (relative to the previous line's indentation).240*/241Outdent = 3242}243244/**245* Describes what to do when pressing Enter.246*/247export interface EnterAction {248/**249* Describe what to do with the indentation.250*/251indentAction: IndentAction;252/**253* Describes text to be appended after the new line and after the indentation.254*/255appendText?: string;256/**257* Describes the number of characters to remove from the new line's indentation.258*/259removeText?: number;260}261262/**263* @internal264*/265export interface CompleteEnterAction {266/**267* Describe what to do with the indentation.268*/269indentAction: IndentAction;270/**271* Describes text to be appended after the new line and after the indentation.272*/273appendText: string;274/**275* Describes the number of characters to remove from the new line's indentation.276*/277removeText: number;278/**279* The line's indentation minus removeText280*/281indentation: string;282}283284/**285* @internal286*/287export class StandardAutoClosingPairConditional {288289readonly open: string;290readonly close: string;291private readonly _inString: boolean;292private readonly _inComment: boolean;293private readonly _inRegEx: boolean;294private _neutralCharacter: string | null = null;295private _neutralCharacterSearched: boolean = false;296297constructor(source: IAutoClosingPairConditional) {298this.open = source.open;299this.close = source.close;300301// initially allowed in all tokens302this._inString = true;303this._inComment = true;304this._inRegEx = true;305306if (Array.isArray(source.notIn)) {307for (let i = 0, len = source.notIn.length; i < len; i++) {308const notIn: string = source.notIn[i];309switch (notIn) {310case 'string':311this._inString = false;312break;313case 'comment':314this._inComment = false;315break;316case 'regex':317this._inRegEx = false;318break;319}320}321}322}323324public isOK(standardToken: StandardTokenType): boolean {325switch (standardToken) {326case StandardTokenType.Other:327return true;328case StandardTokenType.Comment:329return this._inComment;330case StandardTokenType.String:331return this._inString;332case StandardTokenType.RegEx:333return this._inRegEx;334}335}336337public shouldAutoClose(context: ScopedLineTokens, column: number): boolean {338// Always complete on empty line339if (context.getTokenCount() === 0) {340return true;341}342343const tokenIndex = context.findTokenIndexAtOffset(column - 2);344const standardTokenType = context.getStandardTokenType(tokenIndex);345return this.isOK(standardTokenType);346}347348private _findNeutralCharacterInRange(fromCharCode: number, toCharCode: number): string | null {349for (let charCode = fromCharCode; charCode <= toCharCode; charCode++) {350const character = String.fromCharCode(charCode);351if (!this.open.includes(character) && !this.close.includes(character)) {352return character;353}354}355return null;356}357358/**359* Find a character in the range [0-9a-zA-Z] that does not appear in the open or close360*/361public findNeutralCharacter(): string | null {362if (!this._neutralCharacterSearched) {363this._neutralCharacterSearched = true;364if (!this._neutralCharacter) {365this._neutralCharacter = this._findNeutralCharacterInRange(CharCode.Digit0, CharCode.Digit9);366}367if (!this._neutralCharacter) {368this._neutralCharacter = this._findNeutralCharacterInRange(CharCode.a, CharCode.z);369}370if (!this._neutralCharacter) {371this._neutralCharacter = this._findNeutralCharacterInRange(CharCode.A, CharCode.Z);372}373}374return this._neutralCharacter;375}376}377378/**379* @internal380*/381export class AutoClosingPairs {382// it is useful to be able to get pairs using either end of open and close383384/** Key is first character of open */385public readonly autoClosingPairsOpenByStart: Map<string, StandardAutoClosingPairConditional[]>;386/** Key is last character of open */387public readonly autoClosingPairsOpenByEnd: Map<string, StandardAutoClosingPairConditional[]>;388/** Key is first character of close */389public readonly autoClosingPairsCloseByStart: Map<string, StandardAutoClosingPairConditional[]>;390/** Key is last character of close */391public readonly autoClosingPairsCloseByEnd: Map<string, StandardAutoClosingPairConditional[]>;392/** Key is close. Only has pairs that are a single character */393public readonly autoClosingPairsCloseSingleChar: Map<string, StandardAutoClosingPairConditional[]>;394395constructor(autoClosingPairs: StandardAutoClosingPairConditional[]) {396this.autoClosingPairsOpenByStart = new Map<string, StandardAutoClosingPairConditional[]>();397this.autoClosingPairsOpenByEnd = new Map<string, StandardAutoClosingPairConditional[]>();398this.autoClosingPairsCloseByStart = new Map<string, StandardAutoClosingPairConditional[]>();399this.autoClosingPairsCloseByEnd = new Map<string, StandardAutoClosingPairConditional[]>();400this.autoClosingPairsCloseSingleChar = new Map<string, StandardAutoClosingPairConditional[]>();401for (const pair of autoClosingPairs) {402appendEntry(this.autoClosingPairsOpenByStart, pair.open.charAt(0), pair);403appendEntry(this.autoClosingPairsOpenByEnd, pair.open.charAt(pair.open.length - 1), pair);404appendEntry(this.autoClosingPairsCloseByStart, pair.close.charAt(0), pair);405appendEntry(this.autoClosingPairsCloseByEnd, pair.close.charAt(pair.close.length - 1), pair);406if (pair.close.length === 1 && pair.open.length === 1) {407appendEntry(this.autoClosingPairsCloseSingleChar, pair.close, pair);408}409}410}411}412413function appendEntry<K, V>(target: Map<K, V[]>, key: K, value: V): void {414if (target.has(key)) {415target.get(key)!.push(value);416} else {417target.set(key, [value]);418}419}420421422