Skip to content

Commit 2a63d7b

Browse files
authored
1 parent 105eef7 commit 2a63d7b

File tree

4 files changed

+49
-18
lines changed

4 files changed

+49
-18
lines changed

src/vs/platform/files/common/watcher.ts

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -291,21 +291,26 @@ export function coalesceEvents(changes: IDiskFileChange[]): IDiskFileChange[] {
291291
return coalescer.coalesce();
292292
}
293293

294+
export function normalizeWatcherPattern(path: string, pattern: string | IRelativePattern): string | IRelativePattern {
295+
296+
// Patterns are always matched on the full absolute path
297+
// of the event. As such, if the pattern is not absolute
298+
// and is a string and does not start with a leading
299+
// `**`, we have to convert it to a relative pattern with
300+
// the given `base`
301+
302+
if (typeof pattern === 'string' && !pattern.startsWith(GLOBSTAR) && !isAbsolute(pattern)) {
303+
return { base: path, pattern };
304+
}
305+
306+
return pattern;
307+
}
308+
294309
export function parseWatcherPatterns(path: string, patterns: Array<string | IRelativePattern>): ParsedPattern[] {
295310
const parsedPatterns: ParsedPattern[] = [];
296311

297312
for (const pattern of patterns) {
298-
let normalizedPattern = pattern;
299-
300-
// Patterns are always matched on the full absolute path
301-
// of the event. As such, if the pattern is not absolute
302-
// and does not start with a leading `**`, we have to
303-
// convert it to a relative pattern with the given `base`
304-
if (typeof normalizedPattern === 'string' && !normalizedPattern.startsWith(GLOBSTAR) && !isAbsolute(normalizedPattern)) {
305-
normalizedPattern = { base: path, pattern: normalizedPattern };
306-
}
307-
308-
parsedPatterns.push(parse(normalizedPattern));
313+
parsedPatterns.push(parse(normalizeWatcherPattern(path, pattern)));
309314
}
310315

311316
return parsedPatterns;

src/vs/platform/files/node/watcher/nodejs/nodejsWatcher.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export class NodeJSWatcher extends Disposable implements INonRecursiveWatcher {
6161
// Logging
6262

6363
if (requestsToStartWatching.length) {
64-
this.trace(`Request to start watching: ${requestsToStartWatching.map(request => `${request.path} (excludes: ${request.excludes.length > 0 ? request.excludes : '<none>'}, includes: ${request.includes && request.includes.length > 0 ? request.includes : '<all>'})`).join(',')}`);
64+
this.trace(`Request to start watching: ${requestsToStartWatching.map(request => `${request.path} (excludes: ${request.excludes.length > 0 ? request.excludes : '<none>'}, includes: ${request.includes && request.includes.length > 0 ? JSON.stringify(request.includes) : '<all>'})`).join(',')}`);
6565
}
6666

6767
if (pathsToStopWatching.length) {

src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ export class ParcelWatcher extends Disposable implements IRecursiveWatcher {
145145
// Logging
146146

147147
if (requestsToStartWatching.length) {
148-
this.trace(`Request to start watching: ${requestsToStartWatching.map(request => `${request.path} (excludes: ${request.excludes.length > 0 ? request.excludes : '<none>'}, includes: ${request.includes && request.includes.length > 0 ? request.includes : '<all>'})`).join(',')}`);
148+
this.trace(`Request to start watching: ${requestsToStartWatching.map(request => `${request.path} (excludes: ${request.excludes.length > 0 ? request.excludes : '<none>'}, includes: ${request.includes && request.includes.length > 0 ? JSON.stringify(request.includes) : '<all>'})`).join(',')}`);
149149
}
150150

151151
if (pathsToStopWatching.length) {

src/vs/workbench/api/browser/mainThreadFileSystem.ts

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace
1414
import { ILogService } from 'vs/platform/log/common/log';
1515
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
1616
import { IWorkbenchFileService } from 'vs/workbench/services/files/common/files';
17+
import { normalizeWatcherPattern } from 'vs/platform/files/common/watcher';
18+
import { GLOBSTAR } from 'vs/base/common/glob';
19+
import { rtrim } from 'vs/base/common/strings';
1720

1821
@extHostNamedCustomer(MainContext.MainThreadFileSystem)
1922
export class MainThreadFileSystem implements MainThreadFileSystemShape {
@@ -163,17 +166,34 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape {
163166
return this._fileService.activateProvider(scheme);
164167
}
165168

166-
$watch(extensionId: string, session: number, resource: UriComponents, opts: IWatchOptions): void {
169+
async $watch(extensionId: string, session: number, resource: UriComponents, unvalidatedOpts: IWatchOptions): Promise<void> {
167170
const uri = URI.revive(resource);
168-
const isInsideWorkspace = this._contextService.isInsideWorkspace(uri);
171+
const workspaceFolder = this._contextService.getWorkspaceFolder(uri);
172+
173+
const opts = { ...unvalidatedOpts };
174+
175+
// Convert a recursive watcher to a flat watcher if the path
176+
// turns out to not be a folder. Recursive watching is only
177+
// possible on folders, so we help all file watchers by checking
178+
// early.
179+
if (opts.recursive) {
180+
try {
181+
const stat = await this._fileService.stat(uri);
182+
if (!stat.isDirectory) {
183+
opts.recursive = false;
184+
}
185+
} catch (error) {
186+
this._logService.error(`MainThreadFileSystem#$watch(): failed to stat a resource for file watching (extension: ${extensionId}, path: ${uri.toString(true)}, recursive: ${opts.recursive}, session: ${session}): ${error}`);
187+
}
188+
}
169189

170190
// Refuse to watch anything that is already watched via
171191
// our workspace watchers in case the request is a
172192
// recursive file watcher.
173193
// Still allow for non-recursive watch requests as a way
174194
// to bypass configured exlcude rules though
175195
// (see https://github.com/microsoft/vscode/issues/146066)
176-
if (isInsideWorkspace && opts.recursive) {
196+
if (workspaceFolder && opts.recursive) {
177197
this._logService.trace(`MainThreadFileSystem#$watch(): ignoring request to start watching because path is inside workspace (extension: ${extensionId}, path: ${uri.toString(true)}, recursive: ${opts.recursive}, session: ${session})`);
178198
return;
179199
}
@@ -200,7 +220,12 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape {
200220
// excluded via `files.watcherExclude`. As such, we configure
201221
// to include each configured exclude pattern so that only those
202222
// events are reported that are otherwise excluded.
203-
else if (isInsideWorkspace) {
223+
// However, we cannot just use the pattern as is, because a pattern
224+
// such as `bar` for a exclude, will work to exclude any of
225+
// `<workspace path>/bar` but will not work as include for files within
226+
// `bar` unless a suffix of `/**` if added.
227+
// (https://github.com/microsoft/vscode/issues/148245)
228+
else if (workspaceFolder) {
204229
const config = this._configurationService.getValue<IFilesConfiguration>();
205230
if (config.files?.watcherExclude) {
206231
for (const key in config.files.watcherExclude) {
@@ -209,7 +234,8 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape {
209234
opts.includes = [];
210235
}
211236

212-
opts.includes.push(key);
237+
const includePattern = `${rtrim(key, '/')}/${GLOBSTAR}`;
238+
opts.includes.push(normalizeWatcherPattern(workspaceFolder.uri.fsPath, includePattern));
213239
}
214240
}
215241
}

0 commit comments

Comments
 (0)