diff --git a/extensions/html/package.json b/extensions/html/package.json
index 3b8d0cb9e28..c5e5d53120d 100644
--- a/extensions/html/package.json
+++ b/extensions/html/package.json
@@ -235,8 +235,8 @@
},
"dependencies": {
"vscode-extension-telemetry": "0.0.15",
- "vscode-languageclient": "4.0.0-next.9",
- "vscode-nls": "^3.2.1"
+ "vscode-languageclient": "^4.0.0",
+ "vscode-nls": "^3.2.2"
},
"devDependencies": {
"@types/node": "7.0.43"
diff --git a/extensions/html/server/package.json b/extensions/html/server/package.json
index 0fc64522691..ba166f4017c 100644
--- a/extensions/html/server/package.json
+++ b/extensions/html/server/package.json
@@ -8,13 +8,13 @@
"node": "*"
},
"dependencies": {
- "vscode-css-languageservice": "^3.0.6",
+ "vscode-css-languageservice": "^3.0.8",
"vscode-emmet-helper": "1.2.1",
- "vscode-html-languageservice": "^2.0.17-next.3",
- "vscode-languageserver": "4.0.0-next.4",
- "vscode-languageserver-types": "^3.6.0-next.1",
- "vscode-nls": "^3.2.1",
- "vscode-uri": "^1.0.1"
+ "vscode-html-languageservice": "^2.1.1",
+ "vscode-languageserver": "^4.0.0",
+ "vscode-languageserver-types": "^3.6.1",
+ "vscode-nls": "^3.2.2",
+ "vscode-uri": "^1.0.3"
},
"devDependencies": {
"@types/mocha": "2.2.33",
diff --git a/extensions/html/server/src/htmlServerMain.ts b/extensions/html/server/src/htmlServerMain.ts
index b7e9e502420..c6c20bf78df 100644
--- a/extensions/html/server/src/htmlServerMain.ts
+++ b/extensions/html/server/src/htmlServerMain.ts
@@ -4,14 +4,15 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
-import { createConnection, IConnection, TextDocuments, InitializeParams, InitializeResult, RequestType, DocumentRangeFormattingRequest, Disposable, DocumentSelector, TextDocumentPositionParams, ServerCapabilities, Position, CompletionTriggerKind } from 'vscode-languageserver';
+import {
+ createConnection, IConnection, TextDocuments, InitializeParams, InitializeResult, RequestType,
+ DocumentRangeFormattingRequest, Disposable, DocumentSelector, TextDocumentPositionParams, ServerCapabilities,
+ Position, CompletionTriggerKind, ConfigurationRequest, ConfigurationParams, DidChangeWorkspaceFoldersNotification,
+ WorkspaceFolder, DocumentColorRequest, ColorInformation, ColorPresentationRequest
+} from 'vscode-languageserver';
import { TextDocument, Diagnostic, DocumentLink, SymbolInformation, CompletionList } from 'vscode-languageserver-types';
import { getLanguageModes, LanguageModes, Settings } from './modes/languageModes';
-import { ConfigurationRequest, ConfigurationParams } from 'vscode-languageserver-protocol/lib/protocol.configuration.proposed';
-import { DocumentColorRequest, ServerCapabilities as CPServerCapabilities, ColorInformation, ColorPresentationRequest } from 'vscode-languageserver-protocol/lib/protocol.colorProvider.proposed';
-import { DidChangeWorkspaceFoldersNotification, WorkspaceFolder } from 'vscode-languageserver-protocol/lib/protocol.workspaceFolders.proposed';
-
import { format } from './modes/formatting';
import { pushAll } from './utils/arrays';
import { getDocumentContext } from './utils/documentContext';
@@ -21,6 +22,7 @@ import { doComplete as emmetDoComplete, updateExtensionsPath as updateEmmetExten
import { getPathCompletionParticipant } from './modes/pathCompletion';
import { FoldingRangesRequest, FoldingProviderServerCapabilities } from './protocol/foldingProvider.proposed';
+import { getFoldingRegions } from './modes/htmlFolding';
namespace TagCloseRequest {
export const type: RequestType = new RequestType('html/tag');
@@ -113,7 +115,7 @@ connection.onInitialize((params: InitializeParams): InitializeResult => {
clientDynamicRegisterSupport = hasClientCapability('workspace', 'symbol', 'dynamicRegistration');
scopedSettingsSupport = hasClientCapability('workspace', 'configuration');
workspaceFoldersSupport = hasClientCapability('workspace', 'workspaceFolders');
- let capabilities: ServerCapabilities & CPServerCapabilities & FoldingProviderServerCapabilities = {
+ let capabilities: ServerCapabilities & FoldingProviderServerCapabilities = {
// Tell the client that the server works in FULL text document sync mode
textDocumentSync: documents.syncKind,
completionProvider: clientSnippetSupport ? { resolveProvider: true, triggerCharacters: [...emmetTriggerCharacters, '.', ':', '<', '"', '=', '/'] } : undefined,
@@ -469,15 +471,11 @@ connection.onRequest(TagCloseRequest.type, params => {
}, null, `Error while computing tag close actions for ${params.textDocument.uri}`);
});
-connection.onRequest(FoldingRangesRequest.type, params => {
+connection.onRequest(FoldingRangesRequest.type, (params, token) => {
return runSafe(() => {
let document = documents.get(params.textDocument.uri);
if (document) {
- let mode = languageModes.getMode('html');
- if (mode && mode.getFoldingRanges) {
- return mode.getFoldingRanges(document);
- }
- return null;
+ return getFoldingRegions(languageModes, document, params.maxRanges, token);
}
return null;
}, null, `Error while computing folding regions for ${params.textDocument.uri}`);
diff --git a/extensions/html/server/src/modes/cssMode.ts b/extensions/html/server/src/modes/cssMode.ts
index 13f41c01fdc..08f823233d3 100644
--- a/extensions/html/server/src/modes/cssMode.ts
+++ b/extensions/html/server/src/modes/cssMode.ts
@@ -9,7 +9,7 @@ import { TextDocument, Position, Range } from 'vscode-languageserver-types';
import { getCSSLanguageService, Stylesheet, ICompletionParticipant } from 'vscode-css-languageservice';
import { LanguageMode, Settings } from './languageModes';
import { HTMLDocumentRegions, CSS_STYLE_RULE } from './embeddedSupport';
-import { Color } from 'vscode-languageserver-protocol/lib/protocol.colorProvider.proposed';
+import { Color } from 'vscode-languageserver';
import { extractAbbreviation } from 'vscode-emmet-helper';
export function getCSSMode(documentRegions: LanguageModelCache): LanguageMode {
diff --git a/extensions/html/server/src/modes/htmlFolding.ts b/extensions/html/server/src/modes/htmlFolding.ts
new file mode 100644
index 00000000000..2a16b020b45
--- /dev/null
+++ b/extensions/html/server/src/modes/htmlFolding.ts
@@ -0,0 +1,170 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * 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 '../protocol/foldingProvider.proposed';
+import { LanguageModes } from './languageModes';
+
+export function getFoldingRegions(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[] = [];
+ if (htmlMode && htmlMode.getFoldingRanges) {
+ ranges.push(...htmlMode.getFoldingRanges(document, range));
+ }
+ let modeRanges = languageModes.getModesInRange(document, range);
+ for (let modeRange of modeRanges) {
+ let mode = modeRange.mode;
+ if (mode && mode !== htmlMode && mode.getFoldingRanges && !modeRange.attributeValue) {
+ ranges.push(...mode.getFoldingRanges(document, modeRange));
+ }
+ }
+ if (maxRanges && ranges.length > maxRanges) {
+ ranges = limitRanges(ranges, maxRanges);
+ }
+ return { ranges };
+}
+
+function limitRanges(ranges: FoldingRange[], maxRanges: number) {
+ ranges = ranges.sort((r1, r2) => {
+ let diff = r1.startLine - r2.startLine;
+ if (diff === 0) {
+ diff = r1.endLine - r2.endLine;
+ }
+ return diff;
+ });
+
+ // compute each range's nesting level in 'nestingLevels'.
+ // count the number of ranges for each level in 'nestingLevelCounts'
+ let top: FoldingRange | null = null;
+ let previous: FoldingRange[] = [];
+ let nestingLevels: number[] = [];
+ let nestingLevelCounts: number[] = [];
+
+ let setNestingLevel = (level: number) => {
+ nestingLevels.push(level);
+ if (level < 30) {
+ nestingLevelCounts[level] = (nestingLevelCounts[level] || 0) + 1;
+ }
+ };
+
+ for (let i = 0; i < ranges.length; i++) {
+ let entry = ranges[i];
+ if (!top) {
+ top = entry;
+ setNestingLevel(0);
+ } else {
+ if (entry.startLine > top.startLine) {
+ if (entry.endLine <= top.endLine) {
+ previous.push(top);
+ top = entry;
+ setNestingLevel(previous.length);
+ } else if (entry.startLine > top.startLine) {
+ do {
+ top = previous.pop();
+ } while (top && entry.startLine > top.endLine);
+ previous.push(top);
+ top = entry;
+ setNestingLevel(previous.length);
+ }
+ }
+ }
+ }
+ let entries = 0;
+ let maxLevel = 0;
+ for (let i = 0; i < nestingLevels.length; i++) {
+ let n = nestingLevels[i];
+ if (n) {
+ if (n + entries > maxRanges) {
+ maxLevel = i;
+ break;
+ }
+ entries += n;
+ }
+ }
+ return ranges.filter((r, index) => (typeof nestingLevels[index] === 'number') && nestingLevels[index] < maxLevel);
+}
+
+
+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: FoldingRange[] = [];
+ let elementNames: string[] = [];
+ let lastTagName = null;
+ let prevStart = -1;
+
+ function addRange(range: FoldingRange) {
+ ranges.push(range);
+ }
+
+ while (token !== TokenType.EOS) {
+ switch (token) {
+ case TokenType.StartTagOpen: {
+ let startLine = document.positionAt(scanner.getTokenOffset()).line;
+ let range = { startLine, endLine: startLine };
+ stack.push(range);
+ break;
+ }
+ case TokenType.StartTag: {
+ lastTagName = scanner.getTokenText();
+ elementNames.push(lastTagName);
+ break;
+ }
+ case TokenType.EndTag: {
+ lastTagName = scanner.getTokenText();
+ break;
+ }
+ case TokenType.EndTagClose:
+ case TokenType.StartTagSelfClose: {
+ let name = elementNames.pop();
+ let range = stack.pop();
+ while (name && name !== lastTagName) {
+ name = elementNames.pop();
+ range = stack.pop();
+ }
+ let line = document.positionAt(scanner.getTokenOffset()).line;
+ if (range && line > range.startLine + 1 && prevStart !== range.startLine) {
+ range.endLine = line - 1;
+ addRange(range);
+ prevStart = range.startLine;
+ }
+ break;
+ }
+ case TokenType.Comment: {
+ let text = scanner.getTokenText();
+ let m = text.match(/^\s*#(region\b)|(endregion\b)/);
+ if (m) {
+ let line = document.positionAt(scanner.getTokenOffset()).line;
+ if (m[1]) { // start pattern match
+ let range = { startLine: line, endLine: line, type: FoldingRangeType.Region };
+ stack.push(range);
+ elementNames.push('');
+ } else {
+ let i = stack.length - 1;
+ while (i >= 0 && stack[i].type !== FoldingRangeType.Region) {
+ i--;
+ }
+ if (i >= 0) {
+ let range = stack[i];
+ stack.length = i;
+ if (line > range.startLine && prevStart !== range.startLine) {
+ range.endLine = line;
+ addRange(range);
+ prevStart = range.startLine;
+ }
+ }
+ }
+ }
+ break;
+ }
+ }
+ token = scanner.scan();
+ }
+ return ranges;
+}
\ No newline at end of file
diff --git a/extensions/html/server/src/modes/htmlMode.ts b/extensions/html/server/src/modes/htmlMode.ts
index aa3fceeeb2d..b09e0c2a17f 100644
--- a/extensions/html/server/src/modes/htmlMode.ts
+++ b/extensions/html/server/src/modes/htmlMode.ts
@@ -5,11 +5,12 @@
'use strict';
import { getLanguageModelCache } from '../languageModelCache';
-import { LanguageService as HTMLLanguageService, HTMLDocument, DocumentContext, FormattingOptions, HTMLFormatConfiguration, TokenType } from 'vscode-html-languageservice';
+import { LanguageService as HTMLLanguageService, HTMLDocument, DocumentContext, FormattingOptions, HTMLFormatConfiguration } from 'vscode-html-languageservice';
import { TextDocument, Position, Range } from 'vscode-languageserver-types';
import { LanguageMode, Settings } from './languageModes';
-import { FoldingRangeType, FoldingRange, FoldingRangeList } from '../protocol/foldingProvider.proposed';
+import { FoldingRange } from '../protocol/foldingProvider.proposed';
+import { getHTMLFoldingRegions } from './htmlFolding';
export function getHTMLMode(htmlLanguageService: HTMLLanguageService): LanguageMode {
let globalSettings: Settings = {};
@@ -67,78 +68,8 @@ export function getHTMLMode(htmlLanguageService: HTMLLanguageService): LanguageM
formatSettings = merge(formatParams, formatSettings);
return htmlLanguageService.format(document, range, formatSettings);
},
- getFoldingRanges(document: TextDocument): FoldingRangeList {
- const scanner = htmlLanguageService.createScanner(document.getText());
- let token = scanner.scan();
- let ranges: FoldingRange[] = [];
- let stack: FoldingRange[] = [];
- let elementNames: string[] = [];
- let lastTagName = null;
- let prevStart = -1;
- while (token !== TokenType.EOS) {
- switch (token) {
- case TokenType.StartTagOpen: {
- let startLine = document.positionAt(scanner.getTokenOffset()).line;
- let range = { startLine, endLine: startLine };
- stack.push(range);
- break;
- }
- case TokenType.StartTag: {
- lastTagName = scanner.getTokenText();
- elementNames.push(lastTagName);
- break;
- }
- case TokenType.EndTag: {
- lastTagName = scanner.getTokenText();
- break;
- }
- case TokenType.EndTagClose:
- case TokenType.StartTagSelfClose: {
- let name = elementNames.pop();
- let range = stack.pop();
- while (name && name !== lastTagName) {
- name = elementNames.pop();
- range = stack.pop();
- }
- let line = document.positionAt(scanner.getTokenOffset()).line;
- if (range && line > range.startLine + 1 && prevStart !== range.startLine) {
- range.endLine = line - 1;
- ranges.push(range);
- prevStart = range.startLine;
- }
- break;
- }
- case TokenType.Comment: {
- let text = scanner.getTokenText();
- let m = text.match(/^\s*#(region\b)|(endregion\b)/);
- if (m) {
- let line = document.positionAt(scanner.getTokenOffset()).line;
- if (m[1]) { // start pattern match
- let range = { startLine: line, endLine: line, type: FoldingRangeType.Region };
- stack.push(range);
- elementNames.push('');
- } else {
- let i = stack.length - 1;
- while (i >= 0 && stack[i].type !== FoldingRangeType.Region) {
- i--;
- }
- if (i >= 0) {
- let range = stack[i];
- stack.length = i;
- if (line > range.startLine && prevStart !== range.startLine) {
- range.endLine = line;
- ranges.push(range);
- prevStart = range.startLine;
- }
- }
- }
- }
- break;
- }
- }
- token = scanner.scan();
- }
- return { ranges };
+ getFoldingRanges(document: TextDocument, range: Range): FoldingRange[] {
+ return getHTMLFoldingRegions(htmlLanguageService, document, range);
},
doAutoClose(document: TextDocument, position: Position) {
diff --git a/extensions/html/server/src/modes/languageModes.ts b/extensions/html/server/src/modes/languageModes.ts
index 1f527b9b2fa..6378680dbb8 100644
--- a/extensions/html/server/src/modes/languageModes.ts
+++ b/extensions/html/server/src/modes/languageModes.ts
@@ -9,8 +9,8 @@ import {
CompletionItem, Location, SignatureHelp, Definition, TextEdit, TextDocument, Diagnostic, DocumentLink, Range,
Hover, DocumentHighlight, CompletionList, Position, FormattingOptions, SymbolInformation
} from 'vscode-languageserver-types';
-
-import { ColorInformation, ColorPresentation, Color } from 'vscode-languageserver-protocol/lib/protocol.colorProvider.proposed';
+import { ColorInformation, ColorPresentation, Color } from 'vscode-languageserver';
+import { FoldingRange } from '../protocol/foldingProvider.proposed';
import { getLanguageModelCache, LanguageModelCache } from '../languageModelCache';
import { getDocumentRegions, HTMLDocumentRegions } from './embeddedSupport';
@@ -20,8 +20,6 @@ import { getHTMLMode } from './htmlMode';
export { ColorInformation, ColorPresentation, Color };
-import { FoldingRangeList } from '../protocol/foldingProvider.proposed';
-
export interface Settings {
css?: any;
html?: any;
@@ -51,7 +49,7 @@ export interface LanguageMode {
findDocumentColors?: (document: TextDocument) => ColorInformation[];
getColorPresentations?: (document: TextDocument, color: Color, range: Range) => ColorPresentation[];
doAutoClose?: (document: TextDocument, position: Position) => string | null;
- getFoldingRanges?: (document: TextDocument) => FoldingRangeList | null;
+ getFoldingRanges?: (document: TextDocument, range: Range) => FoldingRange[];
onDocumentRemoved(document: TextDocument): void;
dispose(): void;
}
diff --git a/extensions/html/server/src/modes/pathCompletion.ts b/extensions/html/server/src/modes/pathCompletion.ts
index 46b5d34fd91..9b2ccbbd8ca 100644
--- a/extensions/html/server/src/modes/pathCompletion.ts
+++ b/extensions/html/server/src/modes/pathCompletion.ts
@@ -5,17 +5,17 @@
'use strict';
import { TextDocument, CompletionList, CompletionItemKind, CompletionItem, TextEdit, Range, Position } from 'vscode-languageserver-types';
-import { Proposed } from 'vscode-languageserver-protocol';
+import { WorkspaceFolder } from 'vscode-languageserver';
import * as path from 'path';
import * as fs from 'fs';
import URI from 'vscode-uri';
-import { ICompletionParticipant } from 'vscode-html-languageservice/lib/htmlLanguageService';
+import { ICompletionParticipant } from 'vscode-html-languageservice';
import { startsWith } from '../utils/strings';
import { contains } from '../utils/arrays';
export function getPathCompletionParticipant(
document: TextDocument,
- workspaceFolders: Proposed.WorkspaceFolder[] | undefined,
+ workspaceFolders: WorkspaceFolder[] | undefined,
result: CompletionList
): ICompletionParticipant {
return {
@@ -104,7 +104,7 @@ const isDir = (p: string) => {
return fs.statSync(p).isDirectory();
};
-function resolveWorkspaceRoot(activeDoc: TextDocument, workspaceFolders: Proposed.WorkspaceFolder[]): string | undefined {
+function resolveWorkspaceRoot(activeDoc: TextDocument, workspaceFolders: WorkspaceFolder[]): string | undefined {
for (let i = 0; i < workspaceFolders.length; i++) {
if (startsWith(activeDoc.uri, workspaceFolders[i].uri)) {
return path.resolve(URI.parse(workspaceFolders[i].uri).fsPath);
diff --git a/extensions/html/server/src/test/completions.test.ts b/extensions/html/server/src/test/completions.test.ts
index 1777c039cd5..645727c8c6f 100644
--- a/extensions/html/server/src/test/completions.test.ts
+++ b/extensions/html/server/src/test/completions.test.ts
@@ -12,7 +12,7 @@ import { TextDocument, CompletionList, CompletionItemKind, } from 'vscode-langua
import { getLanguageModes } from '../modes/languageModes';
import { applyEdits } from '../utils/edits';
import { getPathCompletionParticipant } from '../modes/pathCompletion';
-import { Proposed } from 'vscode-languageserver-protocol';
+import { WorkspaceFolder } from 'vscode-languageserver';
export interface ItemDescription {
label: string;
@@ -49,7 +49,7 @@ suite('Completions', () => {
const testUri = 'test://test/test.html';
- function assertCompletions(value: string, expected: { count?: number, items?: ItemDescription[] }, uri = testUri, workspaceFolders?: Proposed.WorkspaceFolder[]): void {
+ function assertCompletions(value: string, expected: { count?: number, items?: ItemDescription[] }, uri = testUri, workspaceFolders?: WorkspaceFolder[]): void {
let offset = value.indexOf('|');
value = value.substr(0, offset) + value.substr(offset + 1);
diff --git a/extensions/html/server/src/test/folding.test.ts b/extensions/html/server/src/test/folding.test.ts
new file mode 100644
index 00000000000..45407529efb
--- /dev/null
+++ b/extensions/html/server/src/test/folding.test.ts
@@ -0,0 +1,140 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+'use strict';
+
+import 'mocha';
+import * as assert from 'assert';
+import { TextDocument } from 'vscode-languageserver';
+import { getFoldingRegions } from '../modes/htmlFolding';
+import { getLanguageModes } from '../modes/languageModes';
+
+interface ExpectedIndentRange {
+ startLine: number;
+ endLine: number;
+ type?: string;
+}
+
+function assertRanges(lines: string[], expected: ExpectedIndentRange[], nRanges?: number): void {
+ let document = TextDocument.create('test://foo/bar.json', 'json', 1, lines.join('\n'));
+ let languageModes = getLanguageModes({ css: true, javascript: true });
+ let actual = getFoldingRegions(languageModes, document, nRanges, null)!.ranges;
+
+ let actualRanges = [];
+ for (let i = 0; i < actual.length; i++) {
+ actualRanges[i] = r(actual[i].startLine, actual[i].endLine, actual[i].type);
+ }
+ actualRanges = actualRanges.sort((r1, r2) => r1.startLine - r2.startLine);
+ assert.deepEqual(actualRanges, expected);
+}
+
+function r(startLine: number, endLine: number, type?: string): ExpectedIndentRange {
+ return { startLine, endLine, type };
+}
+
+suite('Object 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*/'