bufferSyncSupport.ts 9.1 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

D
Dirk Baeumer 已提交
6
import * as fs from 'fs';
M
Matt Bierner 已提交
7
import { CancellationTokenSource, Disposable, TextDocument, TextDocumentChangeEvent, TextDocumentContentChangeEvent, Uri, workspace, EventEmitter } from 'vscode';
E
Erich Gamma 已提交
8
import * as Proto from '../protocol';
9
import { ITypeScriptServiceClient } from '../typescriptService';
E
Erich Gamma 已提交
10
import { Delayer } from '../utils/async';
11
import { disposeAll } from '../utils/dispose';
M
Matt Bierner 已提交
12
import * as languageModeIds from '../utils/languageModeIds';
M
Matt Bierner 已提交
13
import API from '../utils/api';
M
Matt Bierner 已提交
14

E
Erich Gamma 已提交
15 16

interface IDiagnosticRequestor {
17
	requestDiagnostic(resource: Uri): void;
E
Erich Gamma 已提交
18 19
}

20 21
function mode2ScriptKind(mode: string): 'TS' | 'TSX' | 'JS' | 'JSX' | undefined {
	switch (mode) {
22 23 24 25
		case languageModeIds.typescript: return 'TS';
		case languageModeIds.typescriptreact: return 'TSX';
		case languageModeIds.javascript: return 'JS';
		case languageModeIds.javascriptreact: return 'JSX';
26 27 28
	}
	return undefined;
}
29

E
Erich Gamma 已提交
30 31
class SyncedBuffer {

M
Matt Bierner 已提交
32 33 34 35
	constructor(
		private readonly document: TextDocument,
		private readonly filepath: string,
		private readonly diagnosticRequestor: IDiagnosticRequestor,
36
		private readonly client: ITypeScriptServiceClient
M
Matt Bierner 已提交
37
	) { }
E
Erich Gamma 已提交
38 39

	public open(): void {
40
		const args: Proto.OpenRequestArgs = {
D
Dirk Baeumer 已提交
41
			file: this.filepath,
42
			fileContent: this.document.getText(),
E
Erich Gamma 已提交
43
		};
44

M
Matt Bierner 已提交
45
		if (this.client.apiVersion.gte(API.v203)) {
46
			const scriptKind = mode2ScriptKind(this.document.languageId);
47 48
			if (scriptKind) {
				args.scriptKindName = scriptKind;
49
			}
50
		}
51

M
Matt Bierner 已提交
52
		if (this.client.apiVersion.gte(API.v230)) {
53
			args.projectRootPath = this.client.getWorkspaceRootForResource(this.document.uri);
54
		}
55

M
Matt Bierner 已提交
56
		if (this.client.apiVersion.gte(API.v240)) {
57 58 59 60 61
			const tsPluginsForDocument = this.client.plugins
				.filter(x => x.languages.indexOf(this.document.languageId) >= 0);

			if (tsPluginsForDocument.length) {
				(args as any).plugins = tsPluginsForDocument.map(plugin => plugin.name);
62 63 64
			}
		}

E
Erich Gamma 已提交
65 66 67
		this.client.execute('open', args, false);
	}

68 69 70 71
	public get lineCount(): number {
		return this.document.lineCount;
	}

E
Erich Gamma 已提交
72
	public close(): void {
M
Matt Bierner 已提交
73
		const args: Proto.FileRequestArgs = {
E
Erich Gamma 已提交
74 75 76 77 78
			file: this.filepath
		};
		this.client.execute('close', args, false);
	}

M
Matt Bierner 已提交
79 80
	public onContentChanged(events: TextDocumentContentChangeEvent[]): void {
		const filePath = this.client.normalizePath(this.document.uri);
E
Erich Gamma 已提交
81 82 83 84
		if (!filePath) {
			return;
		}

M
Matt Bierner 已提交
85
		for (const { range, text } of events) {
M
Matt Bierner 已提交
86
			const args: Proto.ChangeRequestArgs = {
E
Erich Gamma 已提交
87 88 89 90 91 92 93 94 95
				file: filePath,
				line: range.start.line + 1,
				offset: range.start.character + 1,
				endLine: range.end.line + 1,
				endOffset: range.end.character + 1,
				insertString: text
			};
			this.client.execute('change', args, false);
		}
96
		this.diagnosticRequestor.requestDiagnostic(this.document.uri);
E
Erich Gamma 已提交
97 98 99
	}
}

100 101 102 103 104 105 106 107
class SyncedBufferMap {
	private readonly _map = new Map<string, SyncedBuffer>();

	constructor(
		private readonly _normalizePath: (resource: Uri) => string | null
	) { }

	public has(resource: Uri): boolean {
M
Matt Bierner 已提交
108
		const file = this.toKey(resource);
109 110 111 112
		return !!file && this._map.has(file);
	}

	public get(resource: Uri): SyncedBuffer | undefined {
M
Matt Bierner 已提交
113
		const file = this.toKey(resource);
114 115 116 117
		return file ? this._map.get(file) : undefined;
	}

	public set(resource: Uri, buffer: SyncedBuffer) {
M
Matt Bierner 已提交
118
		const file = this.toKey(resource);
119 120 121 122 123 124
		if (file) {
			this._map.set(file, buffer);
		}
	}

	public delete(resource: Uri): void {
M
Matt Bierner 已提交
125
		const file = this.toKey(resource);
126 127 128 129 130 131 132 133 134 135 136 137 138
		if (file) {
			this._map.delete(file);
		}
	}

	public get allBuffers(): Iterable<SyncedBuffer> {
		return this._map.values();
	}

	public get allResources(): Iterable<string> {
		return this._map.keys();
	}

M
Matt Bierner 已提交
139 140 141
	private toKey(resource: Uri): string | null {
		return this._normalizePath(resource);
	}
142 143
}

M
Matt Bierner 已提交
144

E
Erich Gamma 已提交
145 146
export default class BufferSyncSupport {

147
	private readonly client: ITypeScriptServiceClient;
E
Erich Gamma 已提交
148

149
	private _validate: boolean;
150 151
	private readonly modeIds: Set<string>;
	private readonly disposables: Disposable[] = [];
152
	private readonly syncedBuffers: SyncedBufferMap;
E
Erich Gamma 已提交
153

M
Matt Bierner 已提交
154
	private readonly pendingDiagnostics = new Map<string, number>();
155
	private readonly diagnosticDelayer: Delayer<any>;
M
Matt Bierner 已提交
156
	private pendingGetErr: { request: Promise<any>, files: string[], token: CancellationTokenSource } | undefined;
E
Erich Gamma 已提交
157

M
💄  
Matt Bierner 已提交
158
	constructor(
159
		client: ITypeScriptServiceClient,
M
💄  
Matt Bierner 已提交
160 161 162
		modeIds: string[],
		validate: boolean
	) {
E
Erich Gamma 已提交
163
		this.client = client;
164
		this.modeIds = new Set<string>(modeIds);
165
		this._validate = validate;
E
Erich Gamma 已提交
166

167
		this.diagnosticDelayer = new Delayer<any>(300);
E
Erich Gamma 已提交
168

169
		this.syncedBuffers = new SyncedBufferMap(path => this.client.normalizePath(path));
170 171
	}

M
Matt Bierner 已提交
172 173 174
	private readonly _onDelete = new EventEmitter<Uri>();
	public readonly onDelete = this._onDelete.event;

175
	public listen(): void {
176
		workspace.onDidOpenTextDocument(this.openTextDocument, this, this.disposables);
177 178
		workspace.onDidCloseTextDocument(this.onDidCloseTextDocument, this, this.disposables);
		workspace.onDidChangeTextDocument(this.onDidChangeTextDocument, this, this.disposables);
179
		workspace.textDocuments.forEach(this.openTextDocument, this);
E
Erich Gamma 已提交
180 181
	}

182 183 184 185
	public set validate(value: boolean) {
		this._validate = value;
	}

186
	public handles(resource: Uri): boolean {
187
		return this.syncedBuffers.has(resource);
188 189
	}

E
Erich Gamma 已提交
190
	public reOpenDocuments(): void {
191
		for (const buffer of this.syncedBuffers.allBuffers) {
M
Matt Bierner 已提交
192 193
			buffer.open();
		}
E
Erich Gamma 已提交
194 195 196
	}

	public dispose(): void {
197
		disposeAll(this.disposables);
M
Matt Bierner 已提交
198
		this._onDelete.dispose();
E
Erich Gamma 已提交
199 200
	}

201
	public openTextDocument(document: TextDocument): void {
202
		if (!this.modeIds.has(document.languageId)) {
E
Erich Gamma 已提交
203 204
			return;
		}
M
Matt Bierner 已提交
205 206
		const resource = document.uri;
		const filepath = this.client.normalizePath(resource);
E
Erich Gamma 已提交
207 208 209
		if (!filepath) {
			return;
		}
210 211 212 213 214

		if (this.syncedBuffers.has(resource)) {
			return;
		}

M
Matt Bierner 已提交
215
		const syncedBuffer = new SyncedBuffer(document, filepath, this, this.client);
216
		this.syncedBuffers.set(resource, syncedBuffer);
E
Erich Gamma 已提交
217
		syncedBuffer.open();
218
		this.requestDiagnostic(resource);
E
Erich Gamma 已提交
219 220
	}

221
	public closeResource(resource: Uri): void {
222
		const syncedBuffer = this.syncedBuffers.get(resource);
E
Erich Gamma 已提交
223 224 225
		if (!syncedBuffer) {
			return;
		}
226
		this.syncedBuffers.delete(resource);
E
Erich Gamma 已提交
227
		syncedBuffer.close();
228
		if (!fs.existsSync(resource.fsPath)) {
M
Matt Bierner 已提交
229
			this._onDelete.fire(resource);
D
Dirk Baeumer 已提交
230 231
			this.requestAllDiagnostics();
		}
E
Erich Gamma 已提交
232 233
	}

234 235 236 237
	private onDidCloseTextDocument(document: TextDocument): void {
		this.closeResource(document.uri);
	}

238
	private onDidChangeTextDocument(e: TextDocumentChangeEvent): void {
239 240 241
		const syncedBuffer = this.syncedBuffers.get(e.document.uri);
		if (syncedBuffer) {
			syncedBuffer.onContentChanged(e.contentChanges);
M
Matt Bierner 已提交
242 243 244 245
			if (this.pendingGetErr) {
				this.pendingGetErr.token.cancel();
				this.pendingGetErr = undefined;
			}
E
Erich Gamma 已提交
246 247 248 249
		}
	}

	public requestAllDiagnostics() {
250 251 252
		if (!this._validate) {
			return;
		}
253
		for (const filePath of this.syncedBuffers.allResources) {
254
			this.pendingDiagnostics.set(filePath, Date.now());
M
Matt Bierner 已提交
255
		}
E
Erich Gamma 已提交
256 257
		this.diagnosticDelayer.trigger(() => {
			this.sendPendingDiagnostics();
258
		}, 200);
E
Erich Gamma 已提交
259 260
	}

261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
	public getErr(resources: Uri[]): any {
		const handledResources = resources.filter(resource => this.handles(resource));
		if (!handledResources.length) {
			return;
		}

		for (const resource of handledResources) {
			const file = this.client.normalizePath(resource);
			if (file) {
				this.pendingDiagnostics.set(file, Date.now());
			}
		}

		this.diagnosticDelayer.trigger(() => {
			this.sendPendingDiagnostics();
		}, 200);
	}

279
	public requestDiagnostic(resource: Uri): void {
M
Matt Bierner 已提交
280
		if (!this._validate) {
D
Dirk Baeumer 已提交
281 282
			return;
		}
283

284 285 286 287 288
		const file = this.client.normalizePath(resource);
		if (!file) {
			return;
		}

289
		this.pendingDiagnostics.set(file, Date.now());
290
		const buffer = this.syncedBuffers.get(resource);
291 292
		let delay = 300;
		if (buffer) {
293
			const lineCount = buffer.lineCount;
294 295
			delay = Math.min(Math.max(Math.ceil(lineCount / 20), 300), 800);
		}
296 297
		this.diagnosticDelayer.trigger(() => {
			this.sendPendingDiagnostics();
298
		}, delay);
E
Erich Gamma 已提交
299 300
	}

301 302 303 304 305
	public hasPendingDiagnostics(resource: Uri): boolean {
		const file = this.client.normalizePath(resource);
		return !file || this.pendingDiagnostics.has(file);
	}

E
Erich Gamma 已提交
306
	private sendPendingDiagnostics(): void {
307 308 309
		if (!this._validate) {
			return;
		}
M
Matt Bierner 已提交
310
		const files = new Set(Array.from(this.pendingDiagnostics.entries())
M
Matt Bierner 已提交
311
			.sort((a, b) => a[1] - b[1])
M
Matt Bierner 已提交
312
			.map(entry => entry[0]));
E
Erich Gamma 已提交
313 314

		// Add all open TS buffers to the geterr request. They might be visible
315
		for (const file of this.syncedBuffers.allResources) {
316
			if (!this.pendingDiagnostics.get(file)) {
M
Matt Bierner 已提交
317
				files.add(file);
E
Erich Gamma 已提交
318
			}
M
Matt Bierner 已提交
319
		}
E
Erich Gamma 已提交
320

M
Matt Bierner 已提交
321 322 323 324 325 326 327 328
		if (this.pendingGetErr) {
			for (const file of this.pendingGetErr.files) {
				files.add(file);
			}
		}

		if (files.size) {
			const fileList = Array.from(files);
329 330
			const args: Proto.GeterrRequestArgs = {
				delay: 0,
M
Matt Bierner 已提交
331 332 333 334 335
				files: fileList
			};
			const token = new CancellationTokenSource();

			const getErr = this.pendingGetErr = {
M
Matt Bierner 已提交
336
				request: this.client.executeAsync('geterr', args, token.token)
M
Matt Bierner 已提交
337 338 339 340 341 342 343 344
					.then(undefined, () => { })
					.then(() => {
						if (this.pendingGetErr === getErr) {
							this.pendingGetErr = undefined;
						}
					}),
				files: fileList,
				token
345 346
			};
		}
347
		this.pendingDiagnostics.clear();
E
Erich Gamma 已提交
348 349
	}
}