diff --git a/extensions/javascript/javascript-language-configuration.json b/extensions/javascript/javascript-language-configuration.json index 4080becc987..ad48834d04a 100644 --- a/extensions/javascript/javascript-language-configuration.json +++ b/extensions/javascript/javascript-language-configuration.json @@ -27,8 +27,8 @@ ], "folding": { "markers": { - "start": "^\\s*//\\s*#region", - "end": "^\\s*//\\s*#endregion" + "start": "^\\s*//\\s*#?region", + "end": "^\\s*//\\s*#?endregion" } } } \ No newline at end of file diff --git a/extensions/typescript/language-configuration.json b/extensions/typescript/language-configuration.json index 4080becc987..ad48834d04a 100644 --- a/extensions/typescript/language-configuration.json +++ b/extensions/typescript/language-configuration.json @@ -27,8 +27,8 @@ ], "folding": { "markers": { - "start": "^\\s*//\\s*#region", - "end": "^\\s*//\\s*#endregion" + "start": "^\\s*//\\s*#?region", + "end": "^\\s*//\\s*#?endregion" } } } \ No newline at end of file diff --git a/src/vs/editor/common/model/indentRanges.ts b/src/vs/editor/common/model/indentRanges.ts index 272b011460d..99396d38f0f 100644 --- a/src/vs/editor/common/model/indentRanges.ts +++ b/src/vs/editor/common/model/indentRanges.ts @@ -6,6 +6,7 @@ 'use strict'; import { ITextModel } from 'vs/editor/common/editorCommon'; +import { FoldingMarkers } from 'vs/editor/common/modes/languageConfiguration'; export class IndentRange { _indentRangeBrand: void; @@ -31,23 +32,15 @@ export class IndentRange { } } -export interface FoldMarkers { - start: string; - end: string; - indent?: number; -} - interface PreviousRegion { indent: number; line: number; marker: RegExp; }; -export function computeRanges(model: ITextModel, offSide: boolean, markers?: FoldMarkers, minimumRangeSize: number = 1): IndentRange[] { +export function computeRanges(model: ITextModel, offSide: boolean, markers?: FoldingMarkers, minimumRangeSize: number = 1): IndentRange[] { let result: IndentRange[] = []; let pattern = void 0; - let patternIndent = -1; if (markers) { - pattern = new RegExp(`(${markers.start})|(?:${markers.end})`); - patternIndent = typeof markers.indent === 'number' ? markers.indent : -1; + pattern = new RegExp(`(${markers.start.source})|(?:${markers.end.source})`); } let previousRegions: PreviousRegion[] = []; @@ -64,7 +57,7 @@ export function computeRanges(model: ITextModel, offSide: boolean, markers?: Fol continue; // only whitespace } let m; - if (pattern && (patternIndent === -1 || patternIndent === indent) && (m = model.getLineContent(line).match(pattern))) { + if (pattern && (m = model.getLineContent(line).match(pattern))) { // folding pattern match if (m[1]) { // start pattern match if (previous.indent >= 0 && !previous.marker) { diff --git a/src/vs/editor/common/model/textModelWithTokens.ts b/src/vs/editor/common/model/textModelWithTokens.ts index 0006a7da417..e0a17aaab0f 100644 --- a/src/vs/editor/common/model/textModelWithTokens.ts +++ b/src/vs/editor/common/model/textModelWithTokens.ts @@ -845,7 +845,7 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke if (!this._indentRanges) { let foldingRules = LanguageConfigurationRegistry.getFoldingRules(this._languageIdentifier.id); let offSide = foldingRules && foldingRules.offSide; - let markers = foldingRules && foldingRules['markers']; + let markers = foldingRules && foldingRules.markers; this._indentRanges = computeRanges(this, offSide, markers); } return this._indentRanges; diff --git a/src/vs/editor/common/modes/languageConfiguration.ts b/src/vs/editor/common/modes/languageConfiguration.ts index 093db6b102a..aaf139ee7ba 100644 --- a/src/vs/editor/common/modes/languageConfiguration.ts +++ b/src/vs/editor/common/modes/languageConfiguration.ts @@ -98,6 +98,17 @@ export interface IndentationRule { } +/** + * Describes 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: + * - the regex should start with '^' + * - regexp flags (i, g) are ignored + */ +export interface FoldingMarkers { + start: RegExp; + end: RegExp; +} + /** * Describes folding rules for a language. */ @@ -109,6 +120,11 @@ export interface FoldingRules { * If not set, `false` is used and empty lines belong to the previous block. */ offSide?: boolean; + + /** + * Region markers used by the language. + */ + markers?: FoldingMarkers; } /** diff --git a/src/vs/editor/test/common/model/indentRanges.test.ts b/src/vs/editor/test/common/model/indentRanges.test.ts index 0a022e43d02..8d37db98ea9 100644 --- a/src/vs/editor/test/common/model/indentRanges.test.ts +++ b/src/vs/editor/test/common/model/indentRanges.test.ts @@ -7,7 +7,8 @@ import * as assert from 'assert'; import { Model } from 'vs/editor/common/model/model'; -import { computeRanges, FoldMarkers } from 'vs/editor/common/model/indentRanges'; +import { computeRanges } from 'vs/editor/common/model/indentRanges'; +import { FoldingMarkers } from 'vs/editor/common/modes/languageConfiguration'; export interface IndentRange { startLineNumber: number; @@ -16,7 +17,7 @@ export interface IndentRange { marker: boolean; } -function assertRanges(lines: string[], expected: IndentRange[], offside: boolean, markers?: FoldMarkers): void { +function assertRanges(lines: string[], expected: IndentRange[], offside: boolean, markers?: FoldingMarkers): void { let model = Model.createFromString(lines.join('\n')); let actual = computeRanges(model, offside, markers); actual.sort((r1, r2) => r1.startLineNumber - r2.startLineNumber); @@ -144,9 +145,9 @@ function r(startLineNumber: number, endLineNumber: number, indent: number, marke // }); // }); -let foldPattern: FoldMarkers = { - start: '^\\s*#region', - end: '^\\s*#endregion' +let markers: FoldingMarkers = { + start: /^\\s*#region/, + end: /^\\s*#endregion/ }; suite('Folding with regions', () => { @@ -160,7 +161,7 @@ suite('Folding with regions', () => { /* 6*/ ' }', /* 7*/ ' #endregion', /* 8*/ '}', - ], [r(1, 7, 0), r(2, 7, 2, true), r(3, 5, 2)], false, foldPattern); + ], [r(1, 7, 0), r(2, 7, 2, true), r(3, 5, 2)], false, markers); }); test('Inside region, not indented', () => { assertRanges([ @@ -172,7 +173,7 @@ suite('Folding with regions', () => { /* 6*/ ' }', /* 7*/ '#endregion', /* 8*/ '', - ], [r(2, 7, 0, true), r(3, 6, 0)], false, foldPattern); + ], [r(2, 7, 0, true), r(3, 6, 0)], false, markers); }); test('Empty Regions', () => { assertRanges([ @@ -183,7 +184,7 @@ suite('Folding with regions', () => { /* 5*/ '', /* 6*/ '#endregion', /* 7*/ 'var y;', - ], [r(2, 3, 0, true), r(4, 6, 0, true)], false, foldPattern); + ], [r(2, 3, 0, true), r(4, 6, 0, true)], false, markers); }); test('Nested Regions', () => { assertRanges([ @@ -194,7 +195,7 @@ suite('Folding with regions', () => { /* 5*/ '#endregion', /* 6*/ '#endregion', /* 7*/ 'var y;', - ], [r(2, 6, 0, true), r(3, 5, 0, true)], false, foldPattern); + ], [r(2, 6, 0, true), r(3, 5, 0, true)], false, markers); }); test('Nested Regions 2', () => { assertRanges([ @@ -207,7 +208,7 @@ suite('Folding with regions', () => { /* 7*/ ' // comment', /* 8*/ ' #endregion', /* 9*/ '}', - ], [r(1, 8, 0), r(2, 8, 2, true), r(4, 6, 2, true)], false, foldPattern); + ], [r(1, 8, 0), r(2, 8, 2, true), r(4, 6, 2, true)], false, markers); }); test('Incomplete Regions', () => { assertRanges([ @@ -215,7 +216,7 @@ suite('Folding with regions', () => { /* 2*/ '#region', /* 3*/ ' // comment', /* 4*/ '}', - ], [], false, foldPattern); + ], [], false, markers); }); test('Incomplete Regions', () => { assertRanges([ @@ -227,7 +228,7 @@ suite('Folding with regions', () => { /* 6*/ '#endregion', /* 7*/ '#endregion', /* 8*/ ' // hello', - ], [r(3, 7, 0, true), r(4, 6, 0, true)], false, foldPattern); + ], [r(3, 7, 0, true), r(4, 6, 0, true)], false, markers); }); test('Indented region before', () => { assertRanges([ @@ -237,7 +238,7 @@ suite('Folding with regions', () => { /* 4*/ '#region', /* 5*/ ' // comment', /* 6*/ '#endregion', - ], [r(1, 3, 0), r(4, 6, 0, true)], false, foldPattern); + ], [r(1, 3, 0), r(4, 6, 0, true)], false, markers); }); test('Indented region before 2', () => { assertRanges([ @@ -247,7 +248,7 @@ suite('Folding with regions', () => { /* 4*/ ' #region', /* 5*/ ' // comment', /* 6*/ ' #endregion', - ], [r(1, 6, 0), r(2, 6, 2), r(4, 6, 4, true)], false, foldPattern); + ], [r(1, 6, 0), r(2, 6, 2), r(4, 6, 4, true)], false, markers); }); test('Indented region in-between', () => { assertRanges([ @@ -257,7 +258,7 @@ suite('Folding with regions', () => { /* 4*/ ' return;', /* 5*/ '', /* 6*/ '#endregion', - ], [r(1, 6, 0, true), r(3, 5, 2)], false, foldPattern); + ], [r(1, 6, 0, true), r(3, 5, 2)], false, markers); }); test('Indented region after', () => { assertRanges([ @@ -267,6 +268,6 @@ suite('Folding with regions', () => { /* 4*/ '#endregion', /* 5*/ ' if (x)', /* 6*/ ' return;', - ], [r(1, 4, 0, true), r(5, 6, 2)], false, foldPattern); + ], [r(1, 4, 0, true), r(5, 6, 2)], false, markers); }); }); \ No newline at end of file diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index bff19722704..8ab78fc57b9 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -4361,6 +4361,17 @@ declare module monaco.languages { unIndentedLinePattern?: RegExp; } + /** + * Describes 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: + * - the regex should start with '^' + * - regexp flags (i, g) are ignored + */ + export interface FoldingMarkers { + start: RegExp; + end: RegExp; + } + /** * Describes folding rules for a language. */ @@ -4372,6 +4383,10 @@ declare module monaco.languages { * If not set, `false` is used and empty lines belong to the previous block. */ offSide?: boolean; + /** + * Region markers used by the language. + */ + markers?: FoldingMarkers; } /** diff --git a/src/vs/workbench/parts/codeEditor/electron-browser/languageConfiguration/languageConfigurationExtensionPoint.ts b/src/vs/workbench/parts/codeEditor/electron-browser/languageConfiguration/languageConfigurationExtensionPoint.ts index d6532f578bd..7da69d61142 100644 --- a/src/vs/workbench/parts/codeEditor/electron-browser/languageConfiguration/languageConfigurationExtensionPoint.ts +++ b/src/vs/workbench/parts/codeEditor/electron-browser/languageConfiguration/languageConfigurationExtensionPoint.ts @@ -119,7 +119,12 @@ export class LanguageConfigurationFileHandler { } if (configuration.folding) { - richEditConfig.folding = configuration.folding; + let markers = configuration.folding.markers; + + richEditConfig.folding = { + offSide: configuration.folding.offSide, + markers: markers ? { start: new RegExp(markers.start), end: new RegExp(markers.end) } : void 0 + }; } LanguageConfigurationRegistry.register(languageIdentifier, richEditConfig); @@ -390,6 +395,20 @@ const schema: IJSONSchema = { offSide: { type: 'boolean', description: 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.'), + }, + markers: { + type: 'object', + description: 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'), + properties: { + start: { + type: 'string', + description: nls.localize('schema.folding.markers.start', 'The RegExp pattern for the start marker. The regexp must start with \'^\'.') + }, + end: { + type: 'string', + description: nls.localize('schema.folding.markers.end', 'The RegExp pattern for the end marker. The regexp must start with \'^\'.') + }, + } } } }