From 59a19ccf11fe26ed7e270da663eb4bdec2041be2 Mon Sep 17 00:00:00 2001 From: Alexander Chen Date: Mon, 4 Apr 2022 09:51:18 -0400 Subject: [PATCH] Included end tag in HTML folding range Signed-off-by: Alexander Chen --- src/htmlLanguageService.ts | 4 +- src/htmlLanguageTypes.ts | 6 +- src/services/htmlFolding.ts | 6 +- src/test/folding.test.ts | 200 +++++++++++++++++++++++++++++++----- 4 files changed, 187 insertions(+), 29 deletions(-) diff --git a/src/htmlLanguageService.ts b/src/htmlLanguageService.ts index 8bebc1f..d99f087 100644 --- a/src/htmlLanguageService.ts +++ b/src/htmlLanguageService.ts @@ -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'; @@ -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; diff --git a/src/htmlLanguageTypes.ts b/src/htmlLanguageTypes.ts index 4d6bd02..a900369 100644 --- a/src/htmlLanguageTypes.ts +++ b/src/htmlLanguageTypes.ts @@ -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. */ @@ -319,3 +319,7 @@ export interface FileSystemProvider { stat(uri: DocumentUri): Promise; readDirectory?(uri: DocumentUri): Promise<[string, FileType][]>; } + +export interface FoldingRangeSettings { + includeClosingTag?: boolean; +} diff --git a/src/services/htmlFolding.ts b/src/services/htmlFolding.ts index fca6339..cffe1c4 100644 --- a/src/services/htmlFolding.ts +++ b/src/services/htmlFolding.ts @@ -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'; @@ -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[] = []; @@ -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 }); } diff --git a/src/test/folding.test.ts b/src/test/folding.test.ts index f2309bd..d7612b3 100644 --- a/src/test/folding.test.ts +++ b/src/test/folding.test.ts @@ -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 { @@ -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++) { @@ -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*/'', /*1*/'Hello', /*2*/'' ]; - assertRanges(input, [r(0, 1)]); + assertRanges(input, [r(0, 2)], undefined, undefined, {includeClosingTag: true}); }); test('Fold two level', () => { @@ -53,7 +53,7 @@ suite('HTML Folding', () => { /*3*/'', /*4*/'' ]; - assertRanges(input, [r(0, 3), r(1, 2)]); + assertRanges(input, [r(0, 4), r(1, 3)], undefined, undefined, {includeClosingTag: true}); }); test('Fold siblings', () => { @@ -67,7 +67,7 @@ suite('HTML Folding', () => { /*6*/'', /*7*/'' ]; - 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', () => { @@ -82,7 +82,7 @@ suite('HTML Folding', () => { /*7*/'>', /*8*/'' ]; - assertRanges(input, [r(0, 7), r(5, 6)]); + assertRanges(input, [r(0, 8), r(5, 7)], undefined, undefined, {includeClosingTag: true}); }); test('Fold comment', () => { @@ -93,7 +93,7 @@ suite('HTML Folding', () => { /*3*/'', ]; - 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', () => { @@ -103,7 +103,7 @@ suite('HTML Folding', () => { /*2*/'', /*3*/'', ]; - assertRanges(input, [r(0, 3, 'region'), r(1, 2, 'region')]); + assertRanges(input, [r(0, 3, 'region'), r(1, 2, 'region')], undefined, undefined, {includeClosingTag: true}); }); @@ -116,7 +116,7 @@ suite('HTML Folding', () => { /*3*/'', /*4*/'', ]; - assertRanges(input, [r(0, 3)]); + assertRanges(input, [r(0, 4)], undefined, undefined, {includeClosingTag: true}); }); test('Fold incomplete 2', () => { @@ -125,7 +125,7 @@ suite('HTML Folding', () => { /*1*/'', /*2*/'', ]; - assertRanges(input, [r(0, 1)]); + assertRanges(input, [r(0, 2)], undefined, undefined, {includeClosingTag: true}); }); test('Fold intersecting region', () => { @@ -137,7 +137,7 @@ suite('HTML Folding', () => { /*4*/'', /*5*/'', ]; - assertRanges(input, [r(0, 3)]); + assertRanges(input, [r(0, 4)], undefined, undefined, {includeClosingTag: true}); }); test('Fold intersecting region 2', () => { @@ -149,7 +149,7 @@ suite('HTML Folding', () => { /*4*/'
', /*5*/'', ]; - assertRanges(input, [r(0, 3, 'region')]); + assertRanges(input, [r(0, 3, 'region')], undefined, undefined, {includeClosingTag: true}); }); test('Test limit', () => { @@ -176,15 +176,169 @@ suite('HTML Folding', () => { /*19*/' ', /*20*/'', ]; - 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*/'', + /*1*/'Hello', + /*2*/'' + ]; + assertRanges(input, [r(0, 1)], undefined, undefined, {includeClosingTag: false}); + }); + + test('Fold two level', () => { + const input = [ + /*0*/'', + /*1*/'', + /*2*/'Hello', + /*3*/'', + /*4*/'' + ]; + assertRanges(input, [r(0, 3), r(1, 2)], undefined, undefined, {includeClosingTag: false}); + }); + + test('Fold siblings', () => { + const input = [ + /*0*/'', + /*1*/'', + /*2*/'Head', + /*3*/'', + /*4*/'', + /*5*/'Body', + /*6*/'', + /*7*/'' + ]; + assertRanges(input, [r(0, 6), r(1, 2), r(4, 5)], undefined, undefined, {includeClosingTag: false}); + }); + + test('Fold self-closing tags', () => { + const input = [ + /*0*/'
', + /*1*/'', + /*2*/'', + /*3*/'
', + /*4*/'
', + /*5*/'', + /*8*/'
' + ]; + assertRanges(input, [r(0, 7), r(5, 6)], undefined, undefined, {includeClosingTag: false}); + }); + + test('Fold comment', () => { + const input = [ + /*0*/'', + /*3*/'', + ]; + assertRanges(input, [r(0, 2, 'comment'), r(3, 4, 'comment')], undefined, undefined, {includeClosingTag: false}); + }); + + test('Fold regions', () => { + const input = [ + /*0*/'', + /*1*/'', + /*2*/'', + /*3*/'', + ]; + assertRanges(input, [r(0, 3, 'region'), r(1, 2, 'region')], undefined, undefined, {includeClosingTag: false}); + }); + + + + test('Fold incomplete', () => { + const input = [ + /*0*/'', + /*1*/'
', + /*2*/'Hello', + /*3*/'', + /*4*/'', + ]; + assertRanges(input, [r(0, 3)], undefined, undefined, {includeClosingTag: false}); + }); + + test('Fold incomplete 2', () => { + const input = [ + /*0*/'
', + /*1*/'', + /*2*/'
', + ]; + assertRanges(input, [r(0, 1)], undefined, undefined, {includeClosingTag: false}); + }); + + test('Fold intersecting region', () => { + const input = [ + /*0*/'', + /*1*/'', + /*2*/'Hello', + /*3*/'
', + /*4*/'', + /*5*/'', + ]; + assertRanges(input, [r(0, 3)], undefined, undefined, {includeClosingTag: false}); + }); + + test('Fold intersecting region 2', () => { + const input = [ + /*0*/'', + /*1*/'', + /*2*/'Hello', + /*3*/'', + /*4*/'
', + /*5*/'', + ]; + assertRanges(input, [r(0, 3, 'region')], undefined, undefined, {includeClosingTag: false}); + }); + + test('Test limit', () => { + const input = [ + /* 0*/'
', + /* 1*/' ', + /* 2*/' ', + /* 3*/' ', + /* 4*/' ,', + /* 5*/' ', + /* 6*/'
',
+			/* 7*/'  ',
+			/* 8*/'   
,', + /* 9*/'
',
+			/*10*/'  ',
+			/*11*/'   
,', + /*12*/'
,', + /*13*/' ', + /*14*/' ', + /*15*/' ,', + /*16*/' ', + /*17*/' ', + /*18*/' ', + /*19*/'
', + /*20*/'
', + ]; + 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}); + }); + +}); \ No newline at end of file