From 52c21cdc6fbd4680396c14df5922b32f7f133aae Mon Sep 17 00:00:00 2001 From: yoyo930021 Date: Fri, 4 Sep 2020 20:31:01 +0800 Subject: [PATCH 1/3] Add html folding feature --- server/src/modes/template/htmlMode.ts | 5 + server/src/modes/template/index.ts | 3 + .../modes/template/services/htmlFolding.ts | 91 +++++++++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 server/src/modes/template/services/htmlFolding.ts diff --git a/server/src/modes/template/htmlMode.ts b/server/src/modes/template/htmlMode.ts index a8f70a1222..45fe8143dc 100644 --- a/server/src/modes/template/htmlMode.ts +++ b/server/src/modes/template/htmlMode.ts @@ -25,6 +25,7 @@ import { VueInfoService } from '../../services/vueInfoService'; import { getComponentInfoTagProvider } from './tagProviders/componentInfoTagProvider'; import { VueVersion } from '../../services/typescriptService/vueVersion'; import { doPropValidation } from './services/vuePropValidation'; +import { getFoldingRanges } from './services/htmlFolding'; export class HTMLMode implements LanguageMode { private tagProviderSettings: CompletionConfiguration; @@ -108,6 +109,10 @@ export class HTMLMode implements LanguageMode { const info = this.vueInfoService ? this.vueInfoService.getInfo(document) : undefined; return findDefinition(embedded, position, this.vueDocuments.refreshAndGet(embedded), info); } + getFoldingRanges(document: TextDocument) { + const embedded = this.embeddedDocuments.refreshAndGet(document); + return getFoldingRanges(embedded); + } onDocumentRemoved(document: TextDocument) { this.vueDocuments.onDocumentRemoved(document); } diff --git a/server/src/modes/template/index.ts b/server/src/modes/template/index.ts index 2ef204ff15..0722031709 100644 --- a/server/src/modes/template/index.ts +++ b/server/src/modes/template/index.ts @@ -86,6 +86,9 @@ export class VueHTMLMode implements LanguageMode { ? interpolationDefinition : this.htmlMode.findDefinition(document, position); } + getFoldingRanges(document: TextDocument) { + return this.htmlMode.getFoldingRanges(document); + } onDocumentRemoved(document: TextDocument) { this.htmlMode.onDocumentRemoved(document); } diff --git a/server/src/modes/template/services/htmlFolding.ts b/server/src/modes/template/services/htmlFolding.ts new file mode 100644 index 0000000000..6294d02fd8 --- /dev/null +++ b/server/src/modes/template/services/htmlFolding.ts @@ -0,0 +1,91 @@ +import { TextDocument, FoldingRange, FoldingRangeKind } from 'vscode-languageserver-types'; + +import { TokenType, createScanner } from '../parser/htmlScanner'; +import { isEmptyElement } from '../tagProviders/htmlTags'; + + +export function getFoldingRanges(document: TextDocument): FoldingRange[] { + const scanner = createScanner(document.getText()); + let token = scanner.scan(); + const ranges: FoldingRange[] = []; + const stack: { startLine: number, tagName: string }[] = []; + let lastTagName = null; + let prevStart = -1; + + function addRange(range: FoldingRange) { + ranges.push(range); + prevStart = range.startLine; + } + + while (token !== TokenType.EOS) { + switch (token) { + case TokenType.StartTag: { + const tagName = scanner.getTokenText(); + const startLine = document.positionAt(scanner.getTokenOffset()).line; + stack.push({ startLine, tagName }); + lastTagName = tagName; + break; + } + case TokenType.EndTag: { + lastTagName = scanner.getTokenText(); + break; + } + case TokenType.StartTagClose: + if (!lastTagName || !isEmptyElement(lastTagName)) { + break; + } + // fallthrough + case TokenType.EndTagClose: + case TokenType.StartTagSelfClose: { + let i = stack.length - 1; + while (i >= 0 && stack[i].tagName !== lastTagName) { + i--; + } + if (i >= 0) { + const stackElement = stack[i]; + stack.length = i; + const line = document.positionAt(scanner.getTokenOffset()).line; + const startLine = stackElement.startLine; + const endLine = line - 1; + if (endLine > startLine && prevStart !== startLine) { + addRange({ startLine, endLine }); + } + } + break; + } + case TokenType.Comment: { + let startLine = document.positionAt(scanner.getTokenOffset()).line; + const text = scanner.getTokenText(); + const m = text.match(/^\s*#(region\b)|(endregion\b)/); + if (m) { + if (m[1]) { // start pattern match + stack.push({ startLine, tagName: '' }); // empty tagName marks region + } else { + let i = stack.length - 1; + while (i >= 0 && stack[i].tagName.length) { + i--; + } + if (i >= 0) { + const stackElement = stack[i]; + stack.length = i; + const endLine = startLine; + startLine = stackElement.startLine; + if (endLine > startLine && prevStart !== startLine) { + addRange({ startLine, endLine, kind: FoldingRangeKind.Region }); + } + } + } + } else { + const endLine = document.positionAt(scanner.getTokenOffset() + scanner.getTokenLength()).line; + if (startLine < endLine) { + addRange({ startLine, endLine, kind: FoldingRangeKind.Comment }); + } + } + break; + } + } + token = scanner.scan(); + } + + return ranges; +} From 9de4f1e906d6f5f906f730c0778fdfb4218417c4 Mon Sep 17 00:00:00 2001 From: yoyo930021 Date: Fri, 4 Sep 2020 20:31:48 +0800 Subject: [PATCH 2/3] Rename isEmptyElement to isVoidElement --- server/src/modes/template/parser/htmlParser.ts | 4 ++-- server/src/modes/template/services/htmlFolding.ts | 10 +++++----- server/src/modes/template/tagProviders/htmlTags.ts | 7 ++++--- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/server/src/modes/template/parser/htmlParser.ts b/server/src/modes/template/parser/htmlParser.ts index 0dfa4115e5..ba72268589 100644 --- a/server/src/modes/template/parser/htmlParser.ts +++ b/server/src/modes/template/parser/htmlParser.ts @@ -1,5 +1,5 @@ import { TokenType, createScanner } from './htmlScanner'; -import { isEmptyElement } from '../tagProviders/htmlTags'; +import { isVoidElement } from '../tagProviders/htmlTags'; import { TextDocument } from 'vscode-languageserver-types'; export class Node { @@ -90,7 +90,7 @@ export function parse(text: string): HTMLDocument { break; case TokenType.StartTagClose: curr.end = scanner.getTokenEnd(); // might be later set to end tag position - if (isEmptyElement(curr.tag) && curr !== htmlDocument) { + if (isVoidElement(curr.tag) && curr !== htmlDocument) { curr.closed = true; curr = curr.parent; } diff --git a/server/src/modes/template/services/htmlFolding.ts b/server/src/modes/template/services/htmlFolding.ts index 6294d02fd8..1c1bc49212 100644 --- a/server/src/modes/template/services/htmlFolding.ts +++ b/server/src/modes/template/services/htmlFolding.ts @@ -1,14 +1,13 @@ import { TextDocument, FoldingRange, FoldingRangeKind } from 'vscode-languageserver-types'; import { TokenType, createScanner } from '../parser/htmlScanner'; -import { isEmptyElement } from '../tagProviders/htmlTags'; - +import { isVoidElement } from '../tagProviders/htmlTags'; export function getFoldingRanges(document: TextDocument): FoldingRange[] { const scanner = createScanner(document.getText()); let token = scanner.scan(); const ranges: FoldingRange[] = []; - const stack: { startLine: number, tagName: string }[] = []; + const stack: { startLine: number; tagName: string }[] = []; let lastTagName = null; let prevStart = -1; @@ -31,7 +30,7 @@ export function getFoldingRanges(document: TextDocument): FoldingRange[] { break; } case TokenType.StartTagClose: - if (!lastTagName || !isEmptyElement(lastTagName)) { + if (!lastTagName || !isVoidElement(lastTagName)) { break; } // fallthrough @@ -58,7 +57,8 @@ export function getFoldingRanges(document: TextDocument): FoldingRange[] { const text = scanner.getTokenText(); const m = text.match(/^\s*#(region\b)|(endregion\b)/); if (m) { - if (m[1]) { // start pattern match + if (m[1]) { + // start pattern match stack.push({ startLine, tagName: '' }); // empty tagName marks region } else { let i = stack.length - 1; diff --git a/server/src/modes/template/tagProviders/htmlTags.ts b/server/src/modes/template/tagProviders/htmlTags.ts index 89cc70e659..31953b6691 100644 --- a/server/src/modes/template/tagProviders/htmlTags.ts +++ b/server/src/modes/template/tagProviders/htmlTags.ts @@ -33,7 +33,8 @@ import { } from './common'; import { MarkupContent } from 'vscode-languageserver-types'; -export const EMPTY_ELEMENTS: string[] = [ +// As defined in https://www.w3.org/TR/html5/syntax.html#void-elements +export const VOID_ELEMENTS: string[] = [ 'area', 'base', 'br', @@ -52,8 +53,8 @@ export const EMPTY_ELEMENTS: string[] = [ 'wbr' ]; -export function isEmptyElement(e: string | undefined): boolean { - return !!e && binarySearch(EMPTY_ELEMENTS, e.toLowerCase(), (s1: string, s2: string) => s1.localeCompare(s2)) >= 0; +export function isVoidElement(e: string | undefined): boolean { + return !!e && binarySearch(VOID_ELEMENTS, e.toLowerCase(), (s1: string, s2: string) => s1.localeCompare(s2)) >= 0; } function genAttr(attrString: string) { From 1eb657de9dca8fd471c61cc02ab14e730f9bb0f7 Mon Sep 17 00:00:00 2001 From: Pine Wu Date: Sat, 5 Sep 2020 00:05:16 +0800 Subject: [PATCH 3/3] Add copyright --- server/src/modes/template/services/htmlFolding.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server/src/modes/template/services/htmlFolding.ts b/server/src/modes/template/services/htmlFolding.ts index 1c1bc49212..050fcbf455 100644 --- a/server/src/modes/template/services/htmlFolding.ts +++ b/server/src/modes/template/services/htmlFolding.ts @@ -1,3 +1,8 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + import { TextDocument, FoldingRange, FoldingRangeKind } from 'vscode-languageserver-types'; import { TokenType, createScanner } from '../parser/htmlScanner';