diff --git a/extensions/json/server/src/jsonSchemaService.ts b/extensions/json/server/src/jsonSchemaService.ts index a667997e61e..09c16651d17 100644 --- a/extensions/json/server/src/jsonSchemaService.ts +++ b/extensions/json/server/src/jsonSchemaService.ts @@ -37,9 +37,13 @@ export interface IJSONSchemaService { getSchemaForResource(resource: string, document: Parser.JSONDocument): Thenable; } +export interface ISchemaAssociations { + [pattern: string]: string[]; +} + export interface ISchemaContributions { schemas?: { [id: string]: IJSONSchema }; - schemaAssociations?: { [pattern: string]: string[] }; + schemaAssociations?: ISchemaAssociations; } export interface ISchemaHandle { diff --git a/extensions/json/server/src/server.ts b/extensions/json/server/src/server.ts index 9603e19ce4b..7335492a7c2 100644 --- a/extensions/json/server/src/server.ts +++ b/extensions/json/server/src/server.ts @@ -10,7 +10,7 @@ import { TextDocuments, ITextDocument, Diagnostic, DiagnosticSeverity, InitializeParams, InitializeResult, TextDocumentIdentifier, TextDocumentPosition, CompletionItem, CompletionItemKind, Files, Hover, SymbolInformation, TextEdit, DocumentFormattingParams, - DocumentRangeFormattingParams, NotificationType + DocumentRangeFormattingParams, NotificationType, RequestType } from 'vscode-languageserver'; import {xhr, IXHROptions, IXHRResponse} from './utils/httpRequest'; @@ -19,7 +19,7 @@ import fs = require('fs'); import URI from './utils/uri'; import Strings = require('./utils/strings'); import {create as createLinesModel, LinesModel} from './utils/lines'; -import {IWorkspaceContextService, ITelemetryService, JSONSchemaService, ISchemaContributions} from './jsonSchemaService'; +import {IWorkspaceContextService, ITelemetryService, JSONSchemaService, ISchemaContributions, ISchemaAssociations} from './jsonSchemaService'; import {parse as parseJSON, ObjectASTNode, JSONDocument} from './jsonParser'; import {JSONCompletion} from './jsonCompletion'; import {JSONHover} from './jsonHover'; @@ -31,6 +31,14 @@ namespace TelemetryNotification { export const type: NotificationType<{ key: string, data: any }> = { get method() { return 'telemetry'; } }; } +namespace SchemaAssociationNotification { + export const type: NotificationType = { get method() { return 'json/schemaAssociations'; } }; +} + +namespace VSCodeContentRequest { + export const type: RequestType = { get method() { return 'vscode/content'; } }; +} + // Create a connection for the server. The connection uses for // stdin / stdout for message passing let connection: IConnection = createConnection(new IPCMessageReader(process), new IPCMessageWriter(process)); @@ -42,11 +50,10 @@ let documents: TextDocuments = new TextDocuments(); // for open, change and close text document events documents.listen(connection); - // 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 capabilites. let workspaceRoot: URI; -connection.onInitialize((params): InitializeResult => { +connection.onInitialize((params: InitializeResult) => { workspaceRoot = URI.parse(params.rootPath); return { capabilities: { @@ -76,14 +83,26 @@ let telemetry = { } } -let request = (options: IXHROptions): Promise => { - if (options.url.indexOf('file://') === 0) { +let request = (options: IXHROptions): Thenable => { + if (Strings.startsWith(options.url, 'file://')) { let fsPath = URI.parse(options.url).fsPath; return new Promise((c, e) => { fs.readFile(fsPath, 'UTF-8', (err, result) => { err ? e({ responseText: '', status: 404 }) : c({ responseText: result.toString(), status: 200 }) }); }); + } else if (Strings.startsWith(options.url, 'vscode-schema://')) { + return connection.sendRequest(VSCodeContentRequest.type, options.url).then(responseText => { + return { + responseText: responseText, + status: 200 + }; + }, error => { + return { + responseText: error.message, + status: 404 + } + }); } return xhr(options); } @@ -103,20 +122,47 @@ documents.onDidChangeContent((change) => { // The settings interface describe the server relevant settings part interface Settings { - json: JSONSettings; + json: { + schemas: JSONSchemaSettings[] + }; } -interface JSONSettings { - schemas: [{ fileMatch: string[], url: string, schema: any }]; +interface JSONSchemaSettings { + fileMatch: string[], + url: string, + schema?: any; } +let jsonConfigurationSettings : JSONSchemaSettings[] = void 0; +let schemaAssociations : ISchemaAssociations = void 0; + // The settings have changed. Is send on server activation as well. connection.onDidChangeConfiguration((change) => { - let jsonSettings = (change.settings).json; + var jsonSettings = (change.settings).json; + jsonConfigurationSettings = jsonSettings && jsonSettings.schemas; + updateConfiguration(); +}); - if (jsonSettings && jsonSettings.schemas) { - jsonSchemaService.clearExternalSchemas(); - jsonSettings.schemas.forEach((schema) => { +// The jsonValidation extension configuration has changed +connection.onNotification(SchemaAssociationNotification.type, (associations) => { + schemaAssociations = associations; + updateConfiguration(); +}); + +function updateConfiguration() { + jsonSchemaService.clearExternalSchemas(); + if (schemaAssociations) { + for (var pattern in schemaAssociations) { + let association = schemaAssociations[pattern]; + if (Array.isArray(association)) { + association.forEach(url => { + jsonSchemaService.registerExternalSchema(url, [pattern]); + }) + } + } + } + if (jsonConfigurationSettings) { + jsonConfigurationSettings.forEach((schema) => { if (schema.url && (schema.fileMatch || schema.schema)) { let url = schema.url; if (!Strings.startsWith(url, 'http://') && !Strings.startsWith(url, 'https://') && !Strings.startsWith(url, 'file://')) { @@ -131,10 +177,10 @@ connection.onDidChangeConfiguration((change) => { } }); } - // Revalidate any open text documents documents.all().forEach(validateTextDocument); -}); +} + function validateTextDocument(textDocument: ITextDocument): void { let jsonDocument = getJSONDocument(textDocument); diff --git a/extensions/json/src/jsonMain.ts b/extensions/json/src/jsonMain.ts index e5d12973de0..269c22ef8e2 100644 --- a/extensions/json/src/jsonMain.ts +++ b/extensions/json/src/jsonMain.ts @@ -6,13 +6,25 @@ import * as path from 'path'; -import {workspace, Disposable, ExtensionContext} from 'vscode'; -import {LanguageClient, LanguageClientOptions, SettingMonitor, ServerOptions, TransportKind, NotificationType} from 'vscode-languageclient'; +import {workspace, Disposable, ExtensionContext, extensions, Uri} from 'vscode'; +import {LanguageClient, LanguageClientOptions, RequestType, SettingMonitor, ServerOptions, TransportKind, NotificationType} from 'vscode-languageclient'; namespace TelemetryNotification { export const type: NotificationType<{ key: string, data: any }> = { get method() { return 'telemetry'; } }; } +namespace VSCodeContentRequest { + export const type: RequestType = { get method() { return 'vscode/content'; } }; +} + +export interface ISchemaAssociations { + [pattern: string]: string[]; +} + +namespace SchemaAssociationNotification { + export const type: NotificationType = { get method() { return 'json/schemaAssociations'; } }; +} + export function activate(context: ExtensionContext) { // The server is implemented in node @@ -45,9 +57,43 @@ export function activate(context: ExtensionContext) { // to be done }); + // handle content request + client.onRequest(VSCodeContentRequest.type, (uri: string) => { + return workspace.openTextDocument(uri).then(doc => doc.getText()); + }); + let disposable = client.start(); + client.sendNotification(SchemaAssociationNotification.type, getSchemaAssociation(context)); + // Push the disposable to the context's subscriptions so that the // client can be deactivated on extension deactivation context.subscriptions.push(disposable); +} + +function getSchemaAssociation(context: ExtensionContext) : ISchemaAssociations { + var associations : ISchemaAssociations = {}; + extensions.all.forEach(extension => { + var packageJSON = extension.packageJSON; + if (packageJSON && packageJSON.contributes && packageJSON.contributes.jsonValidation) { + var jsonValidation = packageJSON.contributes.jsonValidation; + if (Array.isArray(jsonValidation)) { + jsonValidation.forEach(jv => { + var {fileMatch, url} = jv; + if (fileMatch && url) { + if (url[0] === '.' && url[1] === '.') { + url = context.asAbsolutePath(url); + } + var association = associations[fileMatch]; + if (!association) { + association = []; + associations[fileMatch] = association; + } + association.push(url); + } + }); + } + } + }) + return associations; } \ No newline at end of file