jsonServerMain.ts 8.8 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
	IPCMessageReader, IPCMessageWriter, createConnection, IConnection,
M
Martin Aeschlimann 已提交
9
	TextDocuments, TextDocument, InitializeParams, InitializeResult, NotificationType, RequestType
10 11
} from 'vscode-languageserver';

12
import {xhr, XHROptions, XHRResponse, configure as configureHttpRequests} from 'request-light';
13 14 15 16
import path = require('path');
import fs = require('fs');
import URI from './utils/uri';
import Strings = require('./utils/strings');
M
Martin Aeschlimann 已提交
17 18
import {ISchemaAssociations} from './jsonSchemaService';
import {JSONDocument} from './jsonParser';
19
import {IJSONSchema} from './jsonSchema';
20 21
import {ProjectJSONContribution} from './jsoncontributions/projectJSONContribution';
import {GlobPatternContribution} from './jsoncontributions/globPatternContribution';
22
import {FileAssociationContribution} from './jsoncontributions/fileAssociationContribution';
23

M
Martin Aeschlimann 已提交
24 25
import {JSONSchemaConfiguration, getLanguageService} from './jsonLanguageService';

26 27 28
import * as nls from 'vscode-nls';
nls.config(process.env['VSCODE_NLS_CONFIG']);

29 30 31 32 33 34 35 36
namespace SchemaAssociationNotification {
	export const type: NotificationType<ISchemaAssociations> = { get method() { return 'json/schemaAssociations'; } };
}

namespace VSCodeContentRequest {
	export const type: RequestType<string, string, any> = { get method() { return 'vscode/content'; } };
}

37 38 39 40
// 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));

M
Martin Aeschlimann 已提交
41 42 43
console.log = connection.console.log.bind(connection.console);
console.error = connection.console.error.bind(connection.console);

44 45 46 47 48 49 50
// 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);

B
Benjamin Pasero 已提交
51 52
const filesAssociationContribution = new FileAssociationContribution();

53 54 55
// 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;
M
Martin Aeschlimann 已提交
56
connection.onInitialize((params: InitializeParams): InitializeResult => {
57
	workspaceRoot = URI.parse(params.rootPath);
B
Benjamin Pasero 已提交
58
	filesAssociationContribution.setLanguageIds(params.initializationOptions.languageIds);
59 60 61 62
	return {
		capabilities: {
			// Tell the client that the server works in FULL text document sync mode
			textDocumentSync: documents.syncKind,
63
			completionProvider: { resolveProvider: true },
64 65 66 67 68
			hoverProvider: true,
			documentSymbolProvider: true,
			documentRangeFormattingProvider: true,
			documentFormattingProvider: true
		}
A
tslint  
Alex Dima 已提交
69
	};
70 71 72
});

let workspaceContext = {
73 74 75 76
	resolveRelativePath: (relativePath: string, resource: string) => {
		if (typeof relativePath === 'string' && resource) {
			let resourceURI = URI.parse(resource);
			return URI.file(path.normalize(path.join(path.dirname(resourceURI.fsPath), relativePath))).toString();
77
		}
78
		return void 0;
79
	}
A
tslint  
Alex Dima 已提交
80
};
81 82 83

let telemetry = {
	log: (key: string, data: any) => {
84
		connection.telemetry.logEvent({ key, data });
85
	}
A
tslint  
Alex Dima 已提交
86
};
87

88
let request = (options: XHROptions): Thenable<XHRResponse> => {
89
	if (Strings.startsWith(options.url, 'file://')) {
90
		let fsPath = URI.parse(options.url).fsPath;
91
		return new Promise<XHRResponse>((c, e) => {
92
			fs.readFile(fsPath, 'UTF-8', (err, result) => {
A
tslint  
Alex Dima 已提交
93
				err ? e({ responseText: '', status: 404 }) : c({ responseText: result.toString(), status: 200 });
94 95
			});
		});
M
Martin Aeschlimann 已提交
96
	} else if (Strings.startsWith(options.url, 'vscode://')) {
97 98 99 100 101 102 103 104 105
		return connection.sendRequest(VSCodeContentRequest.type, options.url).then(responseText => {
			return {
				responseText: responseText,
				status: 200
			};
		}, error => {
			return {
				responseText: error.message,
				status: 404
A
tslint  
Alex Dima 已提交
106
			};
107
		});
108 109
	}
	return xhr(options);
A
tslint  
Alex Dima 已提交
110
};
111

112 113
let contributions = [
	new ProjectJSONContribution(request),
114
	new GlobPatternContribution(),
B
Benjamin Pasero 已提交
115
	filesAssociationContribution
116
];
M
Martin Aeschlimann 已提交
117
let languageService = getLanguageService(contributions, request, workspaceContext, telemetry);
118 119 120 121 122 123 124 125 126

// 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) => {
	validateTextDocument(change.document);
});

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

136
interface JSONSchemaSettings {
A
tslint  
Alex Dima 已提交
137 138
	fileMatch?: string[];
	url?: string;
139
	schema?: IJSONSchema;
140 141
}

M
Martin Aeschlimann 已提交
142 143
let jsonConfigurationSettings: JSONSchemaSettings[] = void 0;
let schemaAssociations: ISchemaAssociations = void 0;
144

145 146
// The settings have changed. Is send on server activation as well.
connection.onDidChangeConfiguration((change) => {
A
tslint  
Alex Dima 已提交
147
	var settings = <Settings>change.settings;
148
	configureHttpRequests(settings.http && settings.http.proxy, settings.http && settings.http.proxyStrictSSL);
A
tslint  
Alex Dima 已提交
149

150
	jsonConfigurationSettings = settings.json && settings.json.schemas;
151 152
	updateConfiguration();
});
153

154 155 156 157 158 159 160
// The jsonValidation extension configuration has changed
connection.onNotification(SchemaAssociationNotification.type, (associations) => {
	schemaAssociations = associations;
	updateConfiguration();
});

function updateConfiguration() {
M
Martin Aeschlimann 已提交
161
	let schemaConfigs: JSONSchemaConfiguration[] = [];
162 163 164 165
	if (schemaAssociations) {
		for (var pattern in schemaAssociations) {
			let association = schemaAssociations[pattern];
			if (Array.isArray(association)) {
M
Martin Aeschlimann 已提交
166 167
				association.forEach(uri => {
					schemaConfigs.push({ uri, fileMatch: [pattern] });
A
tslint  
Alex Dima 已提交
168
				});
169 170 171 172 173
			}
		}
	}
	if (jsonConfigurationSettings) {
		jsonConfigurationSettings.forEach((schema) => {
174
			if (schema.fileMatch) {
M
Martin Aeschlimann 已提交
175 176 177 178 179
				let uri = schema.url;
				if (!uri && schema.schema) {
					uri = schema.schema.id;
					if (!uri) {
						uri = 'vscode://schemas/custom/' + encodeURIComponent(schema.fileMatch.join('&'));
180 181
					}
				}
M
Martin Aeschlimann 已提交
182
				if (Strings.startsWith(uri, '.') && workspaceRoot) {
183
					// workspace relative path
M
Martin Aeschlimann 已提交
184
					uri = URI.file(path.normalize(path.join(workspaceRoot.fsPath, uri))).toString();
185
				}
M
Martin Aeschlimann 已提交
186 187
				if (uri) {
					schemaConfigs.push({ uri, fileMatch: schema.fileMatch, schema: schema.schema });
188 189 190 191
				}
			}
		});
	}
M
Martin Aeschlimann 已提交
192 193
	languageService.configure(schemaConfigs);

194 195
	// Revalidate any open text documents
	documents.all().forEach(validateTextDocument);
196 197
}

198

199
function validateTextDocument(textDocument: TextDocument): void {
200 201 202 203 204 205
	if (textDocument.getText().length === 0) {
		// ignore empty documents
		connection.sendDiagnostics({ uri: textDocument.uri, diagnostics: [] });
		return;
	}

206
	let jsonDocument = getJSONDocument(textDocument);
M
Martin Aeschlimann 已提交
207
	languageService.doValidation(textDocument, jsonDocument).then(diagnostics => {
208 209 210 211 212 213
		// Send the computed diagnostics to VSCode.
		connection.sendDiagnostics({ uri: textDocument.uri, diagnostics });
	});
}

connection.onDidChangeWatchedFiles((change) => {
214
	// Monitored files have changed in VSCode
215 216
	let hasChanges = false;
	change.changes.forEach(c => {
M
Martin Aeschlimann 已提交
217
		if (languageService.resetSchema(c.uri)) {
218 219 220 221 222 223 224 225
			hasChanges = true;
		}
	});
	if (hasChanges) {
		documents.all().forEach(validateTextDocument);
	}
});

226
function getJSONDocument(document: TextDocument): JSONDocument {
M
Martin Aeschlimann 已提交
227
	return languageService.parseJSONDocument(document);
228 229
}

M
Martin Aeschlimann 已提交
230
connection.onCompletion(textDocumentPosition => {
231
	let document = documents.get(textDocumentPosition.textDocument.uri);
232
	let jsonDocument = getJSONDocument(document);
M
Martin Aeschlimann 已提交
233
	return languageService.doComplete(document, textDocumentPosition.position, jsonDocument);
234 235
});

M
Martin Aeschlimann 已提交
236
connection.onCompletionResolve(completionItem => {
M
Martin Aeschlimann 已提交
237
	return languageService.doResolve(completionItem);
238 239
});

M
Martin Aeschlimann 已提交
240 241
connection.onHover(textDocumentPositionParams => {
	let document = documents.get(textDocumentPositionParams.textDocument.uri);
242
	let jsonDocument = getJSONDocument(document);
M
Martin Aeschlimann 已提交
243
	return languageService.doHover(document, textDocumentPositionParams.position, jsonDocument);
244 245
});

M
Martin Aeschlimann 已提交
246
connection.onDocumentSymbol(documentSymbolParams => {
247
	let document = documents.get(documentSymbolParams.textDocument.uri);
248
	let jsonDocument = getJSONDocument(document);
M
Martin Aeschlimann 已提交
249
	return languageService.findDocumentSymbols(document, jsonDocument);
250 251
});

M
Martin Aeschlimann 已提交
252
connection.onDocumentFormatting(formatParams => {
253
	let document = documents.get(formatParams.textDocument.uri);
M
Martin Aeschlimann 已提交
254
	return languageService.format(document, null, formatParams.options);
255 256
});

M
Martin Aeschlimann 已提交
257
connection.onDocumentRangeFormatting(formatParams => {
258
	let document = documents.get(formatParams.textDocument.uri);
M
Martin Aeschlimann 已提交
259
	return languageService.format(document, formatParams.range, formatParams.options);
260 261 262 263
});

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