typescriptMain.ts 12.8 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 11

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

12
import { env, languages, commands, workspace, window, Uri, ExtensionContext, IndentAction, Diagnostic, DiagnosticCollection, Range, DocumentFilter } from 'vscode';
E
Erich Gamma 已提交
13

14 15 16 17 18
// This must be the first statement otherwise modules might got loaded with
// the wrong locale.
import * as nls from 'vscode-nls';
nls.config({locale: env.language});

19 20
import * as path from 'path';

E
Erich Gamma 已提交
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
import * as Proto from './protocol';
import TypeScriptServiceClient from './typescriptServiceClient';
import { ITypescriptServiceClientHost } from './typescriptService';

import HoverProvider from './features/hoverProvider';
import DefinitionProvider from './features/definitionProvider';
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';

37
import * as VersionStatus from './utils/versionStatus';
38
import * as ProjectStatus from './utils/projectStatus';
39

40 41
interface LanguageDescription {
	id: string;
42
	diagnosticSource: string;
43
	modeIds: string[];
44
	extensions: string[];
45 46
}

E
Erich Gamma 已提交
47 48 49
export function activate(context: ExtensionContext): void {
	let MODE_ID_TS = 'typescript';
	let MODE_ID_TSX = 'typescriptreact';
50 51
	let MODE_ID_JS = 'javascript';
	let MODE_ID_JSX = 'javascriptreact';
E
Erich Gamma 已提交
52

53 54 55
	let clientHost = new TypeScriptServiceClientHost([
		{
			id: 'typescript',
56
			diagnosticSource: 'ts',
57 58
			modeIds: [MODE_ID_TS, MODE_ID_TSX],
			extensions: ['.ts', '.tsx']
59 60 61
		},
		{
			id: 'javascript',
62
			diagnosticSource: 'js',
63 64
			modeIds: [MODE_ID_JS, MODE_ID_JSX],
			extensions: ['.js', '.jsx']
65 66 67
		}
	]);

E
Erich Gamma 已提交
68
	let client = clientHost.serviceClient;
D
Dirk Baeumer 已提交
69 70 71 72

	context.subscriptions.push(commands.registerCommand('typescript.reloadProjects', () => {
		clientHost.reloadProjects();
	}));
73

74 75 76 77
	context.subscriptions.push(commands.registerCommand('javascript.reloadProjects', () => {
		clientHost.reloadProjects();
	}));

78
	window.onDidChangeActiveTextEditor(VersionStatus.showHideStatus, null, context.subscriptions);
E
Erich Gamma 已提交
79
	client.onReady().then(() => {
80 81 82
		context.subscriptions.push(ProjectStatus.create(client,
			path => new Promise(resolve => setTimeout(() => resolve(clientHost.handles(path)), 750)),
			context.workspaceState));
E
Erich Gamma 已提交
83 84
	}, () => {
		// Nothing to do here. The client did show a message;
A
tslint  
Alex Dima 已提交
85
	});
E
Erich Gamma 已提交
86 87
}

88
const validateSetting = 'validate.enable';
E
Erich Gamma 已提交
89

90
class LanguageProvider {
E
Erich Gamma 已提交
91

92
	private description: LanguageDescription;
93
	private extensions: Map<boolean>;
94
	private syntaxDiagnostics: Map<Diagnostic[]>;
E
Erich Gamma 已提交
95
	private currentDiagnostics: DiagnosticCollection;
96 97
	private bufferSyncSupport: BufferSyncSupport;

98 99 100
	private completionItemProvider: CompletionItemProvider;
	private formattingProvider: FormattingProvider;

101 102
	private _validate: boolean;

103
	constructor(client: TypeScriptServiceClient, description: LanguageDescription) {
104
		this.description = description;
105 106
		this.extensions = Object.create(null);
		description.extensions.forEach(extension => this.extensions[extension] = true);
107 108
		this._validate = true;

109 110 111 112
		this.bufferSyncSupport = new BufferSyncSupport(client, description.modeIds, {
			delete: (file: string) => {
				this.currentDiagnostics.delete(Uri.file(file));
			}
113
		}, this.extensions);
114 115
		this.syntaxDiagnostics = Object.create(null);
		this.currentDiagnostics = languages.createDiagnosticCollection(description.id);
116

117

118 119 120 121 122
		workspace.onDidChangeConfiguration(this.configurationChanged, this);
		this.configurationChanged();

		client.onReady().then(() => {
			this.registerProviders(client);
123
			this.bufferSyncSupport.listen();
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
		}, () => {
			// Nothing to do here. The client did show a message;
		});
	}

	private registerProviders(client: TypeScriptServiceClient): void {
		let config = workspace.getConfiguration(this.id);

		this.completionItemProvider = new CompletionItemProvider(client);
		this.completionItemProvider.updateConfiguration(config);

		let hoverProvider = new HoverProvider(client);
		let definitionProvider = new DefinitionProvider(client);
		let documentHighlightProvider = new DocumentHighlightProvider(client);
		let referenceProvider = new ReferenceProvider(client);
		let documentSymbolProvider = new DocumentSymbolProvider(client);
		let signatureHelpProvider = new SignatureHelpProvider(client);
		let renameProvider = new RenameProvider(client);
		this.formattingProvider = new FormattingProvider(client);
		this.formattingProvider.updateConfiguration(config);

		this.description.modeIds.forEach(modeId => {
146 147 148 149 150 151 152 153 154 155 156
			let selector: DocumentFilter = { scheme: 'file', language: modeId };
			languages.registerCompletionItemProvider(selector, this.completionItemProvider, '.');
			languages.registerHoverProvider(selector, hoverProvider);
			languages.registerDefinitionProvider(selector, definitionProvider);
			languages.registerDocumentHighlightProvider(selector, documentHighlightProvider);
			languages.registerReferenceProvider(selector, referenceProvider);
			languages.registerDocumentSymbolProvider(selector, documentSymbolProvider);
			languages.registerSignatureHelpProvider(selector, signatureHelpProvider, '(', ',');
			languages.registerRenameProvider(selector, renameProvider);
			languages.registerDocumentRangeFormattingEditProvider(selector, this.formattingProvider);
			languages.registerOnTypeFormattingEditProvider(selector, this.formattingProvider, ';', '}', '\n');
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
			languages.registerWorkspaceSymbolProvider(new WorkspaceSymbolProvider(client, modeId));
			languages.setLanguageConfiguration(modeId, {
				indentationRules: {
					// ^(.*\*/)?\s*\}.*$
					decreaseIndentPattern: /^(.*\*\/)?\s*\}.*$/,
					// ^.*\{[^}"']*$
					increaseIndentPattern: /^.*\{[^}"']*$/
				},
				wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g,
				comments: {
					lineComment: '//',
					blockComment: ['/*', '*/']
				},
				brackets: [
					['{', '}'],
					['[', ']'],
					['(', ')'],
				],
				onEnterRules: [
					{
						// e.g. /** | */
						beforeText: /^\s*\/\*\*(?!\/)([^\*]|\*(?!\/))*$/,
						afterText: /^\s*\*\/$/,
						action: { indentAction: IndentAction.IndentOutdent, appendText: ' * ' }
					},
					{
						// e.g. /** ...|
						beforeText: /^\s*\/\*\*(?!\/)([^\*]|\*(?!\/))*$/,
						action: { indentAction: IndentAction.None, appendText: ' * ' }
					},
					{
						// e.g.  * ...|
						beforeText: /^(\t|(\ \ ))*\ \*(\ ([^\*]|\*(?!\/))*)?$/,
						action: { indentAction: IndentAction.None, appendText: '* ' }
					},
					{
						// e.g.  */|
						beforeText: /^(\t|(\ \ ))*\ \*\/\s*$/,
						action: { indentAction: IndentAction.None, removeText: 1 }
196 197 198 199 200
					},
					{
						// e.g.  *-----*/|
						beforeText: /^(\t|(\ \ ))*\ \*[^/]*\*\/\s*$/,
						action: { indentAction: IndentAction.None, removeText: 1 }
201 202 203 204 205 206 207
					}
				],

				__electricCharacterSupport: {
					docComment: { scope: 'comment.documentation', open: '/**', lineStart: ' * ', close: ' */' }
				},

208 209 210 211 212 213 214 215
				autoClosingPairs: [
					{ open: '{', close: '}' },
					{ open: '[', close: ']' },
					{ open: '(', close: ')' },
					{ open: '"', close: '"', notIn: ['string'] },
					{ open: '\'', close: '\'', notIn: ['string', 'comment'] },
					{ open: '`', close: '`', notIn: ['string', 'comment'] }
				]
216 217 218 219 220 221 222 223 224 225 226 227 228
			});
		});
	}

	private configurationChanged(): void {
		let config = workspace.getConfiguration(this.id);
		this.updateValidate(config.get(validateSetting, true));
		if (this.completionItemProvider) {
			this.completionItemProvider.updateConfiguration(config);
		}
		if (this.formattingProvider) {
			this.formattingProvider.updateConfiguration(config);
		}
229 230 231
	}

	public handles(file: string): boolean {
232 233
		let extension = path.extname(file);
		return (extension && this.extensions[extension]) || this.bufferSyncSupport.handles(file);
234 235
	}

236 237 238 239
	public get id(): string {
		return this.description.id;
	}

240 241 242 243
	public get diagnosticSource(): string {
		return this.description.diagnosticSource;
	}

244
	private updateValidate(value: boolean) {
245 246 247
		if (this._validate === value) {
			return;
		}
248
		this._validate = value;
249
		this.bufferSyncSupport.validate = value;
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284
		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();
	}

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

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

	public semanticDiagnosticsReceived(file: string, diagnostics: Diagnostic[]): void {
		let syntaxMarkers = this.syntaxDiagnostics[file];
		if (syntaxMarkers) {
			delete this.syntaxDiagnostics[file];
			diagnostics = syntaxMarkers.concat(diagnostics);
		}
		this.currentDiagnostics.set(Uri.file(file), diagnostics);
	}
}

class TypeScriptServiceClientHost implements ITypescriptServiceClientHost {
	private client: TypeScriptServiceClient;
285 286
	private languages: LanguageProvider[];
	private languagePerId: Map<LanguageProvider>;
E
Erich Gamma 已提交
287

288
	constructor(descriptions: LanguageDescription[]) {
E
Erich Gamma 已提交
289 290 291 292 293
		let handleProjectCreateOrDelete = () => {
			this.client.execute('reloadProjects', null, false);
			this.triggerAllDiagnostics();
		};
		let handleProjectChange = () => {
D
Dirk Baeumer 已提交
294 295 296
			setTimeout(() => {
				this.triggerAllDiagnostics();
			}, 1500);
A
tslint  
Alex Dima 已提交
297
		};
298
		let watcher = workspace.createFileSystemWatcher('**/[tj]sconfig.json');
E
Erich Gamma 已提交
299 300 301 302 303
		watcher.onDidCreate(handleProjectCreateOrDelete);
		watcher.onDidDelete(handleProjectCreateOrDelete);
		watcher.onDidChange(handleProjectChange);

		this.client = new TypeScriptServiceClient(this);
304 305 306
		this.languages = [];
		this.languagePerId = Object.create(null);
		descriptions.forEach(description => {
307
			let manager = new LanguageProvider(this.client, description);
308 309 310
			this.languages.push(manager);
			this.languagePerId[description.id] = manager;
		});
E
Erich Gamma 已提交
311 312 313 314 315 316
	}

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

D
Dirk Baeumer 已提交
317 318 319 320 321
	public reloadProjects(): void {
		this.client.execute('reloadProjects', null, false);
		this.triggerAllDiagnostics();
	}

D
Dirk Baeumer 已提交
322 323 324 325
	public handles(file: string): boolean {
		return !!this.findLanguage(file);
	}

326
	private findLanguage(file: string): LanguageProvider {
327 328 329 330 331 332 333
		for (let i = 0; i < this.languages.length; i++) {
			let language = this.languages[i];
			if (language.handles(file)) {
				return language;
			}
		}
		return null;
E
Erich Gamma 已提交
334 335 336
	}

	private triggerAllDiagnostics() {
337
		Object.keys(this.languagePerId).forEach(key => this.languagePerId[key].triggerAllDiagnostics());
E
Erich Gamma 已提交
338 339 340 341 342
	}

	/* internal */ populateService(): void {
		// See https://github.com/Microsoft/TypeScript/issues/5530
		workspace.saveAll(false).then((value) => {
343
			Object.keys(this.languagePerId).forEach(key => this.languagePerId[key].reInitialize());
E
Erich Gamma 已提交
344 345 346 347 348 349
		});
	}

	/* internal */ syntaxDiagnosticsReceived(event: Proto.DiagnosticEvent): void {
		let body = event.body;
		if (body.diagnostics) {
350 351
			let language = this.findLanguage(body.file);
			if (language) {
352
				language.syntaxDiagnosticsReceived(body.file, this.createMarkerDatas(body.diagnostics, language.diagnosticSource));
353
			}
E
Erich Gamma 已提交
354 355 356 357 358 359
		}
	}

	/* internal */ semanticDiagnosticsReceived(event: Proto.DiagnosticEvent): void {
		let body = event.body;
		if (body.diagnostics) {
360 361
			let language = this.findLanguage(body.file);
			if (language) {
362
				language.semanticDiagnosticsReceived(body.file, this.createMarkerDatas(body.diagnostics, language.diagnosticSource));
E
Erich Gamma 已提交
363 364 365 366
			}
		}
	}

367
	private createMarkerDatas(diagnostics: Proto.Diagnostic[], source: string): Diagnostic[] {
E
Erich Gamma 已提交
368 369
		let result: Diagnostic[] = [];
		for (let diagnostic of diagnostics) {
370
			let { start, end, text } = diagnostic;
E
Erich Gamma 已提交
371
			let range = new Range(start.line - 1, start.offset - 1, end.line - 1, end.offset - 1);
372 373 374
			let converted = new Diagnostic(range, text);
			converted.source = source;
			result.push(converted);
E
Erich Gamma 已提交
375 376 377 378
		}
		return result;
	}
}