typescriptMain.ts 26.7 KB
Newer Older
I
isidor 已提交
1 2 3 4
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
E
Erich Gamma 已提交
5 6 7 8 9 10

/* --------------------------------------------------------------------------------------------
 * Includes code from typescript-sublime-plugin project, obtained from
 * https://github.com/Microsoft/TypeScript-Sublime-Plugin/blob/master/TypeScript%20Indent.tmPreferences
 * ------------------------------------------------------------------------------------------ */

11
import { env, languages, commands, workspace, window, ExtensionContext, Memento, IndentAction, Diagnostic, DiagnosticCollection, Range, Disposable, Uri, MessageItem, TextEditor, DiagnosticSeverity, TextDocument } from 'vscode';
E
Erich Gamma 已提交
12

13 14 15
// This must be the first statement otherwise modules might got loaded with
// the wrong locale.
import * as nls from 'vscode-nls';
J
Johannes Rieken 已提交
16
nls.config({ locale: env.language });
17
const localize = nls.loadMessageBundle();
18

19 20
import * as path from 'path';

E
Erich Gamma 已提交
21
import * as Proto from './protocol';
22
import * as PConst from './protocol.const';
D
Dirk Baeumer 已提交
23

E
Erich Gamma 已提交
24 25 26 27 28
import TypeScriptServiceClient from './typescriptServiceClient';
import { ITypescriptServiceClientHost } from './typescriptService';

import HoverProvider from './features/hoverProvider';
import DefinitionProvider from './features/definitionProvider';
G
gauss1314 已提交
29
import ImplementationProvider from './features/implementationProvider';
30
import TypeDefintionProvider from './features/typeDefinitionProvider';
E
Erich Gamma 已提交
31 32 33 34 35 36 37 38 39
import DocumentHighlightProvider from './features/documentHighlightProvider';
import ReferenceProvider from './features/referenceProvider';
import DocumentSymbolProvider from './features/documentSymbolProvider';
import SignatureHelpProvider from './features/signatureHelpProvider';
import RenameProvider from './features/renameProvider';
import FormattingProvider from './features/formattingProvider';
import BufferSyncSupport from './features/bufferSyncSupport';
import CompletionItemProvider from './features/completionItemProvider';
import WorkspaceSymbolProvider from './features/workspaceSymbolProvider';
40
import CodeActionProvider from './features/codeActionProvider';
M
Matt Bierner 已提交
41
//import RefactorProvider from './features/refactorProvider';
42
import ReferenceCodeLensProvider from './features/referencesCodeLensProvider';
43
import { JsDocCompletionProvider, TryCompleteJsDocCommand } from './features/jsDocCompletionProvider';
44
import { DirectiveCommentCompletionProvider } from './features/directiveCommentCompletionProvider';
45
import TypeScriptTaskProviderManager from './features/taskProvider';
46
import ImplementationCodeLensProvider from './features/implementationsCodeLensProvider';
E
Erich Gamma 已提交
47

48
import * as ProjectStatus from './utils/projectStatus';
49
import TypingsStatus, { AtaProgressReporter } from './utils/typingsStatus';
M
Matt Bierner 已提交
50
import VersionStatus from './utils/versionStatus';
51 52
import { getContributedTypeScriptServerPlugins, TypeScriptServerPlugin } from './utils/plugins';
import { openOrCreateConfigFile, isImplicitProjectConfigFile } from './utils/tsconfig';
53

54 55
interface LanguageDescription {
	id: string;
56
	diagnosticSource: string;
57
	modeIds: string[];
58
	configFile?: string;
59 60
}

61 62 63 64 65 66 67 68 69 70
enum ProjectConfigAction {
	None,
	CreateConfig,
	LearnMore
}

interface ProjectConfigMessageItem extends MessageItem {
	id: ProjectConfigAction;
}

M
Matt Bierner 已提交
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
const MODE_ID_TS = 'typescript';
const MODE_ID_TSX = 'typescriptreact';
const MODE_ID_JS = 'javascript';
const MODE_ID_JSX = 'javascriptreact';

const standardLanguageDescriptions: LanguageDescription[] = [
	{
		id: 'typescript',
		diagnosticSource: 'ts',
		modeIds: [MODE_ID_TS, MODE_ID_TSX],
		configFile: 'tsconfig.json'
	}, {
		id: 'javascript',
		diagnosticSource: 'js',
		modeIds: [MODE_ID_JS, MODE_ID_JSX],
		configFile: 'jsconfig.json'
	}
];
89

E
Erich Gamma 已提交
90
export function activate(context: ExtensionContext): void {
91
	const plugins = getContributedTypeScriptServerPlugins();
92

M
Matt Bierner 已提交
93 94 95 96
	const lazyClientHost = (() => {
		let clientHost: TypeScriptServiceClientHost | undefined;
		return () => {
			if (!clientHost) {
M
Matt Bierner 已提交
97
				clientHost = new TypeScriptServiceClientHost(standardLanguageDescriptions, context.workspaceState, plugins);
M
Matt Bierner 已提交
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
				context.subscriptions.push(clientHost);

				const host = clientHost;
				clientHost.serviceClient.onReady().then(() => {
					context.subscriptions.push(ProjectStatus.create(host.serviceClient,
						path => new Promise<boolean>(resolve => setTimeout(() => resolve(host.handles(path)), 750)),
						context.workspaceState));
				}, () => {
					// Nothing to do here. The client did show a message;
				});
			}
			return clientHost;
		};
	})();

D
Dirk Baeumer 已提交
113 114

	context.subscriptions.push(commands.registerCommand('typescript.reloadProjects', () => {
M
Matt Bierner 已提交
115
		lazyClientHost().reloadProjects();
D
Dirk Baeumer 已提交
116
	}));
117

118
	context.subscriptions.push(commands.registerCommand('javascript.reloadProjects', () => {
M
Matt Bierner 已提交
119
		lazyClientHost().reloadProjects();
120 121
	}));

122
	context.subscriptions.push(commands.registerCommand('typescript.selectTypeScriptVersion', () => {
M
Matt Bierner 已提交
123
		lazyClientHost().serviceClient.onVersionStatusClicked();
124 125
	}));

126
	context.subscriptions.push(commands.registerCommand('typescript.openTsServerLog', () => {
M
Matt Bierner 已提交
127
		lazyClientHost().serviceClient.openTsServerLogFile();
128 129
	}));

130
	context.subscriptions.push(commands.registerCommand('typescript.restartTsServer', () => {
M
Matt Bierner 已提交
131
		lazyClientHost().serviceClient.restartTsServer();
132 133
	}));

134
	context.subscriptions.push(new TypeScriptTaskProviderManager(() => lazyClientHost().serviceClient));
M
Matt Bierner 已提交
135

136 137 138
	const goToProjectConfig = (isTypeScript: boolean) => {
		const editor = window.activeTextEditor;
		if (editor) {
M
Matt Bierner 已提交
139
			lazyClientHost().goToProjectConfig(isTypeScript, editor.document.uri);
140 141 142 143 144
		}
	};
	context.subscriptions.push(commands.registerCommand('typescript.goToProjectConfig', goToProjectConfig.bind(null, true)));
	context.subscriptions.push(commands.registerCommand('javascript.goToProjectConfig', goToProjectConfig.bind(null, false)));

M
Matt Bierner 已提交
145
	const jsDocCompletionCommand = new TryCompleteJsDocCommand(() => lazyClientHost().serviceClient);
146 147
	context.subscriptions.push(commands.registerCommand(TryCompleteJsDocCommand.COMMAND_NAME, jsDocCompletionCommand.tryCompleteJsDoc, jsDocCompletionCommand));

M
Matt Bierner 已提交
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
	const supportedLanguage = [].concat.apply([], standardLanguageDescriptions.map(x => x.modeIds).concat(plugins.map(x => x.languages)));
	function didOpenTextDocument(textDocument: TextDocument): boolean {
		if (supportedLanguage.indexOf(textDocument.languageId) >= 0) {
			openListener.dispose();
			// Force activation
			void lazyClientHost();
			return true;
		}
		return false;
	};
	const openListener = workspace.onDidOpenTextDocument(didOpenTextDocument);
	for (let textDocument of workspace.textDocuments) {
		if (didOpenTextDocument(textDocument)) {
			break;
		}
	}
E
Erich Gamma 已提交
164 165
}

166

167
const validateSetting = 'validate.enable';
E
Erich Gamma 已提交
168

169
class LanguageProvider {
170

171
	private syntaxDiagnostics: ObjectMap<Diagnostic[]>;
172 173
	private readonly currentDiagnostics: DiagnosticCollection;
	private readonly bufferSyncSupport: BufferSyncSupport;
174

175
	private formattingProvider: FormattingProvider;
176
	private formattingProviderRegistration: Disposable | null;
177
	private typingsStatus: TypingsStatus;
178
	private toUpdateOnConfigurationChanged: ({ updateConfiguration: () => void })[] = [];
179

M
Matt Bierner 已提交
180
	private _validate: boolean = true;
181

182 183
	private readonly disposables: Disposable[] = [];

184 185
	private versionDependentDisposables: Disposable[] = [];

M
Matt Bierner 已提交
186
	constructor(
M
Matt Bierner 已提交
187 188
		private readonly client: TypeScriptServiceClient,
		private readonly description: LanguageDescription
M
Matt Bierner 已提交
189
	) {
190 191
		this.bufferSyncSupport = new BufferSyncSupport(client, description.modeIds, {
			delete: (file: string) => {
192
				this.currentDiagnostics.delete(client.asUrl(file));
193
			}
194
		});
195 196
		this.syntaxDiagnostics = Object.create(null);
		this.currentDiagnostics = languages.createDiagnosticCollection(description.id);
197

198
		this.typingsStatus = new TypingsStatus(client);
199
		new AtaProgressReporter(client);
200

201
		workspace.onDidChangeConfiguration(this.configurationChanged, this, this.disposables);
202 203 204 205
		this.configurationChanged();

		client.onReady().then(() => {
			this.registerProviders(client);
206
			this.bufferSyncSupport.listen();
207 208 209 210 211
		}, () => {
			// Nothing to do here. The client did show a message;
		});
	}

212 213 214 215 216 217 218 219 220 221 222 223
	public dispose(): void {
		if (this.formattingProviderRegistration) {
			this.formattingProviderRegistration.dispose();
		}

		while (this.disposables.length) {
			const obj = this.disposables.pop();
			if (obj) {
				obj.dispose();
			}
		}

224 225 226 227 228 229 230
		while (this.versionDependentDisposables.length) {
			const obj = this.versionDependentDisposables.pop();
			if (obj) {
				obj.dispose();
			}
		}

231 232 233 234 235
		this.typingsStatus.dispose();
		this.currentDiagnostics.dispose();
		this.bufferSyncSupport.dispose();
	}

236
	private registerProviders(client: TypeScriptServiceClient): void {
M
Matt Bierner 已提交
237
		const selector = this.description.modeIds;
M
Matt Bierner 已提交
238
		const config = workspace.getConfiguration(this.id);
239

240 241 242 243
		const completionItemProvider = new CompletionItemProvider(client, this.typingsStatus);
		completionItemProvider.updateConfiguration();
		this.toUpdateOnConfigurationChanged.push(completionItemProvider);
		this.disposables.push(languages.registerCompletionItemProvider(selector, completionItemProvider, '.'));
244

245 246
		this.disposables.push(languages.registerCompletionItemProvider(selector, new DirectiveCommentCompletionProvider(client), '@'));

247 248
		this.formattingProvider = new FormattingProvider(client);
		this.formattingProvider.updateConfiguration(config);
249
		this.disposables.push(languages.registerOnTypeFormattingEditProvider(selector, this.formattingProvider, ';', '}', '\n'));
250
		if (this.formattingProvider.isEnabled()) {
M
Matt Bierner 已提交
251
			this.formattingProviderRegistration = languages.registerDocumentRangeFormattingEditProvider(selector, this.formattingProvider);
252
		}
253

254 255 256
		const jsDocCompletionProvider = new JsDocCompletionProvider(client);
		jsDocCompletionProvider.updateConfiguration();
		this.disposables.push(languages.registerCompletionItemProvider(selector, jsDocCompletionProvider, '*'));
257

258 259 260 261 262 263 264
		this.disposables.push(languages.registerHoverProvider(selector, new HoverProvider(client)));
		this.disposables.push(languages.registerDefinitionProvider(selector, new DefinitionProvider(client)));
		this.disposables.push(languages.registerDocumentHighlightProvider(selector, new DocumentHighlightProvider(client)));
		this.disposables.push(languages.registerReferenceProvider(selector, new ReferenceProvider(client)));
		this.disposables.push(languages.registerDocumentSymbolProvider(selector, new DocumentSymbolProvider(client)));
		this.disposables.push(languages.registerSignatureHelpProvider(selector, new SignatureHelpProvider(client), '(', ','));
		this.disposables.push(languages.registerRenameProvider(selector, new RenameProvider(client)));
M
Matt Bierner 已提交
265

266
		this.disposables.push(languages.registerCodeActionsProvider(selector, new CodeActionProvider(client, this.description.id)));
M
Matt Bierner 已提交
267
		//this.disposables.push(languages.registerCodeActionsProvider(selector, new RefactorProvider(client, this.description.id)));
268
		this.registerVersionDependentProviders();
269

270
		this.description.modeIds.forEach(modeId => {
271
			this.disposables.push(languages.registerWorkspaceSymbolProvider(new WorkspaceSymbolProvider(client, modeId)));
272

273 274 275 276 277 278 279 280 281 282 283
			const referenceCodeLensProvider = new ReferenceCodeLensProvider(client, modeId);
			referenceCodeLensProvider.updateConfiguration();
			this.toUpdateOnConfigurationChanged.push(referenceCodeLensProvider);
			this.disposables.push(languages.registerCodeLensProvider(selector, referenceCodeLensProvider));

			const implementationCodeLensProvider = new ImplementationCodeLensProvider(client, modeId);
			implementationCodeLensProvider.updateConfiguration();
			this.toUpdateOnConfigurationChanged.push(implementationCodeLensProvider);
			this.disposables.push(languages.registerCodeLensProvider(selector, implementationCodeLensProvider));


284
			this.disposables.push(languages.setLanguageConfiguration(modeId, {
285 286
				indentationRules: {
					// ^(.*\*/)?\s*\}.*$
287
					decreaseIndentPattern: /^((?!.*?\/\*).*\*\/)?\s*[\}\]\)].*$/,
288
					// ^.*\{[^}"']*$
289 290
					increaseIndentPattern: /^.*(\{[^}"'`]*|\([^)"'`]*|\[[^\]"'`]*)$/,
					indentNextLinePattern: /^\s*(for|while|if|else)\b(?!.*[;{}]\s*(\/\/.*|\/[*].*[*]\/\s*)?$)/
291 292 293 294 295 296 297 298
				},
				wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g,
				onEnterRules: [
					{
						// e.g. /** | */
						beforeText: /^\s*\/\*\*(?!\/)([^\*]|\*(?!\/))*$/,
						afterText: /^\s*\*\/$/,
						action: { indentAction: IndentAction.IndentOutdent, appendText: ' * ' }
M
Matt Bierner 已提交
299
					}, {
300 301 302
						// e.g. /** ...|
						beforeText: /^\s*\/\*\*(?!\/)([^\*]|\*(?!\/))*$/,
						action: { indentAction: IndentAction.None, appendText: ' * ' }
M
Matt Bierner 已提交
303
					}, {
304 305 306
						// e.g.  * ...|
						beforeText: /^(\t|(\ \ ))*\ \*(\ ([^\*]|\*(?!\/))*)?$/,
						action: { indentAction: IndentAction.None, appendText: '* ' }
M
Matt Bierner 已提交
307
					}, {
308 309 310
						// e.g.  */|
						beforeText: /^(\t|(\ \ ))*\ \*\/\s*$/,
						action: { indentAction: IndentAction.None, removeText: 1 }
311 312 313 314 315
					},
					{
						// e.g.  *-----*/|
						beforeText: /^(\t|(\ \ ))*\ \*[^/]*\*\/\s*$/,
						action: { indentAction: IndentAction.None, removeText: 1 }
316
					}
317
				]
318
			}));
319 320 321

			const EMPTY_ELEMENTS: string[] = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'menuitem', 'meta', 'param', 'source', 'track', 'wbr'];

322
			this.disposables.push(languages.setLanguageConfiguration('jsx-tags', {
323 324 325 326 327 328 329 330 331 332 333 334
				wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\@\$\^\&\*\(\)\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\s]+)/g,
				onEnterRules: [
					{
						beforeText: new RegExp(`<(?!(?:${EMPTY_ELEMENTS.join('|')}))([_:\\w][_:\\w-.\\d]*)([^/>]*(?!/)>)[^<]*$`, 'i'),
						afterText: /^<\/([_:\w][_:\w-.\d]*)\s*>$/i,
						action: { indentAction: IndentAction.IndentOutdent }
					},
					{
						beforeText: new RegExp(`<(?!(?:${EMPTY_ELEMENTS.join('|')}))(\\w[\\w\\d]*)([^/>]*(?!/)>)[^<]*$`, 'i'),
						action: { indentAction: IndentAction.Indent }
					}
				],
335
			}));
336 337 338 339
		});
	}

	private configurationChanged(): void {
M
Matt Bierner 已提交
340
		const config = workspace.getConfiguration(this.id);
341
		this.updateValidate(config.get(validateSetting, true));
342

343 344
		if (this.formattingProvider) {
			this.formattingProvider.updateConfiguration(config);
345 346
			if (!this.formattingProvider.isEnabled() && this.formattingProviderRegistration) {
				this.formattingProviderRegistration.dispose();
347
				this.formattingProviderRegistration = null;
348 349 350 351

			} else if (this.formattingProvider.isEnabled() && !this.formattingProviderRegistration) {
				this.formattingProviderRegistration = languages.registerDocumentRangeFormattingEditProvider(this.description.modeIds, this.formattingProvider);
			}
352
		}
353 354 355

		for (const toUpdate of this.toUpdateOnConfigurationChanged) {
			toUpdate.updateConfiguration();
356
		}
357 358
	}

359 360
	public handles(file: string, doc: TextDocument): boolean {
		if (doc && this.description.modeIds.indexOf(doc.languageId) >= 0) {
D
Dirk Baeumer 已提交
361 362
			return true;
		}
363 364 365 366 367

		if (this.bufferSyncSupport.handles(file)) {
			return true;
		}

M
Matt Bierner 已提交
368
		const basename = path.basename(file);
369 370 371 372
		if (!!basename && basename === this.description.configFile) {
			return true;
		}
		return false;
373 374
	}

375 376 377 378
	public get id(): string {
		return this.description.id;
	}

379 380 381 382
	public get diagnosticSource(): string {
		return this.description.diagnosticSource;
	}

383
	private updateValidate(value: boolean) {
384 385 386
		if (this._validate === value) {
			return;
		}
387
		this._validate = value;
388
		this.bufferSyncSupport.validate = value;
389 390 391 392 393 394 395 396 397 398 399 400 401
		if (value) {
			this.triggerAllDiagnostics();
		} else {
			this.syntaxDiagnostics = Object.create(null);
			this.currentDiagnostics.clear();
		}
	}

	public reInitialize(): void {
		this.currentDiagnostics.clear();
		this.syntaxDiagnostics = Object.create(null);
		this.bufferSyncSupport.reOpenDocuments();
		this.bufferSyncSupport.requestAllDiagnostics();
402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425
		this.registerVersionDependentProviders();
	}

	private registerVersionDependentProviders(): void {
		while (this.versionDependentDisposables.length) {
			const obj = this.versionDependentDisposables.pop();
			if (obj) {
				obj.dispose();
			}
		}

		this.versionDependentDisposables = [];
		if (!this.client) {
			return;
		}

		const selector = this.description.modeIds;
		if (this.client.apiVersion.has220Features()) {
			this.versionDependentDisposables.push(languages.registerImplementationProvider(selector, new ImplementationProvider(this.client)));
		}

		if (this.client.apiVersion.has213Features()) {
			this.versionDependentDisposables.push(languages.registerTypeDefinitionProvider(selector, new TypeDefintionProvider(this.client)));
		}
426 427 428 429 430 431 432 433 434 435 436
	}

	public triggerAllDiagnostics(): void {
		this.bufferSyncSupport.requestAllDiagnostics();
	}

	public syntaxDiagnosticsReceived(file: string, diagnostics: Diagnostic[]): void {
		this.syntaxDiagnostics[file] = diagnostics;
	}

	public semanticDiagnosticsReceived(file: string, diagnostics: Diagnostic[]): void {
M
Matt Bierner 已提交
437
		const syntaxMarkers = this.syntaxDiagnostics[file];
438 439 440 441
		if (syntaxMarkers) {
			delete this.syntaxDiagnostics[file];
			diagnostics = syntaxMarkers.concat(diagnostics);
		}
442
		this.currentDiagnostics.set(this.client.asUrl(file), diagnostics);
443
	}
444 445

	public configFileDiagnosticsReceived(file: string, diagnostics: Diagnostic[]): void {
446
		this.currentDiagnostics.set(this.client.asUrl(file), diagnostics);
447
	}
448 449 450 451
}

class TypeScriptServiceClientHost implements ITypescriptServiceClientHost {
	private client: TypeScriptServiceClient;
452
	private languages: LanguageProvider[] = [];
M
Matt Bierner 已提交
453
	private languagePerId: Map<string, LanguageProvider>;
454
	private readonly disposables: Disposable[] = [];
M
Matt Bierner 已提交
455
	private readonly versionStatus: VersionStatus;
E
Erich Gamma 已提交
456

M
Matt Bierner 已提交
457 458
	constructor(
		descriptions: LanguageDescription[],
459 460
		workspaceState: Memento,
		plugins: TypeScriptServerPlugin[]
M
Matt Bierner 已提交
461 462
	) {
		const handleProjectCreateOrDelete = () => {
E
Erich Gamma 已提交
463 464 465
			this.client.execute('reloadProjects', null, false);
			this.triggerAllDiagnostics();
		};
M
Matt Bierner 已提交
466
		const handleProjectChange = () => {
D
Dirk Baeumer 已提交
467 468 469
			setTimeout(() => {
				this.triggerAllDiagnostics();
			}, 1500);
A
tslint  
Alex Dima 已提交
470
		};
471 472 473 474 475
		const configFileWatcher = workspace.createFileSystemWatcher('**/[tj]sconfig.json');
		this.disposables.push(configFileWatcher);
		configFileWatcher.onDidCreate(handleProjectCreateOrDelete, this, this.disposables);
		configFileWatcher.onDidDelete(handleProjectCreateOrDelete, this, this.disposables);
		configFileWatcher.onDidChange(handleProjectChange, this, this.disposables);
E
Erich Gamma 已提交
476

M
Matt Bierner 已提交
477 478 479
		this.versionStatus = new VersionStatus();
		this.disposables.push(this.versionStatus);

M
Matt Bierner 已提交
480
		this.client = new TypeScriptServiceClient(this, workspaceState, this.versionStatus, plugins, this.disposables);
M
Matt Bierner 已提交
481
		this.languagePerId = new Map();
482
		for (const description of descriptions) {
M
Matt Bierner 已提交
483
			const manager = new LanguageProvider(this.client, description);
484
			this.languages.push(manager);
485
			this.disposables.push(manager);
M
Matt Bierner 已提交
486
			this.languagePerId.set(description.id, manager);
487
		}
488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508

		this.client.onReady().then(() => {
			if (!this.client.apiVersion.has230Features()) {
				return;
			}

			const langauges = new Set<string>();
			for (const plugin of plugins) {
				for (const language of plugin.languages) {
					langauges.add(language);
				}
			}
			if (langauges.size) {
				const description: LanguageDescription = {
					id: 'typescript-plugins',
					modeIds: Array.from(langauges.values()),
					diagnosticSource: 'ts-plugins'
				};
				const manager = new LanguageProvider(this.client, description);
				this.languages.push(manager);
				this.disposables.push(manager);
M
Matt Bierner 已提交
509
				this.languagePerId.set(description.id, manager);
510 511
			}
		});
512 513 514 515

		this.client.onTsServerStarted(() => {
			this.triggerAllDiagnostics();
		});
516 517 518 519 520 521 522 523 524
	}

	public dispose(): void {
		while (this.disposables.length) {
			const obj = this.disposables.pop();
			if (obj) {
				obj.dispose();
			}
		}
E
Erich Gamma 已提交
525 526 527 528 529 530
	}

	public get serviceClient(): TypeScriptServiceClient {
		return this.client;
	}

D
Dirk Baeumer 已提交
531 532 533 534 535
	public reloadProjects(): void {
		this.client.execute('reloadProjects', null, false);
		this.triggerAllDiagnostics();
	}

D
Dirk Baeumer 已提交
536 537 538 539
	public handles(file: string): boolean {
		return !!this.findLanguage(file);
	}

540 541
	public goToProjectConfig(
		isTypeScriptProject: boolean,
542
		resource: Uri
543
	): Thenable<TextEditor | undefined> | undefined {
544
		const rootPath = this.client.getWorkspaceRootForResource(resource);
545 546 547 548 549 550 551 552 553
		if (!rootPath) {
			window.showInformationMessage(
				localize(
					'typescript.projectConfigNoWorkspace',
					'Please open a folder in VS Code to use a TypeScript or JavaScript project'));
			return;
		}

		const file = this.client.normalizePath(resource);
554 555
		// TSServer errors when 'projectInfo' is invoked on a non js/ts file
		if (!file || !this.handles(file)) {
556 557 558 559 560 561 562
			window.showWarningMessage(
				localize(
					'typescript.projectConfigUnsupportedFile',
					'Could not determine TypeScript or JavaScript project. Unsupported file type'));
			return;
		}

563
		return this.client.execute('projectInfo', { file, needFileNameList: false } as protocol.ProjectInfoRequestArgs).then(res => {
564
			if (!res || !res.body) {
565 566
				return window.showWarningMessage(localize('typescript.projectConfigCouldNotGetInfo', 'Could not determine TypeScript or JavaScript project'))
					.then(() => void 0);
567 568 569
			}

			const { configFileName } = res.body;
570
			if (configFileName && !isImplicitProjectConfigFile(configFileName)) {
571
				return workspace.openTextDocument(configFileName)
572 573
					.then(doc =>
						window.showTextDocument(doc, window.activeTextEditor ? window.activeTextEditor.viewColumn : undefined));
574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590
			}

			return window.showInformationMessage<ProjectConfigMessageItem>(
				(isTypeScriptProject
					? localize('typescript.noTypeScriptProjectConfig', 'File is not part of a TypeScript project')
					: localize('typescript.noJavaScriptProjectConfig', 'File is not part of a JavaScript project')
				), {
					title: isTypeScriptProject
						? localize('typescript.configureTsconfigQuickPick', 'Configure tsconfig.json')
						: localize('typescript.configureJsconfigQuickPick', 'Configure jsconfig.json'),
					id: ProjectConfigAction.CreateConfig
				}, {
					title: localize('typescript.projectConfigLearnMore', 'Learn More'),
					id: ProjectConfigAction.LearnMore
				}).then(selected => {
					switch (selected && selected.id) {
						case ProjectConfigAction.CreateConfig:
591
							return openOrCreateConfigFile(isTypeScriptProject, rootPath);
592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607

						case ProjectConfigAction.LearnMore:
							if (isTypeScriptProject) {
								commands.executeCommand('vscode.open', Uri.parse('https://go.microsoft.com/fwlink/?linkid=841896'));
							} else {
								commands.executeCommand('vscode.open', Uri.parse('https://go.microsoft.com/fwlink/?linkid=759670'));
							}
							return;

						default:
							return Promise.resolve(undefined);
					}
				});
		});
	}

608
	private findLanguage(file: string): Thenable<LanguageProvider | null> {
609
		return workspace.openTextDocument(this.client.asUrl(file)).then((doc: TextDocument) => {
610 611 612 613
			for (const language of this.languages) {
				if (language.handles(file, doc)) {
					return language;
				}
614
			}
615 616
			return null;
		}, () => null);
E
Erich Gamma 已提交
617 618 619
	}

	private triggerAllDiagnostics() {
M
Matt Bierner 已提交
620 621 622
		for (const language of this.languagePerId.values()) {
			language.triggerAllDiagnostics();
		}
E
Erich Gamma 已提交
623 624 625 626
	}

	/* internal */ populateService(): void {
		// See https://github.com/Microsoft/TypeScript/issues/5530
M
Matt Bierner 已提交
627 628 629 630
		workspace.saveAll(false).then(() => {
			for (const language of this.languagePerId.values()) {
				language.reInitialize();
			}
E
Erich Gamma 已提交
631 632 633 634
		});
	}

	/* internal */ syntaxDiagnosticsReceived(event: Proto.DiagnosticEvent): void {
635
		const body = event.body;
636
		if (body && body.diagnostics) {
637 638 639 640 641
			this.findLanguage(body.file).then(language => {
				if (language) {
					language.syntaxDiagnosticsReceived(body.file, this.createMarkerDatas(body.diagnostics, language.diagnosticSource));
				}
			});
E
Erich Gamma 已提交
642 643 644 645
		}
	}

	/* internal */ semanticDiagnosticsReceived(event: Proto.DiagnosticEvent): void {
646
		const body = event.body;
647
		if (body && body.diagnostics) {
648 649 650 651 652
			this.findLanguage(body.file).then(language => {
				if (language) {
					language.semanticDiagnosticsReceived(body.file, this.createMarkerDatas(body.diagnostics, language.diagnosticSource));
				}
			});
E
Erich Gamma 已提交
653 654 655
		}
	}

656
	/* internal */ configFileDiagnosticsReceived(event: Proto.ConfigFileDiagnosticEvent): void {
657
		// See https://github.com/Microsoft/TypeScript/issues/10384
658
		const body = event.body;
659
		if (!body || !body.diagnostics || !body.configFile) {
660 661 662
			return;
		}

663 664 665
		// TODO: restore opening trigger file?
		//     body.triggerFile ? this.findLanguage(body.triggerFile)
		(this.findLanguage(body.configFile)).then(language => {
666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688
			if (!language) {
				return;
			}
			if (body.diagnostics.length === 0) {
				language.configFileDiagnosticsReceived(body.configFile, []);
			} else if (body.diagnostics.length >= 1) {
				workspace.openTextDocument(Uri.file(body.configFile)).then((document) => {
					let curly: [number, number, number] | undefined = undefined;
					let nonCurly: [number, number, number] | undefined = undefined;
					let diagnostic: Diagnostic;
					for (let index = 0; index < document.lineCount; index++) {
						const line = document.lineAt(index);
						const text = line.text;
						const firstNonWhitespaceCharacterIndex = line.firstNonWhitespaceCharacterIndex;
						if (firstNonWhitespaceCharacterIndex < text.length) {
							if (text.charAt(firstNonWhitespaceCharacterIndex) === '{') {
								curly = [index, firstNonWhitespaceCharacterIndex, firstNonWhitespaceCharacterIndex + 1];
								break;
							} else {
								const matches = /\s*([^\s]*)(?:\s*|$)/.exec(text.substr(firstNonWhitespaceCharacterIndex));
								if (matches && matches.length >= 1) {
									nonCurly = [index, firstNonWhitespaceCharacterIndex, firstNonWhitespaceCharacterIndex + matches[1].length];
								}
689
							}
690
						}
691
					}
692 693 694 695 696 697 698 699 700 701 702 703 704 705 706
					const match = curly || nonCurly;
					if (match) {
						diagnostic = new Diagnostic(new Range(match[0], match[1], match[0], match[2]), body.diagnostics[0].text);
					} else {
						diagnostic = new Diagnostic(new Range(0, 0, 0, 0), body.diagnostics[0].text);
					}
					if (diagnostic) {
						diagnostic.source = language.diagnosticSource;
						language.configFileDiagnosticsReceived(body.configFile, [diagnostic]);
					}
				}, _error => {
					language.configFileDiagnosticsReceived(body.configFile, [new Diagnostic(new Range(0, 0, 0, 0), body.diagnostics[0].text)]);
				});
			}
		});
707 708
	}

709
	private createMarkerDatas(diagnostics: Proto.Diagnostic[], source: string): Diagnostic[] {
M
Matt Bierner 已提交
710
		const result: Diagnostic[] = [];
E
Erich Gamma 已提交
711
		for (let diagnostic of diagnostics) {
712 713 714 715 716
			const { start, end, text } = diagnostic;
			const range = new Range(start.line - 1, start.offset - 1, end.line - 1, end.offset - 1);
			const converted = new Diagnostic(range, text);
			converted.severity = this.getDiagnosticSeverity(diagnostic);
			converted.source = diagnostic.source || source;
717
			converted.code = '' + diagnostic.code;
718
			result.push(converted);
E
Erich Gamma 已提交
719 720 721
		}
		return result;
	}
722 723 724 725 726 727 728 729 730 731 732 733 734

	private getDiagnosticSeverity(diagnostic: Proto.Diagnostic): DiagnosticSeverity {
		switch (diagnostic.category) {
			case PConst.DiagnosticCategory.error:
				return DiagnosticSeverity.Error;

			case PConst.DiagnosticCategory.warning:
				return DiagnosticSeverity.Warning;

			default:
				return DiagnosticSeverity.Error;
		}
	}
735
}