jsonServerMain.ts 10.6 KB
Newer Older
1 2 3 4 5 6 7
/*---------------------------------------------------------------------------------------------
 *  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 {
8
	createConnection, IConnection,
9
	TextDocuments, TextDocument, InitializeParams, InitializeResult, NotificationType, RequestType,
M
Martin Aeschlimann 已提交
10
	DocumentRangeFormattingRequest, Disposable, Range
11 12
} from 'vscode-languageserver';

J
Johannes Rieken 已提交
13
import { xhr, XHRResponse, configure as configureHttpRequests, getErrorStatusDescription } from 'request-light';
14 15 16
import path = require('path');
import fs = require('fs');
import URI from './utils/uri';
17
import * as URL from 'url';
18
import Strings = require('./utils/strings');
J
Johannes Rieken 已提交
19 20
import { JSONDocument, JSONSchema, LanguageSettings, getLanguageService } from 'vscode-json-languageservice';
import { getLanguageModelCache } from './languageModelCache';
M
Martin Aeschlimann 已提交
21

22 23 24
import * as nls from 'vscode-nls';
nls.config(process.env['VSCODE_NLS_CONFIG']);

25 26 27 28
interface ISchemaAssociations {
	[pattern: string]: string[];
}

29
namespace SchemaAssociationNotification {
30
	export const type: NotificationType<ISchemaAssociations, any> = new NotificationType('json/schemaAssociations');
31 32 33
}

namespace VSCodeContentRequest {
34
	export const type: RequestType<string, string, any, any> = new RequestType('vscode/content');
35 36
}

M
Martin Aeschlimann 已提交
37 38 39 40
namespace ColorSymbolRequest {
	export const type: RequestType<string, Range[], any, any> = new RequestType('json/colorSymbols');
}

41
// Create a connection for the server
42
let connection: IConnection = createConnection();
43

M
Martin Aeschlimann 已提交
44 45 46
console.log = connection.console.log.bind(connection.console);
console.error = connection.console.error.bind(connection.console);

47 48 49 50 51 52 53
// Create a simple text document manager. The text document manager
// supports full document sync only
let documents: TextDocuments = new TextDocuments();
// Make the text document manager listen on the connection
// for open, change and close text document events
documents.listen(connection);

54 55 56
let clientSnippetSupport = false;
let clientDynamicRegisterSupport = false;

57
// After the server has started the client sends an initilize request. The server receives
A
Andre Weinand 已提交
58
// in the passed params the rootPath of the workspace plus the client capabilities.
59
let workspaceRoot: URI;
M
Martin Aeschlimann 已提交
60
connection.onInitialize((params: InitializeParams): InitializeResult => {
61
	workspaceRoot = URI.parse(params.rootPath);
62

63 64 65 66 67 68 69 70 71 72
	function hasClientCapability(...keys: string[]) {
		let c = params.capabilities;
		for (let i = 0; c && i < keys.length; i++) {
			c = c[keys[i]];
		}
		return !!c;
	}

	clientSnippetSupport = hasClientCapability('textDocument', 'completion', 'completionItem', 'snippetSupport');
	clientDynamicRegisterSupport = hasClientCapability('workspace', 'symbol', 'dynamicRegistration');
73 74 75 76
	return {
		capabilities: {
			// Tell the client that the server works in FULL text document sync mode
			textDocumentSync: documents.syncKind,
77
			completionProvider: clientSnippetSupport ? { resolveProvider: true, triggerCharacters: ['"', ':'] } : null,
78 79
			hoverProvider: true,
			documentSymbolProvider: true,
80
			documentRangeFormattingProvider: false
81
		}
A
tslint  
Alex Dima 已提交
82
	};
83 84 85
});

let workspaceContext = {
86
	resolveRelativePath: (relativePath: string, resource: string) => {
87
		return URL.resolve(resource, relativePath);
88
	}
A
tslint  
Alex Dima 已提交
89
};
90

J
Johannes Rieken 已提交
91
let schemaRequestService = (uri: string): Thenable<string> => {
92 93 94
	if (Strings.startsWith(uri, 'file://')) {
		let fsPath = URI.parse(uri).fsPath;
		return new Promise<string>((c, e) => {
95
			fs.readFile(fsPath, 'UTF-8', (err, result) => {
96
				err ? e('') : c(result.toString());
97 98
			});
		});
99 100 101
	} else if (Strings.startsWith(uri, 'vscode://')) {
		return connection.sendRequest(VSCodeContentRequest.type, uri).then(responseText => {
			return responseText;
102
		}, error => {
103
			return error.message;
104
		});
105
	}
106 107 108 109 110 111 112 113 114 115 116
	if (uri.indexOf('//schema.management.azure.com/') !== -1) {
		connection.telemetry.logEvent({
			key: 'json.schema',
			value: {
				schemaURL: uri
			}
		});
	}
	return xhr({ url: uri, followRedirects: 5 }).then(response => {
		return response.responseText;
	}, (error: XHRResponse) => {
117
		return Promise.reject(error.responseText || getErrorStatusDescription(error.status) || error.toString());
118
	});
A
tslint  
Alex Dima 已提交
119
};
120

121
// create the JSON language service
122 123 124
let languageService = getLanguageService({
	schemaRequestService,
	workspaceContext,
125
	contributions: []
126 127
});

128
// The settings interface describes the server relevant settings part
129
interface Settings {
130
	json: {
A
tslint  
Alex Dima 已提交
131
		schemas: JSONSchemaSettings[];
132
		format: { enable: boolean; };
A
tslint  
Alex Dima 已提交
133
	};
M
Martin Aeschlimann 已提交
134
	http: {
A
tslint  
Alex Dima 已提交
135 136 137
		proxy: string;
		proxyStrictSSL: boolean;
	};
138 139
}

140
interface JSONSchemaSettings {
A
tslint  
Alex Dima 已提交
141 142
	fileMatch?: string[];
	url?: string;
143
	schema?: JSONSchema;
144 145
}

M
Martin Aeschlimann 已提交
146 147
let jsonConfigurationSettings: JSONSchemaSettings[] = void 0;
let schemaAssociations: ISchemaAssociations = void 0;
148
let formatterRegistration: Thenable<Disposable> = null;
149

150 151
// The settings have changed. Is send on server activation as well.
connection.onDidChangeConfiguration((change) => {
A
tslint  
Alex Dima 已提交
152
	var settings = <Settings>change.settings;
153
	configureHttpRequests(settings.http && settings.http.proxy, settings.http && settings.http.proxyStrictSSL);
A
tslint  
Alex Dima 已提交
154

155
	jsonConfigurationSettings = settings.json && settings.json.schemas;
156
	updateConfiguration();
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171

	// dynamically enable & disable the formatter
	if (clientDynamicRegisterSupport) {
		let enableFormatter = settings && settings.json && settings.json.format && settings.json.format.enable;
		if (enableFormatter) {
			if (!formatterRegistration) {
				console.log('enable');
				formatterRegistration = connection.client.register(DocumentRangeFormattingRequest.type, { documentSelector: [{ language: 'json' }] });
			}
		} else if (formatterRegistration) {
			console.log('enable');
			formatterRegistration.then(r => r.dispose());
			formatterRegistration = null;
		}
	}
172
});
173

174
// The jsonValidation extension configuration has changed
175
connection.onNotification(SchemaAssociationNotification.type, associations => {
176 177 178 179 180
	schemaAssociations = associations;
	updateConfiguration();
});

function updateConfiguration() {
J
Johannes Rieken 已提交
181
	let languageSettings: LanguageSettings = {
182 183 184 185
		validate: true,
		allowComments: true,
		schemas: []
	};
186 187 188 189
	if (schemaAssociations) {
		for (var pattern in schemaAssociations) {
			let association = schemaAssociations[pattern];
			if (Array.isArray(association)) {
M
Martin Aeschlimann 已提交
190
				association.forEach(uri => {
191
					languageSettings.schemas.push({ uri, fileMatch: [pattern] });
A
tslint  
Alex Dima 已提交
192
				});
193 194 195 196
			}
		}
	}
	if (jsonConfigurationSettings) {
197 198 199 200 201 202 203 204 205 206
		jsonConfigurationSettings.forEach(schema => {
			let uri = schema.url;
			if (!uri && schema.schema) {
				uri = schema.schema.id;
			}
			if (!uri && schema.fileMatch) {
				uri = 'vscode://schemas/custom/' + encodeURIComponent(schema.fileMatch.join('&'));
			}
			if (uri) {
				if (uri[0] === '.' && workspaceRoot) {
207
					// workspace relative path
M
Martin Aeschlimann 已提交
208
					uri = URI.file(path.normalize(path.join(workspaceRoot.fsPath, uri))).toString();
209
				}
210
				languageSettings.schemas.push({ uri, fileMatch: schema.fileMatch, schema: schema.schema });
211 212 213
			}
		});
	}
214
	languageService.configure(languageSettings);
M
Martin Aeschlimann 已提交
215

216
	// Revalidate any open text documents
217
	documents.all().forEach(triggerValidation);
218 219
}

220 221 222 223 224 225 226 227 228 229 230 231
// The content of a text document has changed. This event is emitted
// when the text document first opened or when its content has changed.
documents.onDidChangeContent((change) => {
	triggerValidation(change.document);
});

// a document has closed: clear all diagnostics
documents.onDidClose(event => {
	cleanPendingValidation(event.document);
	connection.sendDiagnostics({ uri: event.document.uri, diagnostics: [] });
});

J
Johannes Rieken 已提交
232
let pendingValidationRequests: { [uri: string]: NodeJS.Timer; } = {};
233 234
const validationDelayMs = 200;

235
function cleanPendingValidation(textDocument: TextDocument): void {
236 237 238
	let request = pendingValidationRequests[textDocument.uri];
	if (request) {
		clearTimeout(request);
239
		delete pendingValidationRequests[textDocument.uri];
240
	}
241 242 243 244
}

function triggerValidation(textDocument: TextDocument): void {
	cleanPendingValidation(textDocument);
245 246 247 248 249
	pendingValidationRequests[textDocument.uri] = setTimeout(() => {
		delete pendingValidationRequests[textDocument.uri];
		validateTextDocument(textDocument);
	}, validationDelayMs);
}
250

251
function validateTextDocument(textDocument: TextDocument): void {
252 253 254 255 256 257
	if (textDocument.getText().length === 0) {
		// ignore empty documents
		connection.sendDiagnostics({ uri: textDocument.uri, diagnostics: [] });
		return;
	}

258
	let jsonDocument = getJSONDocument(textDocument);
M
Martin Aeschlimann 已提交
259
	languageService.doValidation(textDocument, jsonDocument).then(diagnostics => {
260 261 262 263 264 265
		// Send the computed diagnostics to VSCode.
		connection.sendDiagnostics({ uri: textDocument.uri, diagnostics });
	});
}

connection.onDidChangeWatchedFiles((change) => {
266
	// Monitored files have changed in VSCode
267 268
	let hasChanges = false;
	change.changes.forEach(c => {
M
Martin Aeschlimann 已提交
269
		if (languageService.resetSchema(c.uri)) {
270 271 272 273 274 275 276 277
			hasChanges = true;
		}
	});
	if (hasChanges) {
		documents.all().forEach(validateTextDocument);
	}
});

278
let jsonDocuments = getLanguageModelCache<JSONDocument>(10, 60, document => languageService.parseJSONDocument(document));
279 280 281 282 283 284
documents.onDidClose(e => {
	jsonDocuments.onDocumentRemoved(e.document);
});
connection.onShutdown(() => {
	jsonDocuments.dispose();
});
285

286
function getJSONDocument(document: TextDocument): JSONDocument {
287
	return jsonDocuments.get(document);
288 289
}

M
Martin Aeschlimann 已提交
290
connection.onCompletion(textDocumentPosition => {
291
	let document = documents.get(textDocumentPosition.textDocument.uri);
292
	let jsonDocument = getJSONDocument(document);
M
Martin Aeschlimann 已提交
293
	return languageService.doComplete(document, textDocumentPosition.position, jsonDocument);
294 295
});

M
Martin Aeschlimann 已提交
296
connection.onCompletionResolve(completionItem => {
M
Martin Aeschlimann 已提交
297
	return languageService.doResolve(completionItem);
298 299
});

M
Martin Aeschlimann 已提交
300 301
connection.onHover(textDocumentPositionParams => {
	let document = documents.get(textDocumentPositionParams.textDocument.uri);
302
	let jsonDocument = getJSONDocument(document);
M
Martin Aeschlimann 已提交
303
	return languageService.doHover(document, textDocumentPositionParams.position, jsonDocument);
304 305
});

M
Martin Aeschlimann 已提交
306
connection.onDocumentSymbol(documentSymbolParams => {
307
	let document = documents.get(documentSymbolParams.textDocument.uri);
308
	let jsonDocument = getJSONDocument(document);
M
Martin Aeschlimann 已提交
309
	return languageService.findDocumentSymbols(document, jsonDocument);
310 311
});

M
Martin Aeschlimann 已提交
312
connection.onDocumentRangeFormatting(formatParams => {
313
	let document = documents.get(formatParams.textDocument.uri);
M
Martin Aeschlimann 已提交
314
	return languageService.format(document, formatParams.range, formatParams.options);
315 316
});

M
Martin Aeschlimann 已提交
317 318 319 320 321 322 323 324 325
connection.onRequest(ColorSymbolRequest.type, uri => {
	let document = documents.get(uri);
	if (document) {
		let jsonDocument = getJSONDocument(document);
		return languageService.findColorSymbols(document, jsonDocument);
	}
	return [];
});

326 327
// Listen on the connection
connection.listen();