From 21ef28c36ea0285235de92b67db344810cd83974 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 16 Apr 2018 22:19:54 +0200 Subject: [PATCH] [html] update services, add folding for embedded css (for #47808) --- .../server/package.json | 4 +- .../server/src/htmlServerMain.ts | 4 +- .../server/src/modes/cssMode.ts | 9 +- .../server/src/modes/htmlFolding.ts | 100 +--------- .../server/src/modes/htmlMode.ts | 5 +- .../server/src/test/folding.test.ts | 172 ++++++------------ .../html-language-features/server/yarn.lock | 12 +- 7 files changed, 79 insertions(+), 227 deletions(-) diff --git a/extensions/html-language-features/server/package.json b/extensions/html-language-features/server/package.json index 2aa9b5c3742d8..839f00984fa07 100644 --- a/extensions/html-language-features/server/package.json +++ b/extensions/html-language-features/server/package.json @@ -8,9 +8,9 @@ "node": "*" }, "dependencies": { - "vscode-css-languageservice": "^3.0.8", + "vscode-css-languageservice": "^3.0.9-next.6", "vscode-emmet-helper": "1.2.5", - "vscode-html-languageservice": "^2.1.2", + "vscode-html-languageservice": "^2.1.3-next.1", "vscode-languageserver": "^4.0.0", "vscode-languageserver-protocol-foldingprovider": "^1.0.1", "vscode-languageserver-types": "^3.6.1", diff --git a/extensions/html-language-features/server/src/htmlServerMain.ts b/extensions/html-language-features/server/src/htmlServerMain.ts index 3f3db9f636178..7d84f226c1645 100644 --- a/extensions/html-language-features/server/src/htmlServerMain.ts +++ b/extensions/html-language-features/server/src/htmlServerMain.ts @@ -21,7 +21,7 @@ import { formatError, runSafe, runSafeAsync } from './utils/runner'; import { doComplete as emmetDoComplete, updateExtensionsPath as updateEmmetExtensionsPath, getEmmetCompletionParticipants } from 'vscode-emmet-helper'; import { FoldingRangesRequest, FoldingProviderServerCapabilities } from 'vscode-languageserver-protocol-foldingprovider'; -import { getFoldingRegions } from './modes/htmlFolding'; +import { getFoldingRanges } from './modes/htmlFolding'; namespace TagCloseRequest { export const type: RequestType = new RequestType('html/tag'); @@ -465,7 +465,7 @@ connection.onRequest(FoldingRangesRequest.type, (params, token) => { return runSafe(() => { let document = documents.get(params.textDocument.uri); if (document) { - return getFoldingRegions(languageModes, document, params.maxRanges, token); + return getFoldingRanges(languageModes, document, params.maxRanges, token); } return null; }, null, `Error while computing folding regions for ${params.textDocument.uri}`, token); diff --git a/extensions/html-language-features/server/src/modes/cssMode.ts b/extensions/html-language-features/server/src/modes/cssMode.ts index 61f00efdbd5c3..4c240754610dc 100644 --- a/extensions/html-language-features/server/src/modes/cssMode.ts +++ b/extensions/html-language-features/server/src/modes/cssMode.ts @@ -6,7 +6,7 @@ import { LanguageModelCache, getLanguageModelCache } from '../languageModelCache'; import { TextDocument, Position, Range, CompletionList } from 'vscode-languageserver-types'; -import { getCSSLanguageService, Stylesheet, ICompletionParticipant } from 'vscode-css-languageservice'; +import { getCSSLanguageService, Stylesheet, ICompletionParticipant, FoldingRange } from 'vscode-css-languageservice'; import { LanguageMode, Workspace } from './languageModes'; import { HTMLDocumentRegions, CSS_STYLE_RULE } from './embeddedSupport'; import { Color } from 'vscode-languageserver'; @@ -37,7 +37,7 @@ export function getCSSMode(documentRegions: LanguageModelCacheregisteredCompletionParticipants[i]).getId === 'function' && (registeredCompletionParticipants[i]).getId() === 'emmet') { const extractedResults = extractAbbreviation(document, position, { lookAhead: false, syntax: 'css' }); if (extractedResults && extractedResults.abbreviation) { - registeredCompletionParticipants[i].onCssProperty({ propertyName: extractedResults.abbreviation, range: extractedResults.abbreviationRange }); + registeredCompletionParticipants[i].onCssProperty!({ propertyName: extractedResults.abbreviation, range: extractedResults.abbreviationRange }); } } else { nonEmmetCompletionParticipants.push(registeredCompletionParticipants[i]); @@ -75,6 +75,11 @@ export function getCSSMode(documentRegions: LanguageModelCache r.startLine >= range.start.line && r.endLine < range.end.line); + }, onDocumentRemoved(document: TextDocument) { embeddedCSSDocuments.onDocumentRemoved(document); cssStylesheets.onDocumentRemoved(document); diff --git a/extensions/html-language-features/server/src/modes/htmlFolding.ts b/extensions/html-language-features/server/src/modes/htmlFolding.ts index e6ce75d8305d2..0b8654f43aa58 100644 --- a/extensions/html-language-features/server/src/modes/htmlFolding.ts +++ b/extensions/html-language-features/server/src/modes/htmlFolding.ts @@ -3,14 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { TextDocument, CancellationToken, Position } from 'vscode-languageserver'; -import { LanguageService as HTMLLanguageService, TokenType, Range } from 'vscode-html-languageservice'; - -import { FoldingRangeType, FoldingRange, FoldingRangeList } from 'vscode-languageserver-protocol-foldingprovider'; +import { TextDocument, CancellationToken, Position, Range } from 'vscode-languageserver'; +import { FoldingRange, FoldingRangeList } from 'vscode-languageserver-protocol-foldingprovider'; import { LanguageModes } from './languageModes'; -import { binarySearch } from '../utils/arrays'; -export function getFoldingRegions(languageModes: LanguageModes, document: TextDocument, maxRanges: number | undefined, cancellationToken: CancellationToken | null): FoldingRangeList { +export function getFoldingRanges(languageModes: LanguageModes, document: TextDocument, maxRanges: number | undefined, cancellationToken: CancellationToken | null): FoldingRangeList { let htmlMode = languageModes.getMode('html'); let range = Range.create(Position.create(0, 0), Position.create(document.lineCount, 0)); let ranges: FoldingRange[] = []; @@ -92,94 +89,3 @@ 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()); - let token = scanner.scan(); - let ranges: FoldingRange[] = []; - let 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: { - let tagName = scanner.getTokenText(); - let 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) { - let stackElement = stack[i]; - stack.length = i; - let line = document.positionAt(scanner.getTokenOffset()).line; - let startLine = stackElement.startLine; - let endLine = line - 1; - if (endLine > startLine && prevStart !== startLine) { - addRange({ startLine, endLine }); - } - } - break; - } - case TokenType.Comment: { - let startLine = document.positionAt(scanner.getTokenOffset()).line; - let text = scanner.getTokenText(); - let 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) { - let stackElement = stack[i]; - stack.length = i; - let endLine = startLine; - startLine = stackElement.startLine; - if (endLine > startLine && prevStart !== startLine) { - addRange({ startLine, endLine, type: FoldingRangeType.Region }); - } - } - } - } else { - let endLine = document.positionAt(scanner.getTokenOffset() + scanner.getTokenLength()).line; - if (startLine < endLine) { - addRange({ startLine, endLine, type: FoldingRangeType.Comment }); - } - } - break; - } - } - token = scanner.scan(); - } - return ranges; -} \ No newline at end of file diff --git a/extensions/html-language-features/server/src/modes/htmlMode.ts b/extensions/html-language-features/server/src/modes/htmlMode.ts index 0ee63c1fbba33..5176fb3eed28c 100644 --- a/extensions/html-language-features/server/src/modes/htmlMode.ts +++ b/extensions/html-language-features/server/src/modes/htmlMode.ts @@ -10,7 +10,6 @@ import { TextDocument, Position, Range, CompletionItem } from 'vscode-languagese import { LanguageMode, Workspace } from './languageModes'; import { FoldingRange } from 'vscode-languageserver-protocol-foldingprovider'; -import { getHTMLFoldingRegions } from './htmlFolding'; import { getPathCompletionParticipant } from './pathCompletion'; export function getHTMLMode(htmlLanguageService: HTMLLanguageService, workspace: Workspace): LanguageMode { @@ -65,9 +64,9 @@ export function getHTMLMode(htmlLanguageService: HTMLLanguageService, workspace: return htmlLanguageService.format(document, range, formatSettings); }, getFoldingRanges(document: TextDocument, range: Range): FoldingRange[] { - return getHTMLFoldingRegions(htmlLanguageService, document, range); + let ranges = htmlLanguageService.getFoldingRanges(document).ranges; + return ranges.filter(r => r.startLine >= range.start.line && r.endLine < range.end.line); }, - doAutoClose(document: TextDocument, position: Position) { let offset = document.offsetAt(position); let text = document.getText(); diff --git a/extensions/html-language-features/server/src/test/folding.test.ts b/extensions/html-language-features/server/src/test/folding.test.ts index c64621a9e07e6..3307fcbd75bc7 100644 --- a/extensions/html-language-features/server/src/test/folding.test.ts +++ b/extensions/html-language-features/server/src/test/folding.test.ts @@ -8,7 +8,7 @@ import 'mocha'; import * as assert from 'assert'; import { TextDocument } from 'vscode-languageserver'; -import { getFoldingRegions } from '../modes/htmlFolding'; +import { getFoldingRanges } from '../modes/htmlFolding'; import { getLanguageModes } from '../modes/languageModes'; interface ExpectedIndentRange { @@ -24,7 +24,7 @@ function assertRanges(lines: string[], expected: ExpectedIndentRange[], message? folders: [{ name: 'foo', uri: 'test://foo' }] }; let languageModes = getLanguageModes({ css: true, javascript: true }, workspace); - let actual = getFoldingRegions(languageModes, document, nRanges, null)!.ranges; + let actual = getFoldingRanges(languageModes, document, nRanges, null)!.ranges; let actualRanges = []; for (let i = 0; i < actual.length; i++) { @@ -39,75 +39,6 @@ function r(startLine: number, endLine: number, type?: string): ExpectedIndentRan } suite('HTML Folding', () => { - test('Fold one level', () => { - let input = [ - /*0*/'', - /*1*/'Hello', - /*2*/'' - ]; - assertRanges(input, [r(0, 1)]); - }); - - test('Fold two level', () => { - let input = [ - /*0*/'', - /*1*/'', - /*2*/'Hello', - /*3*/'', - /*4*/'' - ]; - assertRanges(input, [r(0, 3), r(1, 2)]); - }); - - test('Fold siblings', () => { - let input = [ - /*0*/'', - /*1*/'', - /*2*/'Head', - /*3*/'', - /*4*/'', - /*5*/'Body', - /*6*/'', - /*7*/'' - ]; - assertRanges(input, [r(0, 6), r(1, 2), r(4, 5)]); - }); - - test('Fold self-closing tags', () => { - let input = [ - /*0*/'' - ]; - assertRanges(input, [r(0, 7), r(5, 6)]); - }); - - test('Fold comment', () => { - let input = [ - /*0*/'', - /*3*/'', - ]; - assertRanges(input, [r(0, 2, 'comment'), r(3, 4, 'comment')]); - }); - - test('Fold regions', () => { - let input = [ - /*0*/'', - /*1*/'', - /*2*/'', - /*3*/'', - ]; - assertRanges(input, [r(0, 3, 'region'), r(1, 2, 'region')]); - }); test('Embedded JavaScript', () => { let input = [ @@ -177,6 +108,61 @@ suite('HTML Folding', () => { assertRanges(input, [r(0, 9), r(1, 8), r(2, 7), r(3, 7, 'region'), r(4, 6, 'region')]); }); + test('Embedded CSS', () => { + let input = [ + /* 0*/'', + /* 1*/'', + /* 2*/'', + /* 8*/'', + /* 9*/'', + ]; + assertRanges(input, [r(0, 8), r(1, 7), r(2, 6), r(3, 5)]); + }); + + test('Embedded CSS - multiple areas', () => { + let input = [ + /* 0*/'', + /* 1*/'', + /* 2*/'', + /* 8*/'', + /*13*/'', + /*14*/'', + ]; + assertRanges(input, [r(0, 13), r(1, 12), r(2, 6), r(3, 6, 'comment'), r(8, 11), r(9, 10)]); + }); + + test('Embedded CSS - regions', () => { + let input = [ + /* 0*/'', + /* 1*/'', + /* 2*/'', + /* 9*/'', + /*10*/'', + ]; + assertRanges(input, [r(0, 9), r(1, 8), r(2, 7), r(3, 7, 'region'), r(4, 6, 'region')]); + }); + + // test('Embedded JavaScript - multi line comment', () => { // let input = [ // /* 0*/'', @@ -192,50 +178,6 @@ suite('HTML Folding', () => { // assertRanges(input, [r(0, 7), r(1, 6), r(2, 5), r(3, 5, 'comment')]); // }); - test('Fold incomplete', () => { - let input = [ - /*0*/'', - /*1*/'
', - /*2*/'Hello', - /*3*/'', - /*4*/'', - ]; - assertRanges(input, [r(0, 3)]); - }); - - test('Fold incomplete 2', () => { - let input = [ - /*0*/'
', - /*1*/'', - /*2*/'
', - ]; - assertRanges(input, [r(0, 1)]); - }); - - test('Fold intersecting region', () => { - let input = [ - /*0*/'', - /*1*/'', - /*2*/'Hello', - /*3*/'
', - /*4*/'', - /*5*/'', - ]; - assertRanges(input, [r(0, 3)]); - }); - - test('Fold intersecting region 2', () => { - let input = [ - /*0*/'', - /*1*/'', - /*2*/'Hello', - /*3*/'', - /*4*/'
', - /*5*/'', - ]; - assertRanges(input, [r(0, 3, 'region')]); - }); - test('Test limit', () => { let input = [ /* 0*/'
', diff --git a/extensions/html-language-features/server/yarn.lock b/extensions/html-language-features/server/yarn.lock index 5452722642de3..1c58a8ba9ffa8 100644 --- a/extensions/html-language-features/server/yarn.lock +++ b/extensions/html-language-features/server/yarn.lock @@ -18,9 +18,9 @@ jsonc-parser@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-1.0.0.tgz#ddcc864ae708e60a7a6dd36daea00172fa8d9272" -vscode-css-languageservice@^3.0.8: - version "3.0.8" - resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-3.0.8.tgz#dc27a2f6eefd191bc603be6b9c0a59232a4c2b9f" +vscode-css-languageservice@^3.0.9-next.6: + version "3.0.9-next.6" + resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-3.0.9-next.6.tgz#4da4d25eabb101713f21e8eb60b6042e504fbd97" dependencies: vscode-languageserver-types "^3.6.1" vscode-nls "^3.2.1" @@ -33,9 +33,9 @@ vscode-emmet-helper@1.2.5: jsonc-parser "^1.0.0" vscode-languageserver-types "^3.6.0-next.1" -vscode-html-languageservice@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/vscode-html-languageservice/-/vscode-html-languageservice-2.1.2.tgz#a492d4d3baaa88ce015179f1e985d91eddba9904" +vscode-html-languageservice@^2.1.3-next.1: + version "2.1.3-next.1" + resolved "https://registry.yarnpkg.com/vscode-html-languageservice/-/vscode-html-languageservice-2.1.3-next.1.tgz#bf8a36fc87d10b833211ff7adeb142a06fd18c61" dependencies: vscode-languageserver-types "^3.6.1" vscode-nls "^3.2.1"