Skip to content

Commit

Permalink
Add support for proposed signature for workspace.createFileSystemWatcher
Browse files Browse the repository at this point in the history
Fixes eclipse-theia#13957

Contributed on behalf of STMicroelectronics

Signed-off-by: Thomas Mäder <[email protected]>
  • Loading branch information
tsmaeder committed Oct 12, 2024
1 parent c7fb4f5 commit 5258a9f
Show file tree
Hide file tree
Showing 5 changed files with 220 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ type Event<T> = vscode.Event<T>;
type IExtensionDescription = Plugin;
type IWaitUntil = WaitUntilEvent;

class FileSystemWatcher implements vscode.FileSystemWatcher {
export class FileSystemWatcher implements vscode.FileSystemWatcher {

private readonly _onDidCreate = new Emitter<vscode.Uri>();
private readonly _onDidChange = new Emitter<vscode.Uri>();
Expand All @@ -68,7 +68,8 @@ class FileSystemWatcher implements vscode.FileSystemWatcher {
return Boolean(this._config & 0b100);
}

constructor(dispatcher: Event<FileSystemEvents>, globPattern: string | IRelativePattern, ignoreCreateEvents?: boolean, ignoreChangeEvents?: boolean, ignoreDeleteEvents?: boolean) {
constructor(dispatcher: Event<FileSystemEvents>, globPattern: string | IRelativePattern,
ignoreCreateEvents?: boolean, ignoreChangeEvents?: boolean, ignoreDeleteEvents?: boolean, excludes?: string[]) {

this._config = 0;
if (ignoreCreateEvents) {
Expand All @@ -82,28 +83,29 @@ class FileSystemWatcher implements vscode.FileSystemWatcher {
}

const parsedPattern = parse(globPattern);
const excludePatterns = excludes?.map(exclude => parse(exclude)) || [];

const subscription = dispatcher(events => {
if (!ignoreCreateEvents) {
for (const created of events.created) {
const uri = URI.revive(created);
if (parsedPattern(uri.fsPath)) {
if (parsedPattern(uri.fsPath) && !excludePatterns.some(p => p(uri.fsPath))) {
this._onDidCreate.fire(uri);
}
}
}
if (!ignoreChangeEvents) {
for (const changed of events.changed) {
const uri = URI.revive(changed);
if (parsedPattern(uri.fsPath)) {
if (parsedPattern(uri.fsPath) && !excludePatterns.some(p => p(uri.fsPath))) {
this._onDidChange.fire(uri);
}
}
}
if (!ignoreDeleteEvents) {
for (const deleted of events.deleted) {
const uri = URI.revive(deleted);
if (parsedPattern(uri.fsPath)) {
if (parsedPattern(uri.fsPath) && !excludePatterns.some(p => p(uri.fsPath))) {
this._onDidDelete.fire(uri);
}
}
Expand All @@ -113,7 +115,7 @@ class FileSystemWatcher implements vscode.FileSystemWatcher {
this._disposable = Disposable.from(this._onDidCreate, this._onDidChange, this._onDidDelete, subscription);
}

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

Expand Down Expand Up @@ -160,8 +162,9 @@ export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServ

// --- file events

createFileSystemWatcher(globPattern: string | IRelativePattern, ignoreCreateEvents?: boolean, ignoreChangeEvents?: boolean, ignoreDeleteEvents?: boolean): vscode.FileSystemWatcher {
return new FileSystemWatcher(this._onFileSystemEvent.event, globPattern, ignoreCreateEvents, ignoreChangeEvents, ignoreDeleteEvents);
createFileSystemWatcher(globPattern: string | IRelativePattern, ignoreCreateEvents?: boolean,
ignoreChangeEvents?: boolean, ignoreDeleteEvents?: boolean, excludes?: string[]): vscode.FileSystemWatcher {
return new FileSystemWatcher(this._onFileSystemEvent.event, globPattern, ignoreCreateEvents, ignoreChangeEvents, ignoreDeleteEvents, excludes);
}

$onFileEvent(events: FileSystemEvents) {
Expand Down
125 changes: 125 additions & 0 deletions packages/plugin-ext/src/plugin/file-system-watcher.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// *****************************************************************************
// Copyright (C) 2019 Red Hat, Inc. and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************

import * as assert from 'assert';
import { FileSystemWatcher } from './file-system-event-service-ext-impl';
import { DisposableCollection, Emitter } from '@theia/core';
import { FileSystemEvents } from '../common';
import { URI } from './types-impl';

const eventSource = new Emitter<FileSystemEvents>();
let disposables = new DisposableCollection();

function checkIgnore(ignoreCreate: number, ignoreChange: number, ignoreDelete: number): void {
const watcher = new FileSystemWatcher(eventSource.event, '**/*.js', !ignoreCreate, !ignoreChange, !ignoreDelete);
disposables.push(watcher);
const matching = URI.file('/foo/bar/zoz.js');

const changed: URI[] = [];
const created: URI[] = [];
const deleted: URI[] = [];
watcher.onDidChange(e => {
changed.push(e);
});

watcher.onDidCreate(e => {
created.push(e);
});

watcher.onDidDelete(e => {
deleted.push(e);
});

eventSource.fire({ changed: [matching], created: [matching], deleted: [matching] });

assert.equal(created.length, ignoreCreate);
assert.equal(deleted.length, ignoreDelete);
assert.equal(changed.length, ignoreChange);

}

describe('File Watcher Test', () => {
afterEach(() => {
disposables.dispose();
disposables = new DisposableCollection();
});

it('Should match files', () => {
const watcher = new FileSystemWatcher(eventSource.event, '**/*.js');
disposables.push(watcher);
const matching = URI.file('/foo/bar/zoz.js');
const notMatching = URI.file('/foo/bar/zoz.ts');
const changed: URI[] = [];
const created: URI[] = [];
const deleted: URI[] = [];
watcher.onDidChange(e => {
changed.push(e);
});

watcher.onDidCreate(e => {
created.push(e);
});

watcher.onDidDelete(e => {
deleted.push(e);
});

const URIs = [matching, notMatching];
eventSource.fire({ changed: URIs, created: URIs, deleted: URIs });
assert.equal(matching.toString(), changed[0]?.toString());
assert.equal(matching.toString(), created[0]?.toString());
assert.equal(matching.toString(), deleted[0]?.toString());
});

it('Should ignore created', () => {
checkIgnore(0, 1, 1);
});

it('Should ignore changed', () => {
checkIgnore(1, 0, 1);
});

it('Should ignore deleted', () => {
checkIgnore(1, 1, 0);
});

it('Should exclude files', () => {
const watcher = new FileSystemWatcher(eventSource.event, '**/*.js', false, false, false, ['**/bar/**']);
disposables.push(watcher);
const notMatching = URI.file('/foo/bar/zoz.js');
const matching = URI.file('/foo/gux/zoz.js');
const changed: URI[] = [];
const created: URI[] = [];
const deleted: URI[] = [];
watcher.onDidChange(e => {
changed.push(e);
});

watcher.onDidCreate(e => {
created.push(e);
});

watcher.onDidDelete(e => {
deleted.push(e);
});

const URIs = [matching, notMatching];
eventSource.fire({ changed: URIs, created: URIs, deleted: URIs });
assert.equal(matching.toString(), changed[0]?.toString());
assert.equal(matching.toString(), created[0]?.toString());
assert.equal(matching.toString(), deleted[0]?.toString());
});
});
20 changes: 18 additions & 2 deletions packages/plugin-ext/src/plugin/plugin-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ import { NotebookDocumentsExtImpl } from './notebook/notebook-documents';
import { NotebookEditorsExtImpl } from './notebook/notebook-editors';
import { TestingExtImpl } from './tests';
import { UriExtImpl } from './uri-ext';
import { isObject } from '@theia/core';

export function createAPIObject<T extends Object>(rawObject: T): T {
return new Proxy(rawObject, {
Expand Down Expand Up @@ -669,6 +670,22 @@ export function createAPIFactory(
onDidStartTerminalShellExecution: Event.None
};

function createFileSystemWatcher(pattern: RelativePattern, options?: theia.FileSystemWatcherOptions): theia.FileSystemWatcher;
function createFileSystemWatcher(pattern: theia.GlobPattern, ignoreCreateEvents?: boolean, ignoreChangeEvents?:
boolean, ignoreDeleteEvents?: boolean): theia.FileSystemWatcher;
function createFileSystemWatcher(pattern: RelativePattern | theia.GlobPattern,
ignoreCreateOrOptions?: theia.FileSystemWatcherOptions | boolean, ignoreChangeEventsBoolean?: boolean, ignoreDeleteEventsBoolean?: boolean): theia.FileSystemWatcher {
if (isObject<theia.FileSystemWatcherOptions>(ignoreCreateOrOptions)) {
const { ignoreCreateEvents, ignoreChangeEvents, ignoreDeleteEvents, excludes } = (ignoreCreateOrOptions as theia.FileSystemWatcherOptions);
return createAPIObject(
extHostFileSystemEvent.createFileSystemWatcher(fromGlobPattern(pattern),
ignoreCreateEvents, ignoreChangeEvents, ignoreDeleteEvents, excludes));
} else {
return createAPIObject(
extHostFileSystemEvent.createFileSystemWatcher(fromGlobPattern(pattern),
ignoreCreateOrOptions as boolean, ignoreChangeEventsBoolean, ignoreDeleteEventsBoolean));
}
}
const workspace: typeof theia.workspace = {

get fs(): theia.FileSystem {
Expand Down Expand Up @@ -774,8 +791,7 @@ export function createAPIFactory(
// Notebook extension will create a document in openNotebookDocument() or create openNotebookDocument()
return notebooksExt.getNotebookDocument(uri).apiNotebook;
},
createFileSystemWatcher: (pattern, ignoreCreate, ignoreChange, ignoreDelete): theia.FileSystemWatcher =>
createAPIObject(extHostFileSystemEvent.createFileSystemWatcher(fromGlobPattern(pattern), ignoreCreate, ignoreChange, ignoreDelete)),
createFileSystemWatcher,
findFiles(include: theia.GlobPattern, exclude?: theia.GlobPattern | null, maxResults?: number, token?: CancellationToken): PromiseLike<URI[]> {
return workspaceExt.findFiles(include, exclude, maxResults, token);
},
Expand Down
1 change: 1 addition & 0 deletions packages/plugin/src/theia.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import './theia-extra';
import './theia.proposed.canonicalUriProvider';
import './theia.proposed.createFileSystemWatcher';
import './theia.proposed.customEditorMove';
import './theia.proposed.debugVisualization';
import './theia.proposed.diffCommand';
Expand Down
65 changes: 65 additions & 0 deletions packages/plugin/src/theia.proposed.createFileSystemWatcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// *****************************************************************************
// Copyright (C) 2024 STMicroelectronics and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// code copied and modified from https://github.com/microsoft/vscode/blob/release/1.93/src/vscode-dts/vscode.proposed.createFileSystemWatcher.d.ts

declare module '@theia/plugin' {

export interface FileSystemWatcherOptions {

/**
* Ignore when files have been created.
*/
readonly ignoreCreateEvents?: boolean;

/**
* Ignore when files have been changed.
*/
readonly ignoreChangeEvents?: boolean;

/**
* Ignore when files have been deleted.
*/
readonly ignoreDeleteEvents?: boolean;

/**
* An optional set of glob patterns to exclude from watching.
* Glob patterns are always matched relative to the watched folder.
*/
readonly excludes: string[];
}

export namespace workspace {

/**
* A variant of {@link workspace.createFileSystemWatcher} that optionally allows to specify
* a set of glob patterns to exclude from watching.
*
* It provides the following advantages over the other {@link workspace.createFileSystemWatcher}
* method:
* - the configured excludes from `files.watcherExclude` setting are NOT applied
* - requests for recursive file watchers inside the opened workspace are NOT ignored
* - the watcher is ONLY notified for events from this request and not from any other watcher
*
* As such, this method is prefered in cases where you want full control over the watcher behavior
* without being impacted by settings or other watchers that are installed.
*/
export function createFileSystemWatcher(pattern: RelativePattern, options?: FileSystemWatcherOptions): FileSystemWatcher;
}
}

0 comments on commit 5258a9f

Please sign in to comment.