Skip to content

Commit

Permalink
plugin: add workspace file api
Browse files Browse the repository at this point in the history
Add `on(will|did)(create|rename|delete)Files` to the plugin's workspace
api. This implementation does not handle the `WorkspaceEdit` apis.

Signed-off-by: Paul Maréchal <paul.marechal@ericsson.com>
  • Loading branch information
paul-marechal committed May 20, 2020
1 parent 8ca3e64 commit 0a5139d
Show file tree
Hide file tree
Showing 14 changed files with 473 additions and 124 deletions.
26 changes: 21 additions & 5 deletions packages/filesystem/src/browser/filesystem-watcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,14 @@ export class FileOperationEmitter<E extends WaitUntilEvent> implements Disposabl

}

/**
* React to file system events, including calls originating from the
* application or event coming from the system's filesystem directly
* (actual file watching).
*
* `on(will|did)(create|rename|delete)` events solely come from application
* usage, not from actual filesystem.
*/
@injectable()
export class FileSystemWatcher implements Disposable {

Expand All @@ -122,6 +130,11 @@ export class FileSystemWatcher implements Disposable {
protected readonly onFileChangedEmitter = new Emitter<FileChangeEvent>();
readonly onFilesChanged = this.onFileChangedEmitter.event;

protected readonly fileCreateEmitter = new FileOperationEmitter<FileEvent>();
readonly onWillCreate = this.fileCreateEmitter.onWill;
readonly onDidFailCreate = this.fileCreateEmitter.onDidFail;
readonly onDidCreate = this.fileCreateEmitter.onDid;

protected readonly fileDeleteEmitter = new FileOperationEmitter<FileEvent>();
readonly onWillDelete = this.fileDeleteEmitter.onWill;
readonly onDidFailDelete = this.fileDeleteEmitter.onDidFail;
Expand Down Expand Up @@ -164,11 +177,15 @@ export class FileSystemWatcher implements Disposable {
}));

this.filesystem.setClient({
/* eslint-disable no-void */
shouldOverwrite: this.shouldOverwrite.bind(this),
willDelete: uri => this.fileDeleteEmitter.fireWill({ uri: new URI(uri) }),
didDelete: (uri, failed) => this.fileDeleteEmitter.fireDid(failed, { uri: new URI(uri) }),
willMove: (source, target) => this.fileMoveEmitter.fireWill({ sourceUri: new URI(source), targetUri: new URI(target) }),
didMove: (source, target, failed) => this.fileMoveEmitter.fireDid(failed, { sourceUri: new URI(source), targetUri: new URI(target) })
willCreate: async uri => void await this.fileCreateEmitter.fireWill({ uri: new URI(uri) }),
didCreate: async (uri, failed) => void await this.fileCreateEmitter.fireDid(failed, { uri: new URI(uri) }),
willDelete: async uri => void await this.fileDeleteEmitter.fireWill({ uri: new URI(uri) }),
didDelete: async (uri, failed) => void await this.fileDeleteEmitter.fireDid(failed, { uri: new URI(uri) }),
willMove: async (sourceUri, targetUri) => void await this.fileMoveEmitter.fireWill({ sourceUri: new URI(sourceUri), targetUri: new URI(targetUri) }),
didMove: async (sourceUri, targetUri, failed) => void await this.fileMoveEmitter.fireDid(failed, { sourceUri: new URI(sourceUri), targetUri: new URI(targetUri) }),
/* eslint-enable no-void */
});
}

Expand Down Expand Up @@ -228,4 +245,3 @@ export class FileSystemWatcher implements Disposable {
}

}

23 changes: 18 additions & 5 deletions packages/filesystem/src/common/filesystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,13 +212,18 @@ export interface FileSystemClient {
*/
shouldOverwrite: FileShouldOverwrite;

willCreate(uri: string): Promise<void>;

didCreate(uri: string, failed: boolean): Promise<void>;

willDelete(uri: string): Promise<void>;

didDelete(uri: string, failed: boolean): Promise<void>;

willMove(sourceUri: string, targetUri: string): Promise<void>;

didMove(sourceUri: string, targetUri: string, failed: boolean): Promise<void>;

}

@injectable()
Expand All @@ -227,25 +232,33 @@ export class DispatchingFileSystemClient implements FileSystemClient {
readonly clients = new Set<FileSystemClient>();

shouldOverwrite(originalStat: FileStat, currentStat: FileStat): Promise<boolean> {
return Promise.race([...this.clients].map(client =>
return Promise.race(Array.from(this.clients, client =>
client.shouldOverwrite(originalStat, currentStat))
);
}

async willCreate(uri: string): Promise<void> {
await Promise.all(Array.from(this.clients, client => client.willCreate(uri)));
}

async didCreate(uri: string, failed: boolean): Promise<void> {
await Promise.all(Array.from(this.clients, client => client.didCreate(uri, failed)));
}

async willDelete(uri: string): Promise<void> {
await Promise.all([...this.clients].map(client => client.willDelete(uri)));
await Promise.all(Array.from(this.clients, client => client.willDelete(uri)));
}

async didDelete(uri: string, failed: boolean): Promise<void> {
await Promise.all([...this.clients].map(client => client.didDelete(uri, failed)));
await Promise.all(Array.from(this.clients, client => client.didDelete(uri, failed)));
}

async willMove(sourceUri: string, targetUri: string): Promise<void> {
await Promise.all([...this.clients].map(client => client.willMove(sourceUri, targetUri)));
await Promise.all(Array.from(this.clients, client => client.willMove(sourceUri, targetUri)));
}

async didMove(sourceUri: string, targetUri: string, failed: boolean): Promise<void> {
await Promise.all([...this.clients].map(client => client.didMove(sourceUri, targetUri, failed)));
await Promise.all(Array.from(this.clients, client => client.didMove(sourceUri, targetUri, failed)));
}

}
Expand Down
38 changes: 38 additions & 0 deletions packages/filesystem/src/node/node-filesystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,25 @@ export class FileSystemNode implements FileSystem {
}

async createFile(uri: string, options?: { content?: string, encoding?: string }): Promise<FileStat> {
if (this.client) {
await this.client.willCreate(uri);
}
let result: FileStat;
let failed = false;
try {
result = await this.doCreateFile(uri, options);
} catch (e) {
failed = true;
throw e;
} finally {
if (this.client) {
await this.client.didCreate(uri, failed);
}
}
return result;
}

protected async doCreateFile(uri: string, options?: { content?: string, encoding?: string }): Promise<FileStat> {
const _uri = new URI(uri);
const parentUri = _uri.parent;
const [stat, parentStat] = await Promise.all([this.doGetStat(_uri, 0), this.doGetStat(parentUri, 0)]);
Expand All @@ -284,6 +303,25 @@ export class FileSystemNode implements FileSystem {
}

async createFolder(uri: string): Promise<FileStat> {
if (this.client) {
await this.client.willCreate(uri);
}
let result: FileStat;
let failed = false;
try {
result = await this.doCreateFolder(uri);
} catch (e) {
failed = true;
throw e;
} finally {
if (this.client) {
await this.client.didCreate(uri, failed);
}
}
return result;
}

async doCreateFolder(uri: string): Promise<FileStat> {
const _uri = new URI(uri);
const stat = await this.doGetStat(_uri, 0);
if (stat) {
Expand Down
24 changes: 12 additions & 12 deletions packages/plugin-ext/src/common/plugin-api-rpc-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,18 +95,6 @@ export interface FileChangeEvent {
type: FileChangeEventType
}

export interface FileMoveEvent {
subscriberId: string,
oldUri: UriComponents,
newUri: UriComponents
}

export interface FileWillMoveEvent {
subscriberId: string,
oldUri: UriComponents,
newUri: UriComponents
}

export type FileChangeEventType = 'created' | 'updated' | 'deleted';

export enum CompletionTriggerKind {
Expand Down Expand Up @@ -547,3 +535,15 @@ export interface CallHierarchyOutgoingCall {
to: CallHierarchyItem;
fromRanges: Range[];
}

export interface CreateFilesEventDTO {
files: UriComponents[]
}

export interface RenameFilesEventDTO {
files: { oldUri: UriComponents, newUri: UriComponents }[]
}

export interface DeleteFilesEventDTO {
files: UriComponents[]
}
16 changes: 11 additions & 5 deletions packages/plugin-ext/src/common/plugin-api-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,17 @@ import {
Breakpoint,
ColorPresentation,
RenameLocation,
FileMoveEvent,
FileWillMoveEvent,
SignatureHelpContext,
CodeAction,
CodeActionContext,
FoldingContext,
FoldingRange,
SelectionRange,
CallHierarchyDefinition,
CallHierarchyReference
CallHierarchyReference,
CreateFilesEventDTO,
RenameFilesEventDTO,
DeleteFilesEventDTO,
} from './plugin-api-rpc-model';
import { ExtPluginApi } from './plugin-ext-api-contribution';
import { KeysToAnyValues, KeysToKeysToAnyValue } from './types';
Expand Down Expand Up @@ -506,8 +507,13 @@ export interface WorkspaceExt {
$onWorkspaceFoldersChanged(event: WorkspaceRootsChangeEvent): void;
$provideTextDocumentContent(uri: string): Promise<string | undefined>;
$fileChanged(event: FileChangeEvent): void;
$onFileRename(event: FileMoveEvent): void;
$onWillRename(event: FileWillMoveEvent): Promise<any>;

$onWillCreateFiles(event: CreateFilesEventDTO): Promise<any[]>;
$onDidCreateFiles(event: CreateFilesEventDTO): void;
$onWillRenameFiles(event: RenameFilesEventDTO): Promise<any[]>;
$onDidRenameFiles(event: RenameFilesEventDTO): void;
$onWillDeleteFiles(event: DeleteFilesEventDTO): Promise<any[]>;
$onDidDeleteFiles(event: DeleteFilesEventDTO): void;
}

export interface DialogsMain {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import { injectable, inject, postConstruct } from 'inversify';
import { parse, ParsedPattern, IRelativePattern } from '@theia/languages/lib/common/language-selector';
import { FileSystemWatcher, FileChangeEvent, FileChangeType, FileChange, FileMoveEvent } from '@theia/filesystem/lib/browser/filesystem-watcher';
import { FileSystemWatcher, FileChangeEvent, FileChangeType, FileChange } from '@theia/filesystem/lib/browser/filesystem-watcher';
import { WorkspaceExt } from '../../common/plugin-api-rpc';
import { FileWatcherSubscriberOptions } from '../../common/plugin-api-rpc-model';
import { RelativePattern } from '../../plugin/types-impl';
Expand All @@ -40,8 +40,6 @@ export class InPluginFileSystemWatcherManager {
@postConstruct()
protected init(): void {
this.fileSystemWatcher.onFilesChanged(event => this.onFilesChangedEventHandler(event));
this.fileSystemWatcher.onDidMove(event => this.onDidMoveEventHandler(event));
this.fileSystemWatcher.onWillMove(event => this.onWillMoveEventHandler(event));
}

// Filter file system changes according to subscribers settings here to avoid unneeded traffic.
Expand Down Expand Up @@ -73,30 +71,8 @@ export class InPluginFileSystemWatcherManager {
}
}

// Filter file system changes according to subscribers settings here to avoid unneeded traffic.
protected onDidMoveEventHandler(change: FileMoveEvent): void {
for (const [id, subscriber] of this.subscribers) {
subscriber.proxy.$onFileRename({
subscriberId: id,
oldUri: theiaUritoUriComponents(change.sourceUri),
newUri: theiaUritoUriComponents(change.targetUri)
});
}
}

// Filter file system changes according to subscribers settings here to avoid unneeded traffic.
protected onWillMoveEventHandler(change: FileMoveEvent): void {
for (const [id, subscriber] of this.subscribers) {
subscriber.proxy.$onWillRename({
subscriberId: id,
oldUri: theiaUritoUriComponents(change.sourceUri),
newUri: theiaUritoUriComponents(change.targetUri)
});
}
}

private uriMatches(subscriber: FileWatcherSubscriber, fileChange: FileChange): boolean {
return subscriber.mather(fileChange.uri.path.toString());
return subscriber.matcher(fileChange.uri.path.toString());
}

/**
Expand All @@ -118,7 +94,7 @@ export class InPluginFileSystemWatcherManager {

const subscriber: FileWatcherSubscriber = {
id: subscriberId,
mather: globPatternMatcher,
matcher: globPatternMatcher,
ignoreCreateEvents: options.ignoreCreateEvents === true,
ignoreChangeEvents: options.ignoreChangeEvents === true,
ignoreDeleteEvents: options.ignoreDeleteEvents === true,
Expand All @@ -141,7 +117,7 @@ export class InPluginFileSystemWatcherManager {

interface FileWatcherSubscriber {
id: string;
mather: ParsedPattern;
matcher: ParsedPattern;
ignoreCreateEvents: boolean;
ignoreChangeEvents: boolean;
ignoreDeleteEvents: boolean;
Expand Down
36 changes: 34 additions & 2 deletions packages/plugin-ext/src/main/browser/workspace-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { interfaces, injectable } from 'inversify';
import { WorkspaceExt, StorageExt, MAIN_RPC_CONTEXT, WorkspaceMain, WorkspaceFolderPickOptionsMain } from '../../common/plugin-api-rpc';
import { RPCProtocol } from '../../common/rpc-protocol';
import { URI as Uri } from 'vscode-uri';
import { UriComponents } from '../../common/uri-components';
import { UriComponents, theiaUritoUriComponents } from '../../common/uri-components';
import { QuickOpenModel, QuickOpenItem, QuickOpenMode } from '@theia/core/lib/browser/quick-open/quick-open-model';
import { MonacoQuickOpenService } from '@theia/monaco/lib/browser/monaco-quick-open-service';
import { FileStat } from '@theia/filesystem/lib/common';
Expand All @@ -32,7 +32,7 @@ import { Emitter, Event, ResourceResolver } from '@theia/core';
import { FileWatcherSubscriberOptions } from '../../common/plugin-api-rpc-model';
import { InPluginFileSystemWatcherManager } from './in-plugin-filesystem-watcher-manager';
import { PluginServer } from '../../common/plugin-protocol';
import { FileSystemPreferences } from '@theia/filesystem/lib/browser';
import { FileSystemPreferences, FileSystemWatcher } from '@theia/filesystem/lib/browser';

export class WorkspaceMainImpl implements WorkspaceMain, Disposable {

Expand All @@ -56,6 +56,8 @@ export class WorkspaceMainImpl implements WorkspaceMain, Disposable {

private fsPreferences: FileSystemPreferences;

private fileSystemWatcher: FileSystemWatcher;

protected readonly toDispose = new DisposableCollection();

constructor(rpc: RPCProtocol, container: interfaces.Container) {
Expand All @@ -67,12 +69,42 @@ export class WorkspaceMainImpl implements WorkspaceMain, Disposable {
this.pluginServer = container.get(PluginServer);
this.workspaceService = container.get(WorkspaceService);
this.fsPreferences = container.get(FileSystemPreferences);
this.fileSystemWatcher = container.get(FileSystemWatcher);
this.inPluginFileSystemWatcherManager = container.get(InPluginFileSystemWatcherManager);

this.processWorkspaceFoldersChanged(this.workspaceService.tryGetRoots());
this.toDispose.push(this.workspaceService.onWorkspaceChanged(roots => {
this.processWorkspaceFoldersChanged(roots);
}));

this.toDispose.push(this.fileSystemWatcher.onWillCreate(event => {
event.waitUntil(this.proxy.$onWillCreateFiles({ files: [theiaUritoUriComponents(event.uri)] }));
}));
this.toDispose.push(this.fileSystemWatcher.onDidCreate(event => {
this.proxy.$onDidCreateFiles({ files: [theiaUritoUriComponents(event.uri)] });
}));
this.toDispose.push(this.fileSystemWatcher.onWillMove(event => {
event.waitUntil(this.proxy.$onWillRenameFiles({
files: [{
oldUri: theiaUritoUriComponents(event.sourceUri),
newUri: theiaUritoUriComponents(event.targetUri),
}],
}));
}));
this.toDispose.push(this.fileSystemWatcher.onDidMove(event => {
this.proxy.$onDidRenameFiles({
files: [{
oldUri: theiaUritoUriComponents(event.sourceUri),
newUri: theiaUritoUriComponents(event.targetUri),
}],
});
}));
this.toDispose.push(this.fileSystemWatcher.onWillDelete(event => {
event.waitUntil(this.proxy.$onWillDeleteFiles({ files: [theiaUritoUriComponents(event.uri)] }));
}));
this.toDispose.push(this.fileSystemWatcher.onDidDelete(event => {
this.proxy.$onDidDeleteFiles({ files: [theiaUritoUriComponents(event.uri)] });
}));
}

dispose(): void {
Expand Down
1 change: 0 additions & 1 deletion packages/plugin-ext/src/plugin/file-system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ export class FileSystemExtImpl implements FileSystemExt {
this.usedSchemes.add(Schemes.DATA);
this.usedSchemes.add(Schemes.COMMAND);
this.fileSystem = new InPluginFileSystemProxy(this.proxy);

}

get fs(): theia.FileSystem {
Expand Down
Loading

0 comments on commit 0a5139d

Please sign in to comment.