[folding] fold regions, initial, preconfigured support. For #12146

This commit is contained in:
Martin Aeschlimann 2017-09-22 13:49:41 +02:00
parent a678f60100
commit 9e05d4b635
9 changed files with 341 additions and 135 deletions

View file

@ -33,5 +33,11 @@
"scopeName": "source.c.platform",
"path": "./syntaxes/Platform.tmLanguage"
}]
},
"folding": {
"markers": {
"start": "^\\s*#pragma\\s+region",
"end": "^\\s*#pragma\\s+endregion"
}
}
}

View file

@ -23,5 +23,11 @@
["<", ">"],
["'", "'"],
["\"", "\""]
]
],
"folding": {
"markers": {
"start": "^\\s*#region",
"end": "^\\s*#endregion"
}
}
}

View file

@ -24,5 +24,11 @@
["'", "'"],
["\"", "\""],
["`", "`"]
]
],
"folding": {
"markers": {
"start": "^\\s*//\\s*#region",
"end": "^\\s*//\\s*#endregion"
}
}
}

View file

@ -22,5 +22,11 @@
["(", ")"],
["\"", "\""],
["'", "'"]
]
],
"folding": {
"markers": {
"start": "^\\s*#region",
"end": "^\\s*#endregion"
}
}
}

View file

@ -24,5 +24,11 @@
["'", "'"],
["\"", "\""],
["`", "`"]
]
],
"folding": {
"markers": {
"start": "^\\s*//\\s*#region",
"end": "^\\s*//\\s*#endregion"
}
}
}

View file

@ -20,5 +20,11 @@
["(", ")"],
["\"", "\""],
["<", ">"]
]
],
"folding": {
"markers": {
"start": "^\\s*#Region",
"end": "^\\s*#End Region"
}
}
}

View file

@ -12,11 +12,13 @@ export class IndentRange {
startLineNumber: number;
endLineNumber: number;
indent: number;
marker: boolean;
constructor(startLineNumber: number, endLineNumber: number, indent: number) {
constructor(startLineNumber: number, endLineNumber: number, indent: number, marker?: boolean) {
this.startLineNumber = startLineNumber;
this.endLineNumber = endLineNumber;
this.indent = indent;
this.marker = marker;
}
public static deepCloneArr(indentRanges: IndentRange[]): IndentRange[] {
@ -29,12 +31,27 @@ export class IndentRange {
}
}
export function computeRanges(model: ITextModel, offSide: boolean, minimumRangeSize: number = 1): 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[] {
let result: IndentRange[] = [];
let previousRegions: { indent: number, line: number }[] = [];
previousRegions.push({ indent: -1, line: model.getLineCount() + 1 }); // sentinel, to make sure there's at least one entry
let pattern = void 0;
let patternIndent = -1;
if (markers) {
pattern = new RegExp(`(${markers.start})|(?:${markers.end})`);
patternIndent = typeof markers.indent === 'number' ? markers.indent : -1;
}
let previousRegions: PreviousRegion[] = [];
previousRegions.push({ indent: -1, line: model.getLineCount() + 1, marker: null }); // sentinel, to make sure there's at least one entry
for (let line = model.getLineCount(); line > 0; line--) {
let indent = model.getIndentLevel(line);
@ -46,26 +63,48 @@ export function computeRanges(model: ITextModel, offSide: boolean, minimumRangeS
}
continue; // only whitespace
}
let m;
if (pattern && (patternIndent === -1 || patternIndent === indent) && (m = model.getLineContent(line).match(pattern))) {
// folding pattern match
if (m[1]) { // start pattern match
if (previous.indent >= 0 && !previous.marker) {
if (previous.indent > indent) {
// discard all regions with larger indent
do {
previousRegions.pop();
previous = previousRegions[previousRegions.length - 1];
} while (previous.indent > indent);
// new folding range
let endLineNumber = previous.line - 1;
if (endLineNumber - line >= minimumRangeSize) {
result.push(new IndentRange(line, endLineNumber, indent));
// discard all regions until the folding pattern
do {
previousRegions.pop();
previous = previousRegions[previousRegions.length - 1];
} while (previous.indent >= 0 && !previous.marker);
}
if (previous.marker) {
// new folding range from pattern, includes the end line
result.push(new IndentRange(line, previous.line, indent, true));
previous.marker = null;
previous.indent = indent;
previous.line = line;
}
} else { // end pattern match
previousRegions.push({ indent: -2, line, marker: pattern });
}
} else {
if (previous.indent > indent) {
// discard all regions with larger indent
do {
previousRegions.pop();
previous = previousRegions[previousRegions.length - 1];
} while (previous.indent > indent);
// new folding range
let endLineNumber = previous.line - 1;
if (endLineNumber - line >= minimumRangeSize) {
result.push(new IndentRange(line, endLineNumber, indent));
}
}
if (previous.indent === indent) {
previous.line = line;
} else { // previous.indent < indent
// new region with a bigger indent
previousRegions.push({ indent, line, marker: null });
}
}
if (previous.indent === indent) {
previous.line = line;
} else { // previous.indent < indent
// new region with a bigger indent
previousRegions.push({ indent, line });
}
}

View file

@ -845,6 +845,8 @@ 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'];
this._indentRanges = computeRanges(this, offSide, markers);
}
return this._indentRanges;
}

View file

@ -7,137 +7,266 @@
import * as assert from 'assert';
import { Model } from 'vs/editor/common/model/model';
import { computeRanges } from 'vs/editor/common/model/indentRanges';
import { computeRanges, FoldMarkers } from 'vs/editor/common/model/indentRanges';
export interface IndentRange {
startLineNumber: number;
endLineNumber: number;
indent: number;
marker: boolean;
}
suite('Indentation Folding', () => {
function assertRanges(lines: string[], expected: IndentRange[], offside): void {
let model = Model.createFromString(lines.join('\n'));
let actual = computeRanges(model, offside);
actual.sort((r1, r2) => r1.startLineNumber - r2.startLineNumber);
assert.deepEqual(actual, expected);
model.dispose();
}
function assertRanges(lines: string[], expected: IndentRange[], offside: boolean, markers?: FoldMarkers): void {
let model = Model.createFromString(lines.join('\n'));
let actual = computeRanges(model, offside, markers);
actual.sort((r1, r2) => r1.startLineNumber - r2.startLineNumber);
assert.deepEqual(actual, expected);
model.dispose();
}
function r(startLineNumber: number, endLineNumber: number, indent: number): IndentRange {
return { startLineNumber, endLineNumber, indent };
}
function r(startLineNumber: number, endLineNumber: number, indent: number, marker?: boolean): IndentRange {
return { startLineNumber, endLineNumber, indent, marker };
}
test('Fold one level', () => {
let range = [
'A',
' A',
' A',
' A'
];
assertRanges(range, [r(1, 4, 0)], true);
assertRanges(range, [r(1, 4, 0)], false);
});
// suite('Indentation Folding', () => {
test('Fold two levels', () => {
let range = [
'A',
' A',
' A',
' A',
' A'
];
assertRanges(range, [r(1, 5, 0), r(3, 5, 2)], true);
assertRanges(range, [r(1, 5, 0), r(3, 5, 2)], false);
});
// test('Fold one level', () => {
// let range = [
// 'A',
// ' A',
// ' A',
// ' A'
// ];
// assertRanges(range, [r(1, 4, 0)], true);
// assertRanges(range, [r(1, 4, 0)], false);
// });
test('Fold three levels', () => {
let range = [
'A',
' A',
' A',
' A',
'A'
];
assertRanges(range, [r(1, 4, 0), r(2, 4, 2), r(3, 4, 4)], true);
assertRanges(range, [r(1, 4, 0), r(2, 4, 2), r(3, 4, 4)], false);
});
// test('Fold two levels', () => {
// let range = [
// 'A',
// ' A',
// ' A',
// ' A',
// ' A'
// ];
// assertRanges(range, [r(1, 5, 0), r(3, 5, 2)], true);
// assertRanges(range, [r(1, 5, 0), r(3, 5, 2)], false);
// });
test('Fold decreasing indent', () => {
let range = [
' A',
' A',
'A'
];
assertRanges(range, [], true);
assertRanges(range, [], false);
});
// test('Fold three levels', () => {
// let range = [
// 'A',
// ' A',
// ' A',
// ' A',
// 'A'
// ];
// assertRanges(range, [r(1, 4, 0), r(2, 4, 2), r(3, 4, 4)], true);
// assertRanges(range, [r(1, 4, 0), r(2, 4, 2), r(3, 4, 4)], false);
// });
test('Fold Java', () => {
// test('Fold decreasing indent', () => {
// let range = [
// ' A',
// ' A',
// 'A'
// ];
// assertRanges(range, [], true);
// assertRanges(range, [], false);
// });
// test('Fold Java', () => {
// assertRanges([
// /* 1*/ 'class A {',
// /* 2*/ ' void foo() {',
// /* 3*/ ' console.log();',
// /* 4*/ ' console.log();',
// /* 5*/ ' }',
// /* 6*/ '',
// /* 7*/ ' void bar() {',
// /* 8*/ ' console.log();',
// /* 9*/ ' }',
// /*10*/ '}',
// /*11*/ 'interface B {',
// /*12*/ ' void bar();',
// /*13*/ '}',
// ], [r(1, 9, 0), r(2, 4, 2), r(7, 8, 2), r(11, 12, 0)], false);
// });
// test('Fold Javadoc', () => {
// assertRanges([
// /* 1*/ '/**',
// /* 2*/ ' * Comment',
// /* 3*/ ' */',
// /* 4*/ 'class A {',
// /* 5*/ ' void foo() {',
// /* 6*/ ' }',
// /* 7*/ '}',
// ], [r(1, 3, 0), r(4, 6, 0)], false);
// });
// test('Fold Whitespace Java', () => {
// assertRanges([
// /* 1*/ 'class A {',
// /* 2*/ '',
// /* 3*/ ' void foo() {',
// /* 4*/ ' ',
// /* 5*/ ' return 0;',
// /* 6*/ ' }',
// /* 7*/ ' ',
// /* 8*/ '}',
// ], [r(1, 7, 0), r(3, 5, 2)], false);
// });
// test('Fold Whitespace Python', () => {
// assertRanges([
// /* 1*/ 'def a:',
// /* 2*/ ' pass',
// /* 3*/ ' ',
// /* 4*/ ' def b:',
// /* 5*/ ' pass',
// /* 6*/ ' ',
// /* 7*/ ' ',
// /* 8*/ 'def c: # since there was a deintent here'
// ], [r(1, 5, 0), r(4, 5, 2)], true);
// });
// test('Fold Tabs', () => {
// assertRanges([
// /* 1*/ 'class A {',
// /* 2*/ '\t\t',
// /* 3*/ '\tvoid foo() {',
// /* 4*/ '\t \t//hello',
// /* 5*/ '\t return 0;',
// /* 6*/ ' \t}',
// /* 7*/ ' ',
// /* 8*/ '}',
// ], [r(1, 7, 0), r(3, 5, 4)], false);
// });
// });
let foldPattern: FoldMarkers = {
start: '^\\s*#region',
end: '^\\s*#endregion'
};
suite('Folding with regions', () => {
test('Inside region, indented', () => {
assertRanges([
/* 1*/ 'class A {',
/* 2*/ ' void foo() {',
/* 3*/ ' console.log();',
/* 4*/ ' console.log();',
/* 5*/ ' }',
/* 6*/ '',
/* 7*/ ' void bar() {',
/* 8*/ ' console.log();',
/* 9*/ ' }',
/*10*/ '}',
/*11*/ 'interface B {',
/*12*/ ' void bar();',
/*13*/ '}',
], [r(1, 9, 0), r(2, 4, 2), r(7, 8, 2), r(11, 12, 0)], false);
});
test('Fold Javadoc', () => {
assertRanges([
/* 1*/ '/**',
/* 2*/ ' * Comment',
/* 3*/ ' */',
/* 4*/ 'class A {',
/* 5*/ ' void foo() {',
/* 6*/ ' }',
/* 7*/ '}',
], [r(1, 3, 0), r(4, 6, 0)], false);
});
test('Fold Whitespace Java', () => {
assertRanges([
/* 1*/ 'class A {',
/* 2*/ '',
/* 2*/ ' #region',
/* 3*/ ' void foo() {',
/* 4*/ ' ',
/* 5*/ ' return 0;',
/* 6*/ ' }',
/* 7*/ ' ',
/* 7*/ ' #endregion',
/* 8*/ '}',
], [r(1, 7, 0), r(3, 5, 2)], false);
], [r(1, 7, 0), r(2, 7, 2, true), r(3, 5, 2)], false, foldPattern);
});
test('Fold Whitespace Python', () => {
test('Inside region, not indented', () => {
assertRanges([
/* 1*/ 'def a:',
/* 2*/ ' pass',
/* 3*/ ' ',
/* 4*/ ' def b:',
/* 5*/ ' pass',
/* 6*/ ' ',
/* 7*/ ' ',
/* 8*/ 'def c: # since there was a deintent here'
], [r(1, 5, 0), r(4, 5, 2)], true);
/* 1*/ 'var x;',
/* 2*/ '#region',
/* 3*/ 'void foo() {',
/* 4*/ ' ',
/* 5*/ ' return 0;',
/* 6*/ ' }',
/* 7*/ '#endregion',
/* 8*/ '',
], [r(2, 7, 0, true), r(3, 6, 0)], false, foldPattern);
});
test('Fold Tabs', () => {
test('Empty Regions', () => {
assertRanges([
/* 1*/ 'var x;',
/* 2*/ '#region',
/* 3*/ '#endregion',
/* 4*/ '#region',
/* 5*/ '',
/* 6*/ '#endregion',
/* 7*/ 'var y;',
], [r(2, 3, 0, true), r(4, 6, 0, true)], false, foldPattern);
});
test('Nested Regions', () => {
assertRanges([
/* 1*/ 'var x;',
/* 2*/ '#region',
/* 3*/ '#region',
/* 4*/ '',
/* 5*/ '#endregion',
/* 6*/ '#endregion',
/* 7*/ 'var y;',
], [r(2, 6, 0, true), r(3, 5, 0, true)], false, foldPattern);
});
test('Nested Regions 2', () => {
assertRanges([
/* 1*/ 'class A {',
/* 2*/ '\t\t',
/* 3*/ '\tvoid foo() {',
/* 4*/ '\t \t//hello',
/* 5*/ '\t return 0;',
/* 6*/ ' \t}',
/* 7*/ ' ',
/* 8*/ '}',
], [r(1, 7, 0), r(3, 5, 4)], false);
/* 2*/ ' #region',
/* 3*/ '',
/* 4*/ ' #region',
/* 5*/ '',
/* 6*/ ' #endregion',
/* 7*/ ' // comment',
/* 8*/ ' #endregion',
/* 9*/ '}',
], [r(1, 8, 0), r(2, 8, 2, true), r(4, 6, 2, true)], false, foldPattern);
});
});
test('Incomplete Regions', () => {
assertRanges([
/* 1*/ 'class A {',
/* 2*/ '#region',
/* 3*/ ' // comment',
/* 4*/ '}',
], [], false, foldPattern);
});
test('Incomplete Regions', () => {
assertRanges([
/* 1*/ '',
/* 2*/ '#region',
/* 3*/ '#region',
/* 4*/ '#region',
/* 5*/ ' // comment',
/* 6*/ '#endregion',
/* 7*/ '#endregion',
/* 8*/ ' // hello',
], [r(3, 7, 0, true), r(4, 6, 0, true)], false, foldPattern);
});
test('Indented region before', () => {
assertRanges([
/* 1*/ 'if (x)',
/* 2*/ ' return;',
/* 3*/ '',
/* 4*/ '#region',
/* 5*/ ' // comment',
/* 6*/ '#endregion',
], [r(1, 3, 0), r(4, 6, 0, true)], false, foldPattern);
});
test('Indented region before 2', () => {
assertRanges([
/* 1*/ 'if (x)',
/* 2*/ ' log();',
/* 3*/ '',
/* 4*/ ' #region',
/* 5*/ ' // comment',
/* 6*/ ' #endregion',
], [r(1, 6, 0), r(2, 6, 2), r(4, 6, 4, true)], false, foldPattern);
});
test('Indented region in-between', () => {
assertRanges([
/* 1*/ '#region',
/* 2*/ ' // comment',
/* 3*/ ' if (x)',
/* 4*/ ' return;',
/* 5*/ '',
/* 6*/ '#endregion',
], [r(1, 6, 0, true), r(3, 5, 2)], false, foldPattern);
});
test('Indented region after', () => {
assertRanges([
/* 1*/ '#region',
/* 2*/ ' // comment',
/* 3*/ '',
/* 4*/ '#endregion',
/* 5*/ ' if (x)',
/* 6*/ ' return;',
], [r(1, 4, 0, true), r(5, 6, 2)], false, foldPattern);
});
});