[html] update services, add folding for embedded css (for #47808)

This commit is contained in:
Martin Aeschlimann 2018-04-16 22:19:54 +02:00
parent e4c975592b
commit 21ef28c36e
7 changed files with 79 additions and 227 deletions

View file

@ -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",

View file

@ -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<TextDocumentPositionParams, string | null, any, any> = 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);

View file

@ -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: LanguageModelCache<HTMLDocumentRegio
if (typeof (<any>registeredCompletionParticipants[i]).getId === 'function' && (<any>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<HTMLDocumentRegio
let embedded = embeddedCSSDocuments.get(document);
return cssLanguageService.getColorPresentations(embedded, cssStylesheets.get(embedded), color, range);
},
getFoldingRanges(document: TextDocument, range: Range): FoldingRange[] {
let embedded = embeddedCSSDocuments.get(document);
let ranges = cssLanguageService.getFoldingRanges(embedded, {}).ranges;
return ranges.filter(r => r.startLine >= range.start.line && r.endLine < range.end.line);
},
onDocumentRemoved(document: TextDocument) {
embeddedCSSDocuments.onDocumentRemoved(document);
cssStylesheets.onDocumentRemoved(document);

View file

@ -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;
}

View file

@ -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();

View file

@ -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*/'<html>',
/*1*/'Hello',
/*2*/'</html>'
];
assertRanges(input, [r(0, 1)]);
});
test('Fold two level', () => {
let input = [
/*0*/'<html>',
/*1*/'<head>',
/*2*/'Hello',
/*3*/'</head>',
/*4*/'</html>'
];
assertRanges(input, [r(0, 3), r(1, 2)]);
});
test('Fold siblings', () => {
let 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)]);
});
test('Fold self-closing tags', () => {
let 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)]);
});
test('Fold comment', () => {
let input = [
/*0*/'<!--',
/*1*/' multi line',
/*2*/'-->',
/*3*/'<!-- some stuff',
/*4*/' some more stuff -->',
];
assertRanges(input, [r(0, 2, 'comment'), r(3, 4, 'comment')]);
});
test('Fold regions', () => {
let input = [
/*0*/'<!-- #region -->',
/*1*/'<!-- #region -->',
/*2*/'<!-- #endregion -->',
/*3*/'<!-- #endregion -->',
];
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*/'<html>',
/* 1*/'<head>',
/* 2*/'<style>',
/* 3*/' foo {',
/* 4*/' display: block;',
/* 5*/' color: black;',
/* 6*/' }',
/* 7*/'</style>',
/* 8*/'</head>',
/* 9*/'</html>',
];
assertRanges(input, [r(0, 8), r(1, 7), r(2, 6), r(3, 5)]);
});
test('Embedded CSS - multiple areas', () => {
let input = [
/* 0*/'<html>',
/* 1*/'<head style="color:red">',
/* 2*/'<style>',
/* 3*/' /*',
/* 4*/' foo: true,',
/* 5*/' bar: {}',
/* 6*/' */',
/* 7*/'</style>',
/* 8*/'<style>',
/* 9*/' @keyframes mymove {',
/*10*/' from {top: 0px;}',
/*11*/' }',
/*12*/'</style>',
/*13*/'</head>',
/*14*/'</html>',
];
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*/'<html>',
/* 1*/'<head>',
/* 2*/'<style>',
/* 3*/' /* #region Lalala */',
/* 4*/' /* #region*/',
/* 5*/' x = 9;',
/* 6*/' /* #endregion*/',
/* 7*/' /* #endregion Lalala*/',
/* 8*/'</style>',
/* 9*/'</head>',
/*10*/'</html>',
];
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*/'<html>',
@ -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*/'<body>',
/*1*/'<div></div>',
/*2*/'Hello',
/*3*/'</div>',
/*4*/'</body>',
];
assertRanges(input, [r(0, 3)]);
});
test('Fold incomplete 2', () => {
let input = [
/*0*/'<be><div>',
/*1*/'<!-- #endregion -->',
/*2*/'</div>',
];
assertRanges(input, [r(0, 1)]);
});
test('Fold intersecting region', () => {
let input = [
/*0*/'<body>',
/*1*/'<!-- #region -->',
/*2*/'Hello',
/*3*/'<div></div>',
/*4*/'</body>',
/*5*/'<!-- #endregion -->',
];
assertRanges(input, [r(0, 3)]);
});
test('Fold intersecting region 2', () => {
let input = [
/*0*/'<!-- #region -->',
/*1*/'<body>',
/*2*/'Hello',
/*3*/'<!-- #endregion -->',
/*4*/'<div></div>',
/*5*/'</body>',
];
assertRanges(input, [r(0, 3, 'region')]);
});
test('Test limit', () => {
let input = [
/* 0*/'<div>',

View file

@ -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"