diff --git a/extensions/html/server/src/modes/htmlFolding.ts b/extensions/html/server/src/modes/htmlFolding.ts index f76574f86e5..ac9143e37d3 100644 --- a/extensions/html/server/src/modes/htmlFolding.ts +++ b/extensions/html/server/src/modes/htmlFolding.ts @@ -8,6 +8,7 @@ import { LanguageService as HTMLLanguageService, TokenType, Range } from 'vscode import { FoldingRangeType, FoldingRange, FoldingRangeList } from '../protocol/foldingProvider.proposed'; import { LanguageModes } from './languageModes'; +import { binarySearch } from '../utils/arrays'; export function getFoldingRegions(languageModes: LanguageModes, document: TextDocument, maxRanges: number | undefined, cancellationToken: CancellationToken | null): FoldingRangeList { let htmlMode = languageModes.getMode('html'); @@ -90,6 +91,11 @@ function limitRanges(ranges: FoldingRange[], maxRanges: number) { return ranges.filter((r, index) => (typeof nestingLevels[index] === 'number') && nestingLevels[index] < maxLevel); } +export const EMPTY_ELEMENTS: string[] = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'menuitem', 'meta', 'param', 'source', 'track', 'wbr']; + +export function isEmptyElement(e: string): boolean { + return !!e && binarySearch(EMPTY_ELEMENTS, e.toLowerCase(), (s1: string, s2: string) => s1.localeCompare(s2)) >= 0; +} export function getHTMLFoldingRegions(htmlLanguageService: HTMLLanguageService, document: TextDocument, range: Range): FoldingRange[] { const scanner = htmlLanguageService.createScanner(document.getText()); @@ -121,6 +127,11 @@ export function getHTMLFoldingRegions(htmlLanguageService: HTMLLanguageService, lastTagName = scanner.getTokenText(); break; } + case TokenType.StartTagClose: + if (!isEmptyElement(lastTagName)) { + break; + } + // fallthrough case TokenType.EndTagClose: case TokenType.StartTagSelfClose: { let name = elementNames.pop(); diff --git a/extensions/html/server/src/test/folding.test.ts b/extensions/html/server/src/test/folding.test.ts index 014ce66cb7b..cd52e2c07af 100644 --- a/extensions/html/server/src/test/folding.test.ts +++ b/extensions/html/server/src/test/folding.test.ts @@ -69,19 +69,20 @@ suite('Object Folding', () => { assertRanges(input, [r(0, 6), r(1, 2), r(4, 5)]); }); - // test('Fold self-closing tags', () => { - // let input = [ - // /*0*/'
', - // /*1*/'', - // /*2*/'', - // /*4*/'', - // /*5*/'Body', - // /*6*/'', - // /*7*/'' - // ]; - // assertRanges(input, [r(0, 6), r(1, 2), r(4, 5)]); - // }); + test('Fold self-closing tags', () => { + let input = [ + /*0*/'
', + /*1*/'', + /*2*/'', + /*3*/'
', + /*4*/'
', + /*5*/'', + /*8*/'
' + ]; + assertRanges(input, [r(0, 7), r(5, 6)]); + }); // test('Fold commment', () => { // let input = [ diff --git a/extensions/html/server/src/utils/arrays.ts b/extensions/html/server/src/utils/arrays.ts index a33ef18597c..cfabc4008ab 100644 --- a/extensions/html/server/src/utils/arrays.ts +++ b/extensions/html/server/src/utils/arrays.ts @@ -57,3 +57,21 @@ function _divideAndMerge(data: T[], compare: (a: T, b: T) => number): void { data[i++] = right[rightIdx++]; } } + +export function binarySearch(array: T[], key: T, comparator: (op1: T, op2: T) => number): number { + let low = 0, + high = array.length - 1; + + while (low <= high) { + let mid = ((low + high) / 2) | 0; + let comp = comparator(array[mid], key); + if (comp < 0) { + low = mid + 1; + } else if (comp > 0) { + high = mid - 1; + } else { + return mid; + } + } + return -(low + 1); +}