[json] update service (folding api for #47808)

This commit is contained in:
Martin Aeschlimann 2018-04-16 15:19:47 +02:00
parent d516e62375
commit 6c223050d7
5 changed files with 10 additions and 269 deletions

View file

@ -13,7 +13,7 @@
"dependencies": {
"jsonc-parser": "^2.0.0-next.1",
"request-light": "^0.2.2",
"vscode-json-languageservice": "^3.0.13",
"vscode-json-languageservice": "^3.1.2-next.1",
"vscode-languageserver": "^4.0.0",
"vscode-languageserver-protocol-foldingprovider": "^1.0.1",
"vscode-nls": "^3.2.2",
@ -33,7 +33,6 @@
"install-service-local": "yarn link vscode-json-languageservice",
"install-server-next": "yarn add vscode-languageserver@next",
"install-server-local": "yarn link vscode-languageserver-server",
"test": "npm run compile && ../../../node_modules/.bin/mocha",
"version": "git commit -m \"JSON Language Server $npm_package_version\" package.json"
}
}

View file

@ -1,117 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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, Position, CancellationToken } from 'vscode-languageserver';
import { createScanner, SyntaxKind, ScanError } from 'jsonc-parser';
import { FoldingRangeType, FoldingRange, FoldingRangeList } from 'vscode-languageserver-protocol-foldingprovider';
export function getFoldingRegions(document: TextDocument, maxRanges: number | undefined, cancellationToken: CancellationToken | null) {
let ranges: FoldingRange[] = [];
let nestingLevels: number[] = [];
let stack: FoldingRange[] = [];
let prevStart = -1;
let scanner = createScanner(document.getText(), false);
let token = scanner.scan();
function addRange(range: FoldingRange) {
ranges.push(range);
nestingLevels.push(stack.length);
}
while (token !== SyntaxKind.EOF) {
if (cancellationToken && cancellationToken.isCancellationRequested) {
return null;
}
switch (token) {
case SyntaxKind.OpenBraceToken:
case SyntaxKind.OpenBracketToken: {
let startLine = document.positionAt(scanner.getTokenOffset()).line;
let range = { startLine, endLine: startLine, type: token === SyntaxKind.OpenBraceToken ? 'object' : 'array' };
stack.push(range);
break;
}
case SyntaxKind.CloseBraceToken:
case SyntaxKind.CloseBracketToken: {
let type = token === SyntaxKind.CloseBraceToken ? 'object' : 'array';
if (stack.length > 0 && stack[stack.length - 1].type === type) {
let 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 SyntaxKind.BlockCommentTrivia: {
let startLine = document.positionAt(scanner.getTokenOffset()).line;
let endLine = document.positionAt(scanner.getTokenOffset() + scanner.getTokenLength()).line;
if (scanner.getTokenError() === ScanError.UnexpectedEndOfComment && startLine + 1 < document.lineCount) {
scanner.setPosition(document.offsetAt(Position.create(startLine + 1, 0)));
} else {
if (startLine < endLine) {
addRange({ startLine, endLine, type: FoldingRangeType.Comment });
prevStart = startLine;
}
}
break;
}
case SyntaxKind.LineCommentTrivia: {
let text = document.getText().substr(scanner.getTokenOffset(), scanner.getTokenLength());
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);
} 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();
}
if (maxRanges && ranges.length > maxRanges) {
let counts: number[] = [];
for (let level of nestingLevels) {
if (level < 30) {
counts[level] = (counts[level] || 0) + 1;
}
}
let entries = 0;
let maxLevel = 0;
for (let i = 0; i < counts.length; i++) {
let n = counts[i];
if (n) {
if (n + entries > maxRanges) {
maxLevel = i;
break;
}
entries += n;
}
}
ranges = ranges.filter((r, index) => nestingLevels[index] < maxLevel);
}
return <FoldingRangeList>{ ranges };
}

View file

@ -18,7 +18,6 @@ import { startsWith } from './utils/strings';
import { formatError, runSafe, runSafeAsync } from './utils/runner';
import { JSONDocument, JSONSchema, getLanguageService, DocumentLanguageSettings, SchemaConfiguration } from 'vscode-json-languageservice';
import { getLanguageModelCache } from './languageModelCache';
import { getFoldingRegions } from './jsonFolding';
import { FoldingRangesRequest, FoldingProviderServerCapabilities } from 'vscode-languageserver-protocol-foldingprovider';
@ -369,7 +368,7 @@ connection.onRequest(FoldingRangesRequest.type, (params, token) => {
return runSafe(() => {
let document = documents.get(params.textDocument.uri);
if (document) {
return getFoldingRegions(document, params.maxRanges, token);
return languageService.getFoldingRanges(document, { maxRanges: params.maxRanges });
}
return null;
}, null, `Error while computing folding ranges for ${params.textDocument.uri}`, token);

View file

@ -1,144 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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 '../jsonFolding';
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 actual = getFoldingRegions(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('JSON Folding', () => {
test('Fold one level', () => {
let input = [
/*0*/'{',
/*1*/'"foo":"bar"',
/*2*/'}'
];
assertRanges(input, [r(0, 1, 'object')]);
});
test('Fold two level', () => {
let input = [
/*0*/'[',
/*1*/'{',
/*2*/'"foo":"bar"',
/*3*/'}',
/*4*/']'
];
assertRanges(input, [r(0, 3, 'array'), r(1, 2, 'object')]);
});
test('Fold Arrays', () => {
let input = [
/*0*/'[',
/*1*/'[',
/*2*/'],[',
/*3*/'1',
/*4*/']',
/*5*/']'
];
assertRanges(input, [r(0, 4, 'array'), r(2, 3, 'array')]);
});
test('Filter start on same line', () => {
let input = [
/*0*/'[[',
/*1*/'[',
/*2*/'],[',
/*3*/'1',
/*4*/']',
/*5*/']]'
];
assertRanges(input, [r(0, 4, 'array'), r(2, 3, 'array')]);
});
test('Fold comment', () => {
let input = [
/*0*/'/*',
/*1*/' multi line',
/*2*/'*/',
];
assertRanges(input, [r(0, 2, 'comment')]);
});
test('Incomplete comment', () => {
let input = [
/*0*/'/*',
/*1*/'{',
/*2*/'"foo":"bar"',
/*3*/'}',
];
assertRanges(input, [r(1, 2, 'object')]);
});
test('Fold regions', () => {
let input = [
/*0*/'// #region',
/*1*/'{',
/*2*/'}',
/*3*/'// #endregion',
];
assertRanges(input, [r(0, 3, 'region')]);
});
test('Test limit', () => {
let 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, 'array'), r(1, 18, 'array'), r(2, 3, 'array'), r(5, 11, 'array'), r(6, 7, 'array'), r(9, 10, 'array'), r(13, 14, 'array'), r(16, 17, 'array')], void 0);
assertRanges(input, [r(0, 19, 'array'), r(1, 18, 'array'), r(2, 3, 'array'), r(5, 11, 'array'), r(6, 7, 'array'), r(9, 10, 'array'), r(13, 14, 'array'), r(16, 17, 'array')], 8);
assertRanges(input, [r(0, 19, 'array'), r(1, 18, 'array'), r(2, 3, 'array'), r(5, 11, 'array'), r(13, 14, 'array'), r(16, 17, 'array')], 7);
assertRanges(input, [r(0, 19, 'array'), r(1, 18, 'array'), r(2, 3, 'array'), r(5, 11, 'array'), r(13, 14, 'array'), r(16, 17, 'array')], 6);
assertRanges(input, [r(0, 19, 'array'), r(1, 18, 'array')], 5);
assertRanges(input, [r(0, 19, 'array'), r(1, 18, 'array')], 4);
assertRanges(input, [r(0, 19, 'array'), r(1, 18, 'array')], 3);
assertRanges(input, [r(0, 19, 'array'), r(1, 18, 'array')], 2);
assertRanges(input, [r(0, 19, 'array')], 1);
});
});

View file

@ -52,6 +52,10 @@ https-proxy-agent@2.1.1:
agent-base "^4.1.0"
debug "^3.1.0"
jsonc-parser@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.0.0.tgz#62ff087a7e753875febf3c55f1fc0cd737c36b5a"
jsonc-parser@^2.0.0-next.1:
version "2.0.0-next.1"
resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.0.0-next.1.tgz#445a824f765a96abfbb286d759a9b1d226b18088"
@ -68,11 +72,11 @@ request-light@^0.2.2:
https-proxy-agent "2.1.1"
vscode-nls "^2.0.2"
vscode-json-languageservice@^3.0.13:
version "3.0.13"
resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-3.0.13.tgz#dd1b7ddfd4f5ca16e2a7adcc7d7f9009ac02319a"
vscode-json-languageservice@^3.1.2-next.1:
version "3.1.2-next.1"
resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-3.1.2-next.1.tgz#8acee581c664e2af72ade2f08252aeae9b5918ca"
dependencies:
jsonc-parser "^2.0.0-next.1"
jsonc-parser "^2.0.0"
vscode-languageserver-types "^3.6.1"
vscode-nls "^3.2.1"
vscode-uri "^1.0.3"