extHostFileSystemEventService.ts 8.3 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, UriComponents } 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, MainContext, MainThreadTextEditorsShape, IWorkspaceFileEditDto, IWorkspaceTextEditDto } 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 { flatten } from 'vs/base/common/arrays';
17
import { CancellationToken } from 'vs/base/common/cancellation';
18
import { ILogService } from 'vs/platform/log/common/log';
E
Erich Gamma 已提交
19

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

111
	private readonly _onDidRenameFile = new Emitter<vscode.FileRenameEvent>();
112 113
	private readonly _onDidCreateFile = new Emitter<vscode.FileCreateEvent>();
	private readonly _onDidDeleteFile = new Emitter<vscode.FileDeleteEvent>();
114
	private readonly _onWillRenameFile = new AsyncEmitter<vscode.FileWillRenameEvent>();
115 116 117 118 119 120
	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;
121

E
Erich Gamma 已提交
122

123 124
	constructor(
		mainContext: IMainContext,
125
		private readonly _logService: ILogService,
126 127 128 129
		private readonly _extHostDocumentsAndEditors: ExtHostDocumentsAndEditors,
		private readonly _mainThreadTextEditors: MainThreadTextEditorsShape = mainContext.getProxy(MainContext.MainThreadTextEditors)
	) {
		//
E
Erich Gamma 已提交
130 131
	}

132 133 134
	//--- file events

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

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

142 143 144 145 146 147

	//--- file operations

	$onDidRunFileOperation(operation: FileOperation, target: UriComponents, source: UriComponents | undefined): void {
		switch (operation) {
			case FileOperation.MOVE:
148
				this._onDidRenameFile.fire(Object.freeze({ files: [{ oldUri: URI.revive(source!), newUri: URI.revive(target) }] }));
149
				break;
150
			case FileOperation.DELETE:
151
				this._onDidDeleteFile.fire(Object.freeze({ files: [URI.revive(target)] }));
152 153
				break;
			case FileOperation.CREATE:
154
				this._onDidCreateFile.fire(Object.freeze({ files: [URI.revive(target)] }));
155 156 157
				break;
			default:
			//ignore, dont send
158
		}
E
Erich Gamma 已提交
159
	}
160

161

162
	getOnWillRenameFileEvent(extension: IExtensionDescription): Event<vscode.FileWillRenameEvent> {
163 164 165 166 167 168 169 170 171 172 173 174
		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> {
175
		return (listener, thisArg, disposables) => {
176
			const wrappedListener: IExtensionListener<E> = function wrapped(e: E) { listener.call(thisArg, e); };
177
			wrappedListener.extension = extension;
178
			return emitter.event(wrappedListener, undefined, disposables);
179 180
		};
	}
181

182
	async $onWillRunFileOperation(operation: FileOperation, target: UriComponents, source: UriComponents | undefined, timeout: number, token: CancellationToken): Promise<any> {
183 184
		switch (operation) {
			case FileOperation.MOVE:
185
				await this._fireWillEvent(this._onWillRenameFile, { files: [{ oldUri: URI.revive(source!), newUri: URI.revive(target) }] }, timeout, token);
186
				break;
187
			case FileOperation.DELETE:
188
				await this._fireWillEvent(this._onWillDeleteFile, { files: [URI.revive(target)] }, timeout, token);
189 190
				break;
			case FileOperation.CREATE:
191
				await this._fireWillEvent(this._onWillCreateFile, { files: [URI.revive(target)] }, timeout, token);
192 193 194
				break;
			default:
			//ignore, dont send
195 196 197
		}
	}

198
	private async _fireWillEvent<E extends IWaitUntil>(emitter: AsyncEmitter<E>, data: Omit<E, 'waitUntil'>, timeout: number, token: CancellationToken): Promise<any> {
199 200

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

202
		await emitter.fireAsync(data, token, async (thenable, listener: IExtensionListener<E>) => {
J
Johannes Rieken 已提交
203
			// ignore all results except for WorkspaceEdits. Those are stored in an array.
204 205
			const now = Date.now();
			const result = await Promise.resolve(thenable);
J
Johannes Rieken 已提交
206 207 208
			if (result instanceof WorkspaceEdit) {
				edits.push(result);
			}
209 210 211 212

			if (Date.now() - now > timeout) {
				this._logService.warn('SLOW file-participant', listener.extension?.identifier);
			}
J
Johannes Rieken 已提交
213
		});
214

215 216
		if (token.isCancellationRequested) {
			return;
217 218
		}

219 220 221
		if (edits.length > 0) {
			// flatten all WorkspaceEdits collected via waitUntil-call
			// and apply them in one go.
222
			const allEdits = new Array<Array<IWorkspaceFileEditDto | IWorkspaceTextEditDto>>();
223 224 225 226 227
			for (let edit of edits) {
				let { edits } = typeConverter.WorkspaceEdit.from(edit, this._extHostDocumentsAndEditors);
				allEdits.push(edits);
			}
			return this._mainThreadTextEditors.$tryApplyWorkspaceEdit({ edits: flatten(allEdits) });
228
		}
229
	}
E
Erich Gamma 已提交
230
}