Skip to content

Commit

Permalink
Included end tag in HTML folding range
Browse files Browse the repository at this point in the history
Signed-off-by: Alexander Chen <[email protected]>
  • Loading branch information
Alexander Chen committed Apr 14, 2022
1 parent 88d4621 commit 59a19cc
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 29 deletions.
4 changes: 2 additions & 2 deletions src/htmlLanguageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { findLinkedEditingRanges } from './services/htmlLinkedEditing';
import {
Scanner, HTMLDocument, CompletionConfiguration, ICompletionParticipant, HTMLFormatConfiguration, DocumentContext,
IHTMLDataProvider, HTMLDataV1, LanguageServiceOptions, TextDocument, SelectionRange, WorkspaceEdit,
Position, CompletionList, Hover, Range, SymbolInformation, TextEdit, DocumentHighlight, DocumentLink, FoldingRange, HoverSettings
Position, CompletionList, Hover, Range, SymbolInformation, TextEdit, DocumentHighlight, DocumentLink, FoldingRange, HoverSettings, FoldingRangeSettings
} from './htmlLanguageTypes';
import { getFoldingRanges } from './services/htmlFolding';
import { getSelectionRanges } from './services/htmlSelectionRange';
Expand All @@ -41,7 +41,7 @@ export interface LanguageService {
findDocumentSymbols(document: TextDocument, htmlDocument: HTMLDocument): SymbolInformation[];
doQuoteComplete(document: TextDocument, position: Position, htmlDocument: HTMLDocument, options?: CompletionConfiguration): string | null;
doTagComplete(document: TextDocument, position: Position, htmlDocument: HTMLDocument): string | null;
getFoldingRanges(document: TextDocument, context?: { rangeLimit?: number }): FoldingRange[];
getFoldingRanges(document: TextDocument, context?: { rangeLimit?: number }, options?: FoldingRangeSettings): FoldingRange[];
getSelectionRanges(document: TextDocument, positions: Position[]): SelectionRange[];
doRename(document: TextDocument, position: Position, newName: string, htmlDocument: HTMLDocument): WorkspaceEdit | null;
findMatchingTagPosition(document: TextDocument, position: Position, htmlDocument: HTMLDocument): Position | null;
Expand Down
6 changes: 5 additions & 1 deletion src/htmlLanguageTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ export namespace ClientCapabilities {

export interface LanguageServiceOptions {
/**
* Unless set to false, the default HTML data provider will be used
* Unless set to false, the default HTML data provider will be used
* along with the providers from customDataProviders.
* Defaults to true.
*/
Expand Down Expand Up @@ -319,3 +319,7 @@ export interface FileSystemProvider {
stat(uri: DocumentUri): Promise<FileStat>;
readDirectory?(uri: DocumentUri): Promise<[string, FileType][]>;
}

export interface FoldingRangeSettings {
includeClosingTag?: boolean;
}
6 changes: 3 additions & 3 deletions src/services/htmlFolding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { TokenType, FoldingRange, FoldingRangeKind, TextDocument } from '../htmlLanguageTypes';
import { TokenType, FoldingRange, FoldingRangeKind, TextDocument, FoldingRangeSettings } from '../htmlLanguageTypes';
import { createScanner } from '../parser/htmlScanner';
import { isVoidElement } from '../languageFacts/fact';

Expand Down Expand Up @@ -80,7 +80,7 @@ function limitRanges(ranges: FoldingRange[], rangeLimit: number) {
return result;
}

export function getFoldingRanges(document: TextDocument, context: { rangeLimit?: number }): FoldingRange[] {
export function getFoldingRanges(document: TextDocument, context: { rangeLimit?: number }, options?: FoldingRangeSettings): FoldingRange[] {
const scanner = createScanner(document.getText());
let token = scanner.scan();
const ranges: FoldingRange[] = [];
Expand Down Expand Up @@ -122,7 +122,7 @@ export function getFoldingRanges(document: TextDocument, context: { rangeLimit?:
stack.length = i;
const line = document.positionAt(scanner.getTokenOffset()).line;
const startLine = stackElement.startLine;
const endLine = line - 1;
const endLine = options?.includeClosingTag ? line : line - 1;
if (endLine > startLine && prevStart !== startLine) {
addRange({ startLine, endLine });
}
Expand Down
200 changes: 177 additions & 23 deletions src/test/folding.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import 'mocha';
import * as assert from 'assert';
import { TextDocument } from '../htmlLanguageTypes';
import { FoldingRangeSettings, TextDocument } from '../htmlLanguageTypes';
import { getFoldingRanges } from '../services/htmlFolding';

interface ExpectedIndentRange {
Expand All @@ -15,13 +15,13 @@ interface ExpectedIndentRange {
kind?: string;
}

function assertRanges(lines: string[], expected: ExpectedIndentRange[], message?: string, nRanges?: number): void {
function assertRanges(lines: string[], expected: ExpectedIndentRange[], message?: string, nRanges?: number, foldingRangeSettings?: FoldingRangeSettings): void {
const document = TextDocument.create('test://foo/bar.json', 'json', 1, lines.join('\n'));
const workspace = {
settings: {},
folders: [{ name: 'foo', uri: 'test://foo' }]
};
const actual = getFoldingRanges(document, { rangeLimit: nRanges });
const actual = getFoldingRanges(document, { rangeLimit: nRanges }, foldingRangeSettings);

let actualRanges = [];
for (let i = 0; i < actual.length; i++) {
Expand All @@ -35,14 +35,14 @@ function r(startLine: number, endLine: number, kind?: string): ExpectedIndentRan
return { startLine, endLine, kind };
}

suite('HTML Folding', () => {
suite('HTML Folding with Closing Tag', () => {
test('Fold one level', () => {
const input = [
/*0*/'<html>',
/*1*/'Hello',
/*2*/'</html>'
];
assertRanges(input, [r(0, 1)]);
assertRanges(input, [r(0, 2)], undefined, undefined, {includeClosingTag: true});
});

test('Fold two level', () => {
Expand All @@ -53,7 +53,7 @@ suite('HTML Folding', () => {
/*3*/'</head>',
/*4*/'</html>'
];
assertRanges(input, [r(0, 3), r(1, 2)]);
assertRanges(input, [r(0, 4), r(1, 3)], undefined, undefined, {includeClosingTag: true});
});

test('Fold siblings', () => {
Expand All @@ -67,7 +67,7 @@ suite('HTML Folding', () => {
/*6*/'</body>',
/*7*/'</html>'
];
assertRanges(input, [r(0, 6), r(1, 2), r(4, 5)]);
assertRanges(input, [r(0, 7), r(1, 3), r(4, 6)], undefined, undefined, {includeClosingTag: true});
});

test('Fold self-closing tags', () => {
Expand All @@ -82,7 +82,7 @@ suite('HTML Folding', () => {
/*7*/'>',
/*8*/'</div>'
];
assertRanges(input, [r(0, 7), r(5, 6)]);
assertRanges(input, [r(0, 8), r(5, 7)], undefined, undefined, {includeClosingTag: true});
});

test('Fold comment', () => {
Expand All @@ -93,7 +93,7 @@ suite('HTML Folding', () => {
/*3*/'<!-- some stuff',
/*4*/' some more stuff -->',
];
assertRanges(input, [r(0, 2, 'comment'), r(3, 4, 'comment')]);
assertRanges(input, [r(0, 2, 'comment'), r(3, 4, 'comment')], undefined, undefined, {includeClosingTag: true});
});

test('Fold regions', () => {
Expand All @@ -103,7 +103,7 @@ suite('HTML Folding', () => {
/*2*/'<!-- #endregion -->',
/*3*/'<!-- #endregion -->',
];
assertRanges(input, [r(0, 3, 'region'), r(1, 2, 'region')]);
assertRanges(input, [r(0, 3, 'region'), r(1, 2, 'region')], undefined, undefined, {includeClosingTag: true});
});


Expand All @@ -116,7 +116,7 @@ suite('HTML Folding', () => {
/*3*/'</div>',
/*4*/'</body>',
];
assertRanges(input, [r(0, 3)]);
assertRanges(input, [r(0, 4)], undefined, undefined, {includeClosingTag: true});
});

test('Fold incomplete 2', () => {
Expand All @@ -125,7 +125,7 @@ suite('HTML Folding', () => {
/*1*/'<!-- #endregion -->',
/*2*/'</div>',
];
assertRanges(input, [r(0, 1)]);
assertRanges(input, [r(0, 2)], undefined, undefined, {includeClosingTag: true});
});

test('Fold intersecting region', () => {
Expand All @@ -137,7 +137,7 @@ suite('HTML Folding', () => {
/*4*/'</body>',
/*5*/'<!-- #endregion -->',
];
assertRanges(input, [r(0, 3)]);
assertRanges(input, [r(0, 4)], undefined, undefined, {includeClosingTag: true});
});

test('Fold intersecting region 2', () => {
Expand All @@ -149,7 +149,7 @@ suite('HTML Folding', () => {
/*4*/'<div></div>',
/*5*/'</body>',
];
assertRanges(input, [r(0, 3, 'region')]);
assertRanges(input, [r(0, 3, 'region')], undefined, undefined, {includeClosingTag: true});
});

test('Test limit', () => {
Expand All @@ -176,15 +176,169 @@ suite('HTML Folding', () => {
/*19*/' </span>',
/*20*/'</div>',
];
assertRanges(input, [r(0, 19), r(1, 18), r(2, 3), r(5, 11), r(6, 7), r(9, 10), r(13, 14), r(16, 17)], 'no limit', void 0);
assertRanges(input, [r(0, 19), r(1, 18), r(2, 3), r(5, 11), r(6, 7), r(9, 10), r(13, 14), r(16, 17)], 'limit 8', 8);
assertRanges(input, [r(0, 19), r(1, 18), r(2, 3), r(5, 11), r(6, 7), r(13, 14), r(16, 17)], 'limit 7', 7);
assertRanges(input, [r(0, 19), r(1, 18), r(2, 3), r(5, 11), r(13, 14), r(16, 17)], 'limit 6', 6);
assertRanges(input, [r(0, 19), r(1, 18), r(2, 3), r(5, 11), r(13, 14)], 'limit 5', 5);
assertRanges(input, [r(0, 19), r(1, 18), r(2, 3), r(5, 11)], 'limit 4', 4);
assertRanges(input, [r(0, 19), r(1, 18), r(2, 3)], 'limit 3', 3);
assertRanges(input, [r(0, 19), r(1, 18)], 'limit 2', 2);
assertRanges(input, [r(0, 19)], 'limit 1', 1);
assertRanges(input, [r(0, 20), r(1, 19), r(2, 4), r(5, 12), r(6, 8), r(9, 11), r(13, 15), r(16, 18)], 'no limit', void 0, {includeClosingTag: true});
assertRanges(input, [r(0, 20), r(1, 19), r(2, 4), r(5, 12), r(6, 8), r(9, 11), r(13, 15), r(16, 18)], 'limit 8', 8, {includeClosingTag: true});
assertRanges(input, [r(0, 20), r(1, 19), r(2, 4), r(5, 12), r(6, 8), r(13, 15), r(16, 18)], 'limit 7', 7, {includeClosingTag: true});
assertRanges(input, [r(0, 20), r(1, 19), r(2, 4), r(5, 12), r(13, 15), r(16, 18)], 'limit 6', 6, {includeClosingTag: true});
assertRanges(input, [r(0, 20), r(1, 19), r(2, 4), r(5, 12), r(13, 15)], 'limit 5', 5, {includeClosingTag: true});
assertRanges(input, [r(0, 20), r(1, 19), r(2, 4), r(5, 12)], 'limit 4', 4, {includeClosingTag: true});
assertRanges(input, [r(0, 20), r(1, 19), r(2, 4)], 'limit 3', 3, {includeClosingTag: true});
assertRanges(input, [r(0, 20), r(1, 19)], 'limit 2', 2, {includeClosingTag: true});
assertRanges(input, [r(0, 20)], 'limit 1', 1, {includeClosingTag: true});
});

});

suite('HTML Folding without Closing Tag', () => {
test('Fold one level', () => {
const input = [
/*0*/'<html>',
/*1*/'Hello',
/*2*/'</html>'
];
assertRanges(input, [r(0, 1)], undefined, undefined, {includeClosingTag: false});
});

test('Fold two level', () => {
const input = [
/*0*/'<html>',
/*1*/'<head>',
/*2*/'Hello',
/*3*/'</head>',
/*4*/'</html>'
];
assertRanges(input, [r(0, 3), r(1, 2)], undefined, undefined, {includeClosingTag: false});
});

test('Fold siblings', () => {
const input = [
/*0*/'<html>',
/*1*/'<head>',
/*2*/'Head',
/*3*/'</head>',
/*4*/'<body class="f">',
/*5*/'Body',
/*6*/'</body>',
/*7*/'</html>'
];
assertRanges(input, [r(0, 6), r(1, 2), r(4, 5)], undefined, undefined, {includeClosingTag: false});
});

test('Fold self-closing tags', () => {
const input = [
/*0*/'<div>',
/*1*/'<a href="top"/>',
/*2*/'<img src="s">',
/*3*/'<br/>',
/*4*/'<br>',
/*5*/'<img class="c"',
/*6*/' src="top"',
/*7*/'>',
/*8*/'</div>'
];
assertRanges(input, [r(0, 7), r(5, 6)], undefined, undefined, {includeClosingTag: false});
});

test('Fold comment', () => {
const input = [
/*0*/'<!--',
/*1*/' multi line',
/*2*/'-->',
/*3*/'<!-- some stuff',
/*4*/' some more stuff -->',
];
assertRanges(input, [r(0, 2, 'comment'), r(3, 4, 'comment')], undefined, undefined, {includeClosingTag: false});
});

test('Fold regions', () => {
const input = [
/*0*/'<!-- #region -->',
/*1*/'<!-- #region -->',
/*2*/'<!-- #endregion -->',
/*3*/'<!-- #endregion -->',
];
assertRanges(input, [r(0, 3, 'region'), r(1, 2, 'region')], undefined, undefined, {includeClosingTag: false});
});



test('Fold incomplete', () => {
const input = [
/*0*/'<body>',
/*1*/'<div></div>',
/*2*/'Hello',
/*3*/'</div>',
/*4*/'</body>',
];
assertRanges(input, [r(0, 3)], undefined, undefined, {includeClosingTag: false});
});

test('Fold incomplete 2', () => {
const input = [
/*0*/'<be><div>',
/*1*/'<!-- #endregion -->',
/*2*/'</div>',
];
assertRanges(input, [r(0, 1)], undefined, undefined, {includeClosingTag: false});
});

test('Fold intersecting region', () => {
const input = [
/*0*/'<body>',
/*1*/'<!-- #region -->',
/*2*/'Hello',
/*3*/'<div></div>',
/*4*/'</body>',
/*5*/'<!-- #endregion -->',
];
assertRanges(input, [r(0, 3)], undefined, undefined, {includeClosingTag: false});
});

test('Fold intersecting region 2', () => {
const input = [
/*0*/'<!-- #region -->',
/*1*/'<body>',
/*2*/'Hello',
/*3*/'<!-- #endregion -->',
/*4*/'<div></div>',
/*5*/'</body>',
];
assertRanges(input, [r(0, 3, 'region')], undefined, undefined, {includeClosingTag: false});
});

test('Test limit', () => {
const input = [
/* 0*/'<div>',
/* 1*/' <span>',
/* 2*/' <b>',
/* 3*/' ',
/* 4*/' </b>,',
/* 5*/' <b>',
/* 6*/' <pre>',
/* 7*/' ',
/* 8*/' </pre>,',
/* 9*/' <pre>',
/*10*/' ',
/*11*/' </pre>,',
/*12*/' </b>,',
/*13*/' <b>',
/*14*/' ',
/*15*/' </b>,',
/*16*/' <b>',
/*17*/' ',
/*18*/' </b>',
/*19*/' </span>',
/*20*/'</div>',
];
assertRanges(input, [r(0, 19), r(1, 18), r(2, 3), r(5, 11), r(6, 7), r(9, 10), r(13, 14), r(16, 17)], 'no limit', void 0, {includeClosingTag: false});
assertRanges(input, [r(0, 19), r(1, 18), r(2, 3), r(5, 11), r(6, 7), r(9, 10), r(13, 14), r(16, 17)], 'limit 8', 8, {includeClosingTag: false});
assertRanges(input, [r(0, 19), r(1, 18), r(2, 3), r(5, 11), r(6, 7), r(13, 14), r(16, 17)], 'limit 7', 7, {includeClosingTag: false});
assertRanges(input, [r(0, 19), r(1, 18), r(2, 3), r(5, 11), r(13, 14), r(16, 17)], 'limit 6', 6, {includeClosingTag: false});
assertRanges(input, [r(0, 19), r(1, 18), r(2, 3), r(5, 11), r(13, 14)], 'limit 5', 5, {includeClosingTag: false});
assertRanges(input, [r(0, 19), r(1, 18), r(2, 3), r(5, 11)], 'limit 4', 4, {includeClosingTag: false});
assertRanges(input, [r(0, 19), r(1, 18), r(2, 3)], 'limit 3', 3, {includeClosingTag: false});
assertRanges(input, [r(0, 19), r(1, 18)], 'limit 2', 2, {includeClosingTag: false});
assertRanges(input, [r(0, 19)], 'limit 1', 1, {includeClosingTag: false});
});

});

0 comments on commit 59a19cc

Please sign in to comment.