mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-24 18:49:00 +01:00
* API: Allow to use the file watcher for aribitrary folders (#3025) * fix tests * update `createFileSystemWatcher` docs * refuse to watch resources that are watched in workspace already * properly check proposed API * make it work via `createFileSystemWacher` (first cut) * more docs * cleanup * enable recursive watching based on pattern * add tests * drop out-of-workspace events when using simple patterns * do not apply excludes when watchig files * log extension watch requests * also log unwatch * improved exclude handling * more docs * drop proposed api needs * remove `suite.only` * cannot watch inside workspace more than once * do not send extension decriptor over * adopt latest changes * add `baseUri` to relative pattern * backwards compat
This commit is contained in:
@@ -830,7 +830,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
return extHostBulkEdits.applyWorkspaceEdit(edit);
|
||||
},
|
||||
createFileSystemWatcher: (pattern, ignoreCreate, ignoreChange, ignoreDelete): vscode.FileSystemWatcher => {
|
||||
return extHostFileSystemEvent.createFileSystemWatcher(typeConverters.GlobPattern.from(pattern), ignoreCreate, ignoreChange, ignoreDelete);
|
||||
return extHostFileSystemEvent.createFileSystemWatcher(extHostWorkspace, extension, typeConverters.GlobPattern.from(pattern), ignoreCreate, ignoreChange, ignoreDelete);
|
||||
},
|
||||
get textDocuments() {
|
||||
return extHostDocuments.getAllDocumentData().map(data => data.document);
|
||||
|
||||
@@ -1043,6 +1043,9 @@ export interface MainThreadFileSystemShape extends IDisposable {
|
||||
$mkdir(resource: UriComponents): Promise<void>;
|
||||
$delete(resource: UriComponents, opts: files.FileDeleteOptions): Promise<void>;
|
||||
|
||||
$watch(extensionId: string, session: number, resource: UriComponents, opts: files.IWatchOptions): void;
|
||||
$unwatch(session: number): void;
|
||||
|
||||
$ensureActivation(scheme: string): Promise<void>;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,23 +4,25 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Emitter, Event, AsyncEmitter, IWaitUntil, IWaitUntilData } from 'vs/base/common/event';
|
||||
import { IRelativePattern, parse } from 'vs/base/common/glob';
|
||||
import { GLOBSTAR, GLOB_SPLIT, parse } from 'vs/base/common/glob';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
|
||||
import type * as vscode from 'vscode';
|
||||
import { ExtHostFileSystemEventServiceShape, FileSystemEvents, IMainContext, SourceTargetPair, IWorkspaceEditDto, IWillRunFileOperationParticipation } from './extHost.protocol';
|
||||
import { ExtHostFileSystemEventServiceShape, FileSystemEvents, IMainContext, SourceTargetPair, IWorkspaceEditDto, IWillRunFileOperationParticipation, MainContext } from './extHost.protocol';
|
||||
import * as typeConverter from './extHostTypeConverters';
|
||||
import { Disposable, WorkspaceEdit } from './extHostTypes';
|
||||
import { Disposable, RelativePattern, WorkspaceEdit } from './extHostTypes';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { FileOperation } from 'vs/platform/files/common/files';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
|
||||
|
||||
class FileSystemWatcher implements vscode.FileSystemWatcher {
|
||||
|
||||
private readonly _onDidCreate = new Emitter<vscode.Uri>();
|
||||
private readonly _onDidChange = new Emitter<vscode.Uri>();
|
||||
private readonly _onDidDelete = new Emitter<vscode.Uri>();
|
||||
|
||||
private _disposable: Disposable;
|
||||
private _config: number;
|
||||
|
||||
@@ -36,7 +38,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(mainContext: IMainContext, workspace: IExtHostWorkspace, extension: IExtensionDescription, dispatcher: Event<FileSystemEvents>, globPattern: string | RelativePattern, ignoreCreateEvents?: boolean, ignoreChangeEvents?: boolean, ignoreDeleteEvents?: boolean) {
|
||||
const watcherDisposable = this.ensureWatching(mainContext, extension, globPattern);
|
||||
|
||||
this._config = 0;
|
||||
if (ignoreCreateEvents) {
|
||||
@@ -51,11 +54,18 @@ class FileSystemWatcher implements vscode.FileSystemWatcher {
|
||||
|
||||
const parsedPattern = parse(globPattern);
|
||||
|
||||
// 1.64.x behaviour change: given the new support to watch any folder
|
||||
// we start to ignore events outside the workspace when only a string
|
||||
// pattern is provided to avoid sending events to extensions that are
|
||||
// unexpected.
|
||||
// https://github.com/microsoft/vscode/issues/3025
|
||||
const excludeOutOfWorkspaceEvents = typeof globPattern === 'string';
|
||||
|
||||
const subscription = dispatcher(events => {
|
||||
if (!ignoreCreateEvents) {
|
||||
for (let created of events.created) {
|
||||
const uri = URI.revive(created);
|
||||
if (parsedPattern(uri.fsPath)) {
|
||||
if (parsedPattern(uri.fsPath) && (!excludeOutOfWorkspaceEvents || workspace.getWorkspaceFolder(uri))) {
|
||||
this._onDidCreate.fire(uri);
|
||||
}
|
||||
}
|
||||
@@ -63,7 +73,7 @@ class FileSystemWatcher implements vscode.FileSystemWatcher {
|
||||
if (!ignoreChangeEvents) {
|
||||
for (let changed of events.changed) {
|
||||
const uri = URI.revive(changed);
|
||||
if (parsedPattern(uri.fsPath)) {
|
||||
if (parsedPattern(uri.fsPath) && (!excludeOutOfWorkspaceEvents || workspace.getWorkspaceFolder(uri))) {
|
||||
this._onDidChange.fire(uri);
|
||||
}
|
||||
}
|
||||
@@ -71,14 +81,34 @@ class FileSystemWatcher implements vscode.FileSystemWatcher {
|
||||
if (!ignoreDeleteEvents) {
|
||||
for (let deleted of events.deleted) {
|
||||
const uri = URI.revive(deleted);
|
||||
if (parsedPattern(uri.fsPath)) {
|
||||
if (parsedPattern(uri.fsPath) && (!excludeOutOfWorkspaceEvents || workspace.getWorkspaceFolder(uri))) {
|
||||
this._onDidDelete.fire(uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this._disposable = Disposable.from(this._onDidCreate, this._onDidChange, this._onDidDelete, subscription);
|
||||
this._disposable = Disposable.from(watcherDisposable, this._onDidCreate, this._onDidChange, this._onDidDelete, subscription);
|
||||
}
|
||||
|
||||
private ensureWatching(mainContext: IMainContext, extension: IExtensionDescription, globPattern: string | RelativePattern): Disposable {
|
||||
let disposable = Disposable.from();
|
||||
|
||||
if (typeof globPattern === 'string') {
|
||||
return disposable; // a pattern alone does not carry sufficient information to start watching anything
|
||||
}
|
||||
|
||||
const proxy = mainContext.getProxy(MainContext.MainThreadFileSystem);
|
||||
|
||||
let recursive = false;
|
||||
if (globPattern.pattern.includes(GLOBSTAR) || globPattern.pattern.includes(GLOB_SPLIT)) {
|
||||
recursive = true; // only watch recursively if pattern indicates the need for it
|
||||
}
|
||||
|
||||
const session = Math.random();
|
||||
proxy.$watch(extension.identifier.value, session, globPattern.baseUri, { recursive, excludes: [] /* excludes are not yet surfaced in the API */ });
|
||||
|
||||
return Disposable.from({ dispose: () => proxy.$unwatch(session) });
|
||||
}
|
||||
|
||||
dispose() {
|
||||
@@ -118,9 +148,8 @@ export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServ
|
||||
readonly onDidCreateFile: Event<vscode.FileCreateEvent> = this._onDidCreateFile.event;
|
||||
readonly onDidDeleteFile: Event<vscode.FileDeleteEvent> = this._onDidDeleteFile.event;
|
||||
|
||||
|
||||
constructor(
|
||||
mainContext: IMainContext,
|
||||
private readonly _mainContext: IMainContext,
|
||||
private readonly _logService: ILogService,
|
||||
private readonly _extHostDocumentsAndEditors: ExtHostDocumentsAndEditors
|
||||
) {
|
||||
@@ -129,8 +158,8 @@ 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(workspace: IExtHostWorkspace, extension: IExtensionDescription, globPattern: string | RelativePattern, ignoreCreateEvents?: boolean, ignoreChangeEvents?: boolean, ignoreDeleteEvents?: boolean): vscode.FileSystemWatcher {
|
||||
return new FileSystemWatcher(this._mainContext, workspace, extension, this._onFileSystemEvent.event, globPattern, ignoreCreateEvents, ignoreChangeEvents, ignoreDeleteEvents);
|
||||
}
|
||||
|
||||
$onFileEvent(events: FileSystemEvents) {
|
||||
|
||||
@@ -1392,15 +1392,25 @@ export namespace GlobPattern {
|
||||
return pattern;
|
||||
}
|
||||
|
||||
if (isRelativePattern(pattern)) {
|
||||
return new types.RelativePattern(pattern.base, pattern.pattern);
|
||||
if (isRelativePattern(pattern) || isLegacyRelativePattern(pattern)) {
|
||||
return new types.RelativePattern(pattern.baseUri ?? pattern.base, pattern.pattern);
|
||||
}
|
||||
|
||||
return pattern; // preserve `undefined` and `null`
|
||||
}
|
||||
|
||||
function isRelativePattern(obj: any): obj is vscode.RelativePattern {
|
||||
export function isRelativePattern(obj: any): obj is vscode.RelativePattern {
|
||||
const rp = obj as vscode.RelativePattern;
|
||||
return rp && URI.isUri(rp.baseUri) && typeof rp.pattern === 'string';
|
||||
}
|
||||
|
||||
function isLegacyRelativePattern(obj: any): obj is { base: string, pattern: string } {
|
||||
|
||||
// Before 1.64.x, `RelativePattern` did not have any `baseUri: Uri`
|
||||
// property. To preserve backwards compatibility with older extensions
|
||||
// we allow this old format when creating the `vscode.RelativePattern`.
|
||||
|
||||
const rp = obj as { base: string, pattern: string };
|
||||
return rp && typeof rp.base === 'string' && typeof rp.pattern === 'string';
|
||||
}
|
||||
}
|
||||
@@ -1581,8 +1591,8 @@ export namespace NotebookExclusiveDocumentPattern {
|
||||
}
|
||||
|
||||
|
||||
if (isRelativePattern(pattern)) {
|
||||
return new types.RelativePattern(pattern.base, pattern.pattern);
|
||||
if (GlobPattern.isRelativePattern(pattern)) {
|
||||
return new types.RelativePattern(pattern.baseUri, pattern.pattern);
|
||||
}
|
||||
|
||||
if (isExclusivePattern(pattern)) {
|
||||
@@ -1601,11 +1611,8 @@ export namespace NotebookExclusiveDocumentPattern {
|
||||
return pattern;
|
||||
}
|
||||
|
||||
if (isRelativePattern(pattern)) {
|
||||
return {
|
||||
base: pattern.base,
|
||||
pattern: pattern.pattern
|
||||
};
|
||||
if (GlobPattern.isRelativePattern(pattern)) {
|
||||
return new types.RelativePattern(pattern.baseUri, pattern.pattern);
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -1628,11 +1635,6 @@ export namespace NotebookExclusiveDocumentPattern {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function isRelativePattern(obj: any): obj is vscode.RelativePattern {
|
||||
const rp = obj as vscode.RelativePattern;
|
||||
return rp && typeof rp.base === 'string' && typeof rp.pattern === 'string';
|
||||
}
|
||||
}
|
||||
|
||||
export namespace NotebookDecorationRenderOptions {
|
||||
|
||||
@@ -2370,15 +2370,26 @@ export enum ConfigurationTarget {
|
||||
|
||||
@es5ClassCompat
|
||||
export class RelativePattern implements IRelativePattern {
|
||||
base: string;
|
||||
|
||||
pattern: string;
|
||||
|
||||
// expose a `baseFolder: URI` property as a workaround for the short-coming
|
||||
// of `IRelativePattern` only supporting `base: string` which always translates
|
||||
// to a `file://` URI. With `baseFolder` we can support non-file based folders
|
||||
// in searches
|
||||
// (https://github.com/microsoft/vscode/commit/6326543b11cf4998c8fd1564cab5c429a2416db3)
|
||||
readonly baseFolder?: URI;
|
||||
private _base!: string;
|
||||
get base(): string {
|
||||
return this._base;
|
||||
}
|
||||
set base(base: string) {
|
||||
this._base = base;
|
||||
this._baseUri = URI.file(base);
|
||||
}
|
||||
|
||||
private _baseUri!: URI;
|
||||
get baseUri(): URI {
|
||||
return this._baseUri;
|
||||
}
|
||||
set baseUri(baseUri: URI) {
|
||||
this._baseUri = baseUri;
|
||||
this._base = baseUri.fsPath;
|
||||
}
|
||||
|
||||
constructor(base: vscode.WorkspaceFolder | URI | string, pattern: string) {
|
||||
if (typeof base !== 'string') {
|
||||
@@ -2392,14 +2403,11 @@ export class RelativePattern implements IRelativePattern {
|
||||
}
|
||||
|
||||
if (typeof base === 'string') {
|
||||
this.baseFolder = URI.file(base);
|
||||
this.base = base;
|
||||
this.baseUri = URI.file(base);
|
||||
} else if (URI.isUri(base)) {
|
||||
this.baseFolder = base;
|
||||
this.base = base.fsPath;
|
||||
this.baseUri = base;
|
||||
} else {
|
||||
this.baseFolder = base.uri;
|
||||
this.base = base.uri.fsPath;
|
||||
this.baseUri = base.uri;
|
||||
}
|
||||
|
||||
this.pattern = pattern;
|
||||
|
||||
@@ -579,7 +579,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac
|
||||
export const IExtHostWorkspace = createDecorator<IExtHostWorkspace>('IExtHostWorkspace');
|
||||
export interface IExtHostWorkspace extends ExtHostWorkspace, ExtHostWorkspaceShape, IExtHostWorkspaceProvider { }
|
||||
|
||||
function parseSearchInclude(include: RelativePattern | string | undefined): { includePattern?: string, folder?: URI; } {
|
||||
function parseSearchInclude(include: RelativePattern | vscode.GlobPattern | string | undefined): { includePattern?: string, folder?: URI; } {
|
||||
let includePattern: string | undefined;
|
||||
let includeFolder: URI | undefined;
|
||||
if (include) {
|
||||
@@ -587,7 +587,7 @@ function parseSearchInclude(include: RelativePattern | string | undefined): { in
|
||||
includePattern = include;
|
||||
} else {
|
||||
includePattern = include.pattern;
|
||||
includeFolder = include.baseFolder || URI.file(include.base);
|
||||
includeFolder = include.baseUri;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user