From baed6fa95b3dc28be11ccb25de4102ad58c8e34c Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 15 Sep 2017 16:39:44 +0200 Subject: [PATCH] [html] multi folder settings optimizations --- extensions/json/client/src/jsonMain.ts | 78 +++++++++++++------- extensions/json/client/src/utils/hash.ts | 59 +++++++++++++++ extensions/json/server/npm-shrinkwrap.json | 4 +- extensions/json/server/package.json | 2 +- extensions/json/server/src/jsonServerMain.ts | 14 +--- 5 files changed, 116 insertions(+), 41 deletions(-) create mode 100644 extensions/json/client/src/utils/hash.ts diff --git a/extensions/json/client/src/jsonMain.ts b/extensions/json/client/src/jsonMain.ts index 026beb78a74..bb933b6a929 100644 --- a/extensions/json/client/src/jsonMain.ts +++ b/extensions/json/client/src/jsonMain.ts @@ -10,11 +10,11 @@ import { workspace, languages, ExtensionContext, extensions, Uri, TextDocument, import { LanguageClient, LanguageClientOptions, RequestType, ServerOptions, TransportKind, NotificationType, DidChangeConfigurationNotification } from 'vscode-languageclient'; import TelemetryReporter from 'vscode-extension-telemetry'; import { ConfigurationFeature } from 'vscode-languageclient/lib/proposed'; - import { DocumentColorRequest } from 'vscode-languageserver-protocol/lib/protocol.colorProvider.proposed'; import * as nls from 'vscode-nls'; +import { hash } from './utils/hash'; let localize = nls.loadMessageBundle(); namespace VSCodeContentRequest { @@ -192,8 +192,6 @@ function getSettings(): Settings { let httpSettings = workspace.getConfiguration('http'); let jsonSettings = workspace.getConfiguration('json'); - let schemas = []; - let settings: Settings = { http: { proxy: httpSettings.get('proxy'), @@ -201,41 +199,69 @@ function getSettings(): Settings { }, json: { format: jsonSettings.get('format'), - schemas: schemas, + schemas: [], + } + }; + let schemaSettingsById: { [schemaId: string]: JSONSchemaSettings } = Object.create(null); + let collectSchemaSettings = (schemaSettings: JSONSchemaSettings[], rootPath?: string, fileMatchPrefix?: string) => { + for (let setting of schemaSettings) { + let url = getSchemaId(setting, rootPath); + if (!url) { + continue; + } + let schemaSetting = schemaSettingsById[url]; + if (!schemaSetting) { + schemaSetting = schemaSettingsById[url] = { url, fileMatch: [] }; + settings.json.schemas.push(schemaSetting); + } + let fileMatches = setting.fileMatch; + if (Array.isArray(fileMatches)) { + if (fileMatchPrefix) { + fileMatches = fileMatches.map(m => fileMatchPrefix + m); + } + schemaSetting.fileMatch.push(...fileMatches); + } + if (setting.schema) { + schemaSetting.schema = setting.schema; + } } }; - let settingsSchemas = jsonSettings.get('schemas'); - if (Array.isArray(settingsSchemas)) { - schemas.push(...settingsSchemas); - } + // merge global and folder settings. Qualify all file matches with the folder path. + let globalSettings = jsonSettings.get('schemas'); + if (Array.isArray(globalSettings)) { + collectSchemaSettings(globalSettings, workspace.rootPath); + } let folders = workspace.workspaceFolders; if (folders) { - folders.forEach(folder => { - let jsonConfig = workspace.getConfiguration('json', folder.uri); - let schemaConfigInfo = jsonConfig.inspect('schemas'); + for (let folder of folders) { + let folderUri = folder.uri; + let schemaConfigInfo = workspace.getConfiguration('json', folderUri).inspect('schemas'); let folderSchemas = schemaConfigInfo.workspaceFolderValue; if (Array.isArray(folderSchemas)) { - folderSchemas.forEach(schema => { - let url = schema.url; - if (!url && schema.schema) { - url = schema.schema.id; - } - if (url && url[0] === '.') { - url = Uri.file(path.normalize(path.join(folder.uri.fsPath, url))).toString(); - } - let fileMatch = schema.fileMatch; - if (fileMatch) { - fileMatch = fileMatch.map(m => folder.uri.toString() + '*' + m); - } - schemas.push({ url, fileMatch, schema: schema.schema }); - }); + let folderPath = folderUri.toString(); + if (folderPath[folderPath.length - 1] !== '/') { + folderPath = folderPath + '/'; + } + collectSchemaSettings(folderSchemas, folderUri.fsPath, folderPath + '*'); }; - }); + }; } return settings; } +function getSchemaId(schema: JSONSchemaSettings, rootPath?: string) { + let url = schema.url; + if (!url) { + if (schema.schema) { + url = schema.schema.id || `vscode://schemas/custom/${encodeURIComponent(hash(schema.schema).toString(16))}`; + } + } else if (rootPath && (url[0] === '.' || url[0] === '/')) { + url = Uri.file(path.normalize(path.join(rootPath, url))).toString(); + } + return url; +} + function getPackageInfo(context: ExtensionContext): IPackageInfo { let extensionPackage = require(context.asAbsolutePath('./package.json')); if (extensionPackage) { diff --git a/extensions/json/client/src/utils/hash.ts b/extensions/json/client/src/utils/hash.ts new file mode 100644 index 00000000000..e7149e43cd7 --- /dev/null +++ b/extensions/json/client/src/utils/hash.ts @@ -0,0 +1,59 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +/** + * Return a hash value for an object. + */ +export function hash(obj: any, hashVal = 0): number { + switch (typeof obj) { + case 'object': + if (obj === null) { + return numberHash(349, hashVal); + } else if (Array.isArray(obj)) { + return arrayHash(obj, hashVal); + } + return objectHash(obj, hashVal); + case 'string': + return stringHash(obj, hashVal); + case 'boolean': + return booleanHash(obj, hashVal); + case 'number': + return numberHash(obj, hashVal); + case 'undefined': + return numberHash(obj, 937); + default: + return numberHash(obj, 617); + } +} + +function numberHash(val: number, initialHashVal: number): number { + return (((initialHashVal << 5) - initialHashVal) + val) | 0; // hashVal * 31 + ch, keep as int32 +} + +function booleanHash(b: boolean, initialHashVal: number): number { + return numberHash(b ? 433 : 863, initialHashVal); +} + +function stringHash(s: string, hashVal: number) { + hashVal = numberHash(149417, hashVal); + for (let i = 0, length = s.length; i < length; i++) { + hashVal = numberHash(s.charCodeAt(i), hashVal); + } + return hashVal; +} + +function arrayHash(arr: any[], initialHashVal: number): number { + initialHashVal = numberHash(104579, initialHashVal); + return arr.reduce((hashVal, item) => hash(item, hashVal), initialHashVal); +} + +function objectHash(obj: any, initialHashVal: number): number { + initialHashVal = numberHash(181387, initialHashVal); + return Object.keys(obj).sort().reduce((hashVal, key) => { + hashVal = stringHash(key, hashVal); + return hash(obj[key], hashVal); + }, initialHashVal); +} diff --git a/extensions/json/server/npm-shrinkwrap.json b/extensions/json/server/npm-shrinkwrap.json index 517efec26a3..3bcdb83008a 100644 --- a/extensions/json/server/npm-shrinkwrap.json +++ b/extensions/json/server/npm-shrinkwrap.json @@ -43,9 +43,9 @@ "resolved": "https://registry.npmjs.org/request-light/-/request-light-0.2.1.tgz" }, "vscode-json-languageservice": { - "version": "2.0.16", + "version": "2.0.18", "from": "vscode-json-languageservice@next", - "resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-2.0.16.tgz" + "resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-2.0.18.tgz" }, "vscode-jsonrpc": { "version": "3.3.1", diff --git a/extensions/json/server/package.json b/extensions/json/server/package.json index d1ece9575d2..684e9c35d28 100644 --- a/extensions/json/server/package.json +++ b/extensions/json/server/package.json @@ -10,7 +10,7 @@ "dependencies": { "jsonc-parser": "^1.0.0", "request-light": "^0.2.1", - "vscode-json-languageservice": "^2.0.16", + "vscode-json-languageservice": "^2.0.18", "vscode-languageserver": "3.4.0-next.6", "vscode-languageserver-protocol": "^3.1.1", "vscode-nls": "^2.0.2", diff --git a/extensions/json/server/src/jsonServerMain.ts b/extensions/json/server/src/jsonServerMain.ts index 183d633ab7a..4d1c416c41c 100644 --- a/extensions/json/server/src/jsonServerMain.ts +++ b/extensions/json/server/src/jsonServerMain.ts @@ -13,7 +13,6 @@ import { import { DocumentColorRequest, ServerCapabilities as CPServerCapabilities } from 'vscode-languageserver-protocol/lib/protocol.colorProvider.proposed'; import { xhr, XHRResponse, configure as configureHttpRequests, getErrorStatusDescription } from 'request-light'; -import path = require('path'); import fs = require('fs'); import URI from 'vscode-uri'; import * as URL from 'url'; @@ -54,9 +53,7 @@ let clientDynamicRegisterSupport = false; // After the server has started the client sends an initilize request. The server receives // in the passed params the rootPath of the workspace plus the client capabilities. -let workspaceRoot: URI; connection.onInitialize((params: InitializeParams): InitializeResult => { - workspaceRoot = URI.parse(params.rootPath); function hasClientCapability(...keys: string[]) { let c = params.capabilities; @@ -192,19 +189,12 @@ function updateConfiguration() { } } if (jsonConfigurationSettings) { - jsonConfigurationSettings.forEach(schema => { + jsonConfigurationSettings.forEach((schema, index) => { let uri = schema.url; if (!uri && schema.schema) { - uri = schema.schema.id; - } - if (!uri && schema.fileMatch) { - uri = 'vscode://schemas/custom/' + encodeURIComponent(schema.fileMatch.join('&')); + uri = schema.schema.id || `vscode://schemas/custom/${index}`; } if (uri) { - if (uri[0] === '.' && workspaceRoot) { - // workspace relative path - uri = URI.file(path.normalize(path.join(workspaceRoot.fsPath, uri))).toString(); - } languageSettings.schemas.push({ uri, fileMatch: schema.fileMatch, schema: schema.schema }); } });