extHostFileSystemEventService.ts 8.1 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

6
import { AsyncEmitter, Emitter, Event, IWaitUntil } from 'vs/base/common/event';
7
import { IRelativePattern, parse } from 'vs/base/common/glob';
8
import { URI } from 'vs/base/common/uri';
J
Johannes Rieken 已提交
9
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
10
import type * as vscode from 'vscode';
11
import { ExtHostFileSystemEventServiceShape, FileSystemEvents, IMainContext, SourceTargetPair, IWorkspaceEditDto } from './extHost.protocol';
12 13
import * as typeConverter from './extHostTypeConverters';
import { Disposable, WorkspaceEdit } from './extHostTypes';
14
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
15
import { FileOperation } from 'vs/platform/files/common/files';
16
import { CancellationToken } from 'vs/base/common/cancellation';
17
import { ILogService } from 'vs/platform/log/common/log';
E
Erich Gamma 已提交
18

19
class FileSystemWatcher implements vscode.FileSystemWatcher {
E
Erich Gamma 已提交
20

21 22 23
	private readonly _onDidCreate = new Emitter<vscode.Uri>();
	private readonly _onDidChange = new Emitter<vscode.Uri>();
	private readonly _onDidDelete = new Emitter<vscode.Uri>();
E
Erich Gamma 已提交
24 25 26
	private _disposable: Disposable;
	private _config: number;

J
Johannes Rieken 已提交
27
	get ignoreCreateEvents(): boolean {
E
Erich Gamma 已提交
28 29 30
		return Boolean(this._config & 0b001);
	}

J
Johannes Rieken 已提交
31
	get ignoreChangeEvents(): boolean {
E
Erich Gamma 已提交
32 33 34
		return Boolean(this._config & 0b010);
	}

J
Johannes Rieken 已提交
35
	get ignoreDeleteEvents(): boolean {
E
Erich Gamma 已提交
36 37 38
		return Boolean(this._config & 0b100);
	}

39
	constructor(dispatcher: Event<FileSystemEvents>, globPattern: string | IRelativePattern, ignoreCreateEvents?: boolean, ignoreChangeEvents?: boolean, ignoreDeleteEvents?: boolean) {
E
Erich Gamma 已提交
40 41

		this._config = 0;
J
Johannes Rieken 已提交
42
		if (ignoreCreateEvents) {
E
Erich Gamma 已提交
43 44
			this._config += 0b001;
		}
J
Johannes Rieken 已提交
45
		if (ignoreChangeEvents) {
E
Erich Gamma 已提交
46 47
			this._config += 0b010;
		}
J
Johannes Rieken 已提交
48
		if (ignoreDeleteEvents) {
E
Erich Gamma 已提交
49 50 51
			this._config += 0b100;
		}

B
Benjamin Pasero 已提交
52
		const parsedPattern = parse(globPattern);
53

54
		const subscription = dispatcher(events => {
E
Erich Gamma 已提交
55 56
			if (!ignoreCreateEvents) {
				for (let created of events.created) {
57
					const uri = URI.revive(created);
J
Johannes Rieken 已提交
58 59
					if (parsedPattern(uri.fsPath)) {
						this._onDidCreate.fire(uri);
E
Erich Gamma 已提交
60 61 62 63 64
					}
				}
			}
			if (!ignoreChangeEvents) {
				for (let changed of events.changed) {
65
					const uri = URI.revive(changed);
J
Johannes Rieken 已提交
66 67
					if (parsedPattern(uri.fsPath)) {
						this._onDidChange.fire(uri);
E
Erich Gamma 已提交
68 69 70 71 72
					}
				}
			}
			if (!ignoreDeleteEvents) {
				for (let deleted of events.deleted) {
73
					const uri = URI.revive(deleted);
J
Johannes Rieken 已提交
74 75
					if (parsedPattern(uri.fsPath)) {
						this._onDidDelete.fire(uri);
E
Erich Gamma 已提交
76 77 78 79 80 81 82 83 84 85 86 87
					}
				}
			}
		});

		this._disposable = Disposable.from(this._onDidCreate, this._onDidChange, this._onDidDelete, subscription);
	}

	dispose() {
		this._disposable.dispose();
	}

88
	get onDidCreate(): Event<vscode.Uri> {
E
Erich Gamma 已提交
89 90 91
		return this._onDidCreate.event;
	}

92
	get onDidChange(): Event<vscode.Uri> {
E
Erich Gamma 已提交
93 94 95
		return this._onDidChange.event;
	}

96
	get onDidDelete(): Event<vscode.Uri> {
E
Erich Gamma 已提交
97 98 99 100
		return this._onDidDelete.event;
	}
}

101
interface IExtensionListener<E> {
102
	extension: IExtensionDescription;
103
	(e: E): any;
104 105
}

106
export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServiceShape {
E
Erich Gamma 已提交
107

108
	private readonly _onFileSystemEvent = new Emitter<FileSystemEvents>();
109

110
	private readonly _onDidRenameFile = new Emitter<vscode.FileRenameEvent>();
111 112
	private readonly _onDidCreateFile = new Emitter<vscode.FileCreateEvent>();
	private readonly _onDidDeleteFile = new Emitter<vscode.FileDeleteEvent>();
113
	private readonly _onWillRenameFile = new AsyncEmitter<vscode.FileWillRenameEvent>();
114 115 116 117 118 119
	private readonly _onWillCreateFile = new AsyncEmitter<vscode.FileWillCreateEvent>();
	private readonly _onWillDeleteFile = new AsyncEmitter<vscode.FileWillDeleteEvent>();

	readonly onDidRenameFile: Event<vscode.FileRenameEvent> = this._onDidRenameFile.event;
	readonly onDidCreateFile: Event<vscode.FileCreateEvent> = this._onDidCreateFile.event;
	readonly onDidDeleteFile: Event<vscode.FileDeleteEvent> = this._onDidDeleteFile.event;
120

E
Erich Gamma 已提交
121

122 123
	constructor(
		mainContext: IMainContext,
124
		private readonly _logService: ILogService,
125
		private readonly _extHostDocumentsAndEditors: ExtHostDocumentsAndEditors
126 127
	) {
		//
E
Erich Gamma 已提交
128 129
	}

130 131 132
	//--- file events

	createFileSystemWatcher(globPattern: string | IRelativePattern, ignoreCreateEvents?: boolean, ignoreChangeEvents?: boolean, ignoreDeleteEvents?: boolean): vscode.FileSystemWatcher {
133
		return new FileSystemWatcher(this._onFileSystemEvent.event, globPattern, ignoreCreateEvents, ignoreChangeEvents, ignoreDeleteEvents);
E
Erich Gamma 已提交
134 135
	}

A
Alex Dima 已提交
136
	$onFileEvent(events: FileSystemEvents) {
137
		this._onFileSystemEvent.fire(events);
138 139
	}

140 141 142

	//--- file operations

143
	$onDidRunFileOperation(operation: FileOperation, files: SourceTargetPair[]): void {
144 145
		switch (operation) {
			case FileOperation.MOVE:
146
				this._onDidRenameFile.fire(Object.freeze({ files: files.map(f => ({ oldUri: URI.revive(f.source!), newUri: URI.revive(f.target) })) }));
147
				break;
148
			case FileOperation.DELETE:
149
				this._onDidDeleteFile.fire(Object.freeze({ files: files.map(f => URI.revive(f.target)) }));
150 151
				break;
			case FileOperation.CREATE:
152
				this._onDidCreateFile.fire(Object.freeze({ files: files.map(f => URI.revive(f.target)) }));
153 154 155
				break;
			default:
			//ignore, dont send
156
		}
E
Erich Gamma 已提交
157
	}
158

159

160
	getOnWillRenameFileEvent(extension: IExtensionDescription): Event<vscode.FileWillRenameEvent> {
161 162 163 164 165 166 167 168 169 170 171 172
		return this._createWillExecuteEvent(extension, this._onWillRenameFile);
	}

	getOnWillCreateFileEvent(extension: IExtensionDescription): Event<vscode.FileWillCreateEvent> {
		return this._createWillExecuteEvent(extension, this._onWillCreateFile);
	}

	getOnWillDeleteFileEvent(extension: IExtensionDescription): Event<vscode.FileWillDeleteEvent> {
		return this._createWillExecuteEvent(extension, this._onWillDeleteFile);
	}

	private _createWillExecuteEvent<E extends IWaitUntil>(extension: IExtensionDescription, emitter: AsyncEmitter<E>): Event<E> {
173
		return (listener, thisArg, disposables) => {
174
			const wrappedListener: IExtensionListener<E> = function wrapped(e: E) { listener.call(thisArg, e); };
175
			wrappedListener.extension = extension;
176
			return emitter.event(wrappedListener, undefined, disposables);
177 178
		};
	}
179

180
	async $onWillRunFileOperation(operation: FileOperation, files: SourceTargetPair[], timeout: number, token: CancellationToken): Promise<IWorkspaceEditDto | undefined> {
181 182
		switch (operation) {
			case FileOperation.MOVE:
183
				return await this._fireWillEvent(this._onWillRenameFile, { files: files.map(f => ({ oldUri: URI.revive(f.source!), newUri: URI.revive(f.target) })) }, timeout, token);
184
			case FileOperation.DELETE:
185
				return await this._fireWillEvent(this._onWillDeleteFile, { files: files.map(f => URI.revive(f.target)) }, timeout, token);
186
			case FileOperation.CREATE:
187
				return await this._fireWillEvent(this._onWillCreateFile, { files: files.map(f => URI.revive(f.target)) }, timeout, token);
188
		}
189
		return undefined;
190 191
	}

192
	private async _fireWillEvent<E extends IWaitUntil>(emitter: AsyncEmitter<E>, data: Omit<E, 'waitUntil'>, timeout: number, token: CancellationToken): Promise<IWorkspaceEditDto | undefined> {
193 194

		const edits: WorkspaceEdit[] = [];
J
Johannes Rieken 已提交
195

J
Johannes Rieken 已提交
196
		await emitter.fireAsync(data, token, async (thenable, listener) => {
J
Johannes Rieken 已提交
197
			// ignore all results except for WorkspaceEdits. Those are stored in an array.
198 199
			const now = Date.now();
			const result = await Promise.resolve(thenable);
J
Johannes Rieken 已提交
200 201 202
			if (result instanceof WorkspaceEdit) {
				edits.push(result);
			}
203 204

			if (Date.now() - now > timeout) {
J
Johannes Rieken 已提交
205
				this._logService.warn('SLOW file-participant', (<IExtensionListener<E>>listener).extension?.identifier);
206
			}
J
Johannes Rieken 已提交
207
		});
208

209
		if (token.isCancellationRequested) {
210
			return undefined;
211 212
		}

213 214 215 216 217 218 219 220 221
		if (edits.length === 0) {
			return undefined;
		}

		// concat all WorkspaceEdits collected via waitUntil-call and send them over to the renderer
		const dto: IWorkspaceEditDto = { edits: [] };
		for (let edit of edits) {
			let { edits } = typeConverter.WorkspaceEdit.from(edit, this._extHostDocumentsAndEditors);
			dto.edits = dto.edits.concat(edits);
222
		}
223
		return dto;
224
	}
E
Erich Gamma 已提交
225
}