提交 9f6518c5 编写于 作者: J Johannes Rieken

move events back to textfile service

上级 7af36eb6
......@@ -4,9 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import { Disposable, IDisposable, toDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle';
import { IFileService, IResolveFileOptions, FileChangesEvent, FileOperationEvent, IFileSystemProviderRegistrationEvent, IFileSystemProvider, IFileStat, IResolveFileResult, ICreateFileOptions, IFileSystemProviderActivationEvent, FileOperationError, FileOperationResult, FileOperation, FileSystemProviderCapabilities, FileType, toFileSystemProviderErrorCode, FileSystemProviderErrorCode, IStat, IFileStatWithMetadata, IResolveMetadataFileOptions, etag, hasReadWriteCapability, hasFileFolderCopyCapability, hasOpenReadWriteCloseCapability, toFileOperationResult, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadWriteCapability, IResolveFileResultWithMetadata, IWatchOptions, IWriteFileOptions, IReadFileOptions, IFileStreamContent, IFileContent, ETAG_DISABLED, FileOperationWillRunEvent } from 'vs/platform/files/common/files';
import { IFileService, IResolveFileOptions, FileChangesEvent, FileOperationEvent, IFileSystemProviderRegistrationEvent, IFileSystemProvider, IFileStat, IResolveFileResult, ICreateFileOptions, IFileSystemProviderActivationEvent, FileOperationError, FileOperationResult, FileOperation, FileSystemProviderCapabilities, FileType, toFileSystemProviderErrorCode, FileSystemProviderErrorCode, IStat, IFileStatWithMetadata, IResolveMetadataFileOptions, etag, hasReadWriteCapability, hasFileFolderCopyCapability, hasOpenReadWriteCloseCapability, toFileOperationResult, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadWriteCapability, IResolveFileResultWithMetadata, IWatchOptions, IWriteFileOptions, IReadFileOptions, IFileStreamContent, IFileContent, ETAG_DISABLED } from 'vs/platform/files/common/files';
import { URI } from 'vs/base/common/uri';
import { Event, Emitter, AsyncEmitter } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import { isAbsolutePath, dirname, basename, joinPath, isEqual, isEqualOrParent } from 'vs/base/common/resources';
import { localize } from 'vs/nls';
import { TernarySearchTree } from 'vs/base/common/map';
......@@ -131,9 +131,6 @@ export class FileService extends Disposable implements IFileService {
//#endregion
private _onWillRunOperation = this._register(new AsyncEmitter<FileOperationWillRunEvent>());
readonly onWillRunOperation = this._onWillRunOperation.event;
private _onAfterOperation: Emitter<FileOperationEvent> = this._register(new Emitter<FileOperationEvent>());
readonly onAfterOperation: Event<FileOperationEvent> = this._onAfterOperation.event;
......@@ -282,9 +279,6 @@ export class FileService extends Disposable implements IFileService {
throw new FileOperationError(localize('fileExists', "File to create already exists ({0})", this.resourceForError(resource)), FileOperationResult.FILE_MODIFIED_SINCE, options);
}
// before events
await this._onWillRunOperation.fireAsync(promises => new FileOperationWillRunEvent(promises, FileOperation.CREATE, resource));
// do write into file (this will create it too)
const fileStat = await this.writeFile(resource, bufferOrReadableOrStream);
......@@ -548,9 +542,6 @@ export class FileService extends Disposable implements IFileService {
const sourceProvider = this.throwIfFileSystemIsReadonly(await this.withReadWriteProvider(source));
const targetProvider = this.throwIfFileSystemIsReadonly(await this.withReadWriteProvider(target));
// before events
await this._onWillRunOperation.fireAsync(promises => new FileOperationWillRunEvent(promises, FileOperation.MOVE, target, source));
// move
const mode = await this.doMoveCopy(sourceProvider, source, targetProvider, target, 'move', !!overwrite);
......@@ -565,9 +556,6 @@ export class FileService extends Disposable implements IFileService {
const sourceProvider = await this.withReadWriteProvider(source);
const targetProvider = this.throwIfFileSystemIsReadonly(await this.withReadWriteProvider(target));
// before events
await this._onWillRunOperation.fireAsync(promises => new FileOperationWillRunEvent(promises, FileOperation.COPY, target, source));
// copy
const mode = await this.doMoveCopy(sourceProvider, source, targetProvider, target, 'copy', !!overwrite);
......@@ -722,9 +710,6 @@ export class FileService extends Disposable implements IFileService {
async createFolder(resource: URI): Promise<IFileStatWithMetadata> {
const provider = this.throwIfFileSystemIsReadonly(await this.withProvider(resource));
// before events
await this._onWillRunOperation.fireAsync(promises => new FileOperationWillRunEvent(promises, FileOperation.CREATE, resource));
// mkdir recursively
await this.mkdirp(provider, resource);
......@@ -787,9 +772,6 @@ export class FileService extends Disposable implements IFileService {
}
}
// Before Event
await this._onWillRunOperation.fireAsync(promises => new FileOperationWillRunEvent(promises, FileOperation.DELETE, resource));
// Delete through provider
await provider.delete(resource, { recursive, useTrash });
......
......@@ -7,7 +7,7 @@ import { sep } from 'vs/base/common/path';
import { URI } from 'vs/base/common/uri';
import * as glob from 'vs/base/common/glob';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { Event, IWaitUntil } from 'vs/base/common/event';
import { Event } from 'vs/base/common/event';
import { startsWithIgnoreCase } from 'vs/base/common/strings';
import { IDisposable } from 'vs/base/common/lifecycle';
import { isEqualOrParent, isEqual } from 'vs/base/common/resources';
......@@ -57,11 +57,6 @@ export interface IFileService {
*/
readonly onFileChanges: Event<FileChangesEvent>;
/**
* An event that is fired before attempting a certain file operation.
*/
readonly onWillRunOperation: Event<FileOperationWillRunEvent>;
/**
* An event that is fired upon successful completion of a certain file operation.
*/
......@@ -377,23 +372,6 @@ export class FileOperationEvent {
}
}
export class FileOperationWillRunEvent implements IWaitUntil {
constructor(
private _thenables: Promise<any>[],
readonly operation: FileOperation,
readonly target: URI,
readonly source?: URI | undefined
) { }
waitUntil(thenable: Promise<any>): void {
if (Object.isFrozen(this._thenables)) {
throw new Error('waitUntil cannot be used aync');
}
this._thenables.push(thenable);
}
}
/**
* Possible changes that can occur to a file.
*/
......
......@@ -15,7 +15,7 @@ import { getPathFromAmdModule } from 'vs/base/common/amd';
import { copy, rimraf, symlink, RimRafMode, rimrafSync } from 'vs/base/node/pfs';
import { URI } from 'vs/base/common/uri';
import { existsSync, statSync, readdirSync, readFileSync, writeFileSync, renameSync, unlinkSync, mkdirSync, createReadStream } from 'fs';
import { FileOperation, FileOperationEvent, IFileStat, FileOperationResult, FileSystemProviderCapabilities, FileChangeType, IFileChange, FileChangesEvent, FileOperationError, etag, IStat, IFileStatWithMetadata, FileOperationWillRunEvent } from 'vs/platform/files/common/files';
import { FileOperation, FileOperationEvent, IFileStat, FileOperationResult, FileSystemProviderCapabilities, FileChangeType, IFileChange, FileChangesEvent, FileOperationError, etag, IStat, IFileStatWithMetadata } from 'vs/platform/files/common/files';
import { NullLogService } from 'vs/platform/log/common/log';
import { isLinux, isWindows } from 'vs/base/common/platform';
import { DisposableStore } from 'vs/base/common/lifecycle';
......@@ -155,9 +155,6 @@ suite('Disk File Service', function () {
});
test('createFolder', async () => {
let beforeEvent: FileOperationWillRunEvent | undefined;
disposables.add(service.onWillRunOperation(e => beforeEvent = e));
let event: FileOperationEvent | undefined;
disposables.add(service.onAfterOperation(e => event = e));
......@@ -170,11 +167,6 @@ suite('Disk File Service', function () {
assert.equal(newFolder.name, 'newFolder');
assert.equal(existsSync(newFolder.resource.fsPath), true);
assert.ok(beforeEvent);
assert.equal(beforeEvent!.target.fsPath, newFolderResource.fsPath);
assert.equal(beforeEvent!.source, undefined);
assert.equal(beforeEvent!.operation, FileOperation.CREATE);
assert.ok(event);
assert.equal(event!.resource.fsPath, newFolderResource.fsPath);
assert.equal(event!.operation, FileOperation.CREATE);
......@@ -418,9 +410,6 @@ suite('Disk File Service', function () {
});
test('deleteFile', async () => {
let beforeEvent: FileOperationWillRunEvent | undefined;
disposables.add(service.onWillRunOperation(e => beforeEvent = e));
let event: FileOperationEvent;
disposables.add(service.onAfterOperation(e => event = e));
......@@ -431,11 +420,6 @@ suite('Disk File Service', function () {
assert.equal(existsSync(source.resource.fsPath), false);
assert.ok(beforeEvent!);
assert.equal(beforeEvent!.target.fsPath, resource.fsPath);
assert.equal(beforeEvent!.source, undefined);
assert.equal(beforeEvent!.operation, FileOperation.DELETE);
assert.ok(event!);
assert.equal(event!.resource.fsPath, resource.fsPath);
assert.equal(event!.operation, FileOperation.DELETE);
......@@ -471,9 +455,6 @@ suite('Disk File Service', function () {
});
test('move', async () => {
let beforeEvent: FileOperationWillRunEvent | undefined;
disposables.add(service.onWillRunOperation(e => beforeEvent = e));
let event: FileOperationEvent;
disposables.add(service.onAfterOperation(e => event = e));
......@@ -486,10 +467,6 @@ suite('Disk File Service', function () {
assert.equal(existsSync(renamed.resource.fsPath), true);
assert.equal(existsSync(source.fsPath), false);
assert.ok(beforeEvent!);
assert.equal(beforeEvent!.operation, FileOperation.MOVE);
assert.equal(beforeEvent!.source?.fsPath, source.fsPath);
assert.equal(beforeEvent!.target?.fsPath, target.fsPath);
assert.ok(event!);
assert.equal(event!.resource.fsPath, source.fsPath);
assert.equal(event!.operation, FileOperation.MOVE);
......@@ -844,9 +821,6 @@ suite('Disk File Service', function () {
}
async function doTestCopy(sourceName: string = 'index.html') {
let beforeEvent: FileOperationWillRunEvent | undefined;
disposables.add(service.onWillRunOperation(e => beforeEvent = e));
let event: FileOperationEvent;
disposables.add(service.onAfterOperation(e => event = e));
......@@ -857,10 +831,6 @@ suite('Disk File Service', function () {
assert.equal(existsSync(copied.resource.fsPath), true);
assert.equal(existsSync(source.resource.fsPath), true);
assert.ok(beforeEvent!);
assert.equal(beforeEvent!.source!.fsPath, source.resource.fsPath);
assert.equal(beforeEvent!.operation, FileOperation.COPY);
assert.equal(beforeEvent!.target!.fsPath, copied.resource.fsPath);
assert.ok(event!);
assert.equal(event!.resource.fsPath, source.resource.fsPath);
assert.equal(event!.operation, FileOperation.COPY);
......@@ -1369,9 +1339,6 @@ suite('Disk File Service', function () {
});
async function assertCreateFile(converter: (content: string) => VSBuffer | VSBufferReadable | VSBufferReadableStream): Promise<void> {
let beforeEvent: FileOperationWillRunEvent | undefined;
disposables.add(service.onWillRunOperation(e => beforeEvent = e));
let event: FileOperationEvent;
disposables.add(service.onAfterOperation(e => event = e));
......@@ -1382,10 +1349,6 @@ suite('Disk File Service', function () {
assert.equal(existsSync(fileStat.resource.fsPath), true);
assert.equal(readFileSync(fileStat.resource.fsPath), contents);
assert.ok(beforeEvent!);
assert.equal(beforeEvent!.target.fsPath, resource.fsPath);
assert.equal(beforeEvent!.source, undefined);
assert.equal(beforeEvent!.operation, FileOperation.CREATE);
assert.ok(event!);
assert.equal(event!.resource.fsPath, resource.fsPath);
assert.equal(event!.operation, FileOperation.CREATE);
......
......@@ -3,19 +3,21 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { FileChangeType, IFileService } from 'vs/platform/files/common/files';
import { extHostCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { ExtHostContext, FileSystemEvents, IExtHostContext } from '../common/extHost.protocol';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
@extHostCustomer
export class MainThreadFileSystemEventService {
private readonly _listener = new Array<IDisposable>();
private readonly _listener = new DisposableStore();
constructor(
extHostContext: IExtHostContext,
@IFileService fileService: IFileService
@IFileService fileService: IFileService,
@ITextFileService textFileService: ITextFileService
) {
const proxy = extHostContext.getProxy(ExtHostContext.ExtHostFileSystemEventService);
......@@ -26,7 +28,7 @@ export class MainThreadFileSystemEventService {
changed: [],
deleted: []
};
fileService.onFileChanges(event => {
this._listener.add(fileService.onFileChanges(event => {
for (let change of event.changes) {
switch (change.type) {
case FileChangeType.ADDED:
......@@ -45,14 +47,14 @@ export class MainThreadFileSystemEventService {
events.created.length = 0;
events.changed.length = 0;
events.deleted.length = 0;
}, undefined, this._listener);
}));
// file operation events
fileService.onWillRunOperation(e => proxy.$onWillRunFileOperation(e.operation, e.target, e.source));
fileService.onAfterOperation(e => proxy.$onDidRunFileOperation(e.operation, e.resource, e.target && e.target.resource), undefined, this._listener);
this._listener.add(textFileService.onWillRunOperation(e => proxy.$onWillRunFileOperation(e.operation, e.target, e.source)));
this._listener.add(textFileService.onDidRunOperation(e => proxy.$onDidRunFileOperation(e.operation, e.target, e.source)));
}
dispose(): void {
dispose(this._listener);
this._listener.dispose();
}
}
......@@ -6,14 +6,14 @@
import * as nls from 'vs/nls';
import { URI } from 'vs/base/common/uri';
import * as objects from 'vs/base/common/objects';
import { Event, Emitter } from 'vs/base/common/event';
import { Event, Emitter, AsyncEmitter } from 'vs/base/common/event';
import * as platform from 'vs/base/common/platform';
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { IResult, ITextFileOperationResult, ITextFileService, ITextFileStreamContent, IAutoSaveConfiguration, AutoSaveMode, SaveReason, ITextFileEditorModelManager, ITextFileEditorModel, ModelState, ISaveOptions, AutoSaveContext, ITextFileContent, IResourceEncodings, IReadTextFileOptions, IWriteTextFileOptions, toBufferOrReadable, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles';
import { IResult, ITextFileOperationResult, ITextFileService, ITextFileStreamContent, IAutoSaveConfiguration, AutoSaveMode, SaveReason, ITextFileEditorModelManager, ITextFileEditorModel, ModelState, ISaveOptions, AutoSaveContext, ITextFileContent, IResourceEncodings, IReadTextFileOptions, IWriteTextFileOptions, toBufferOrReadable, TextFileOperationError, TextFileOperationResult, FileOperationWillRunEvent, FileOperationDidRunEvent } from 'vs/workbench/services/textfile/common/textfiles';
import { ConfirmResult, IRevertOptions } from 'vs/workbench/common/editor';
import { ILifecycleService, ShutdownReason, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IFileService, IFilesConfiguration, FileOperationError, FileOperationResult, AutoSaveConfiguration, HotExitConfiguration, IFileStatWithMetadata, ICreateFileOptions } from 'vs/platform/files/common/files';
import { IFileService, IFilesConfiguration, FileOperationError, FileOperationResult, AutoSaveConfiguration, HotExitConfiguration, IFileStatWithMetadata, ICreateFileOptions, FileOperation } from 'vs/platform/files/common/files';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { Disposable } from 'vs/base/common/lifecycle';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
......@@ -52,6 +52,14 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
private readonly _onFilesAssociationChange: Emitter<void> = this._register(new Emitter<void>());
readonly onFilesAssociationChange: Event<void> = this._onFilesAssociationChange.event;
private _onWillRunOperation = this._register(new AsyncEmitter<FileOperationWillRunEvent>());
readonly onWillRunOperation = this._onWillRunOperation.event;
private _onDidRunOperation = this._register(new Emitter<FileOperationDidRunEvent>());
readonly onDidRunOperation = this._onDidRunOperation.event;
private _models: TextFileEditorModelManager;
get models(): ITextFileEditorModelManager { return this._models; }
......@@ -405,6 +413,10 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
}
async create(resource: URI, value?: string | ITextSnapshot, options?: ICreateFileOptions): Promise<IFileStatWithMetadata> {
// before event
await this._onWillRunOperation.fireAsync(promises => new FileOperationWillRunEvent(promises, FileOperation.CREATE, resource));
const stat = await this.doCreate(resource, value, options);
// If we had an existing model for the given resource, load
......@@ -416,6 +428,9 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
await existingModel.revert();
}
// after event
this._onDidRunOperation.fire(new FileOperationDidRunEvent(FileOperation.CREATE, resource));
return stat;
}
......@@ -428,15 +443,21 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
}
async delete(resource: URI, options?: { useTrash?: boolean, recursive?: boolean }): Promise<void> {
const dirtyFiles = this.getDirty().filter(dirty => isEqualOrParent(dirty, resource));
await this._onWillRunOperation.fireAsync(promises => new FileOperationWillRunEvent(promises, FileOperation.DELETE, resource));
const dirtyFiles = this.getDirty().filter(dirty => isEqualOrParent(dirty, resource));
await this.revertAll(dirtyFiles, { soft: true });
return this.fileService.del(resource, options);
await this.fileService.del(resource, options);
this._onDidRunOperation.fire(new FileOperationDidRunEvent(FileOperation.DELETE, resource));
}
async move(source: URI, target: URI, overwrite?: boolean): Promise<IFileStatWithMetadata> {
// before events
await this._onWillRunOperation.fireAsync(promises => new FileOperationWillRunEvent(promises, FileOperation.MOVE, target, source));
// find all models that related to either source or target (can be many if resource is a folder)
const sourceModels: ITextFileEditorModel[] = [];
const conflictingModels: ITextFileEditorModel[] = [];
......@@ -514,6 +535,9 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
}
}));
// after event
this._onDidRunOperation.fire(new FileOperationDidRunEvent(FileOperation.MOVE, target, source));
return stat;
}
......
......@@ -4,10 +4,10 @@
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
import { Event } from 'vs/base/common/event';
import { Event, IWaitUntil } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IEncodingSupport, ConfirmResult, IRevertOptions, IModeSupport } from 'vs/workbench/common/editor';
import { IBaseStatWithMetadata, IFileStatWithMetadata, IReadFileOptions, IWriteFileOptions, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
import { IBaseStatWithMetadata, IFileStatWithMetadata, IReadFileOptions, IWriteFileOptions, FileOperationError, FileOperationResult, FileOperation } from 'vs/platform/files/common/files';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ITextEditorModel } from 'vs/editor/common/services/resolverService';
import { ITextBufferFactory, ITextModel, ITextSnapshot } from 'vs/editor/common/model';
......@@ -28,6 +28,16 @@ export interface ITextFileService extends IDisposable {
readonly isHotExitEnabled: boolean;
/**
* An event that is fired before attempting a certain file operation.
*/
readonly onWillRunOperation: Event<FileOperationWillRunEvent>;
/**
* An event that is fired after a file operation has been performed.
*/
readonly onDidRunOperation: Event<FileOperationDidRunEvent>;
/**
* Access to the manager of text file editor models providing further methods to work with them.
*/
......@@ -145,6 +155,35 @@ export interface ITextFileService extends IDisposable {
getAutoSaveConfiguration(): IAutoSaveConfiguration;
}
export class FileOperationWillRunEvent implements IWaitUntil {
constructor(
private _thenables: Promise<any>[],
readonly operation: FileOperation,
readonly target: URI,
readonly source?: URI | undefined
) { }
waitUntil(thenable: Promise<any>): void {
if (Object.isFrozen(this._thenables)) {
throw new Error('waitUntil cannot be used aync');
}
this._thenables.push(thenable);
}
}
export class FileOperationDidRunEvent {
constructor(
readonly operation: FileOperation,
readonly target: URI,
readonly source?: URI | undefined
) { }
}
export interface IReadTextFileOptions extends IReadFileOptions {
/**
......
......@@ -923,8 +923,6 @@ export class TestFileService implements IFileService {
public _serviceBrand: undefined;
readonly onWillRunOperation = Event.None;
private readonly _onFileChanges: Emitter<FileChangesEvent>;
private readonly _onAfterOperation: Emitter<FileOperationEvent>;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册