diff --git a/extensions/typescript-language-features/src/extension.browser.ts b/extensions/typescript-language-features/src/extension.browser.ts index 7b5c15f1846..25d99e13a14 100644 --- a/extensions/typescript-language-features/src/extension.browser.ts +++ b/extensions/typescript-language-features/src/extension.browser.ts @@ -78,7 +78,7 @@ export async function activate(context: vscode.ExtensionContext): Promise { logDirectoryProvider: noopLogDirectoryProvider, cancellerFactory: noopRequestCancellerFactory, versionProvider, - processFactory: new WorkerServerProcessFactory(context.extensionUri), + processFactory: new WorkerServerProcessFactory(context.extensionUri, logger), activeJsTsEditorTracker, serviceConfigurationProvider: new BrowserServiceConfigurationProvider(), experimentTelemetryReporter, diff --git a/extensions/typescript-language-features/src/tsServer/fileWatchingManager.ts b/extensions/typescript-language-features/src/tsServer/fileWatchingManager.ts index bc4f6d5b973..d67c80833c5 100644 --- a/extensions/typescript-language-features/src/tsServer/fileWatchingManager.ts +++ b/extensions/typescript-language-features/src/tsServer/fileWatchingManager.ts @@ -6,6 +6,7 @@ import * as vscode from 'vscode'; import { Utils } from 'vscode-uri'; import { Schemes } from '../configuration/schemes'; +import { Logger } from '../logging/logger'; import { disposeAll, IDisposable } from '../utils/dispose'; import { ResourceMap } from '../utils/resourceMap'; @@ -18,19 +19,27 @@ type DirWatcherEntry = { export class FileWatcherManager { private readonly _fileWatchers = new Map(); private readonly _dirWatchers = new ResourceMap<{ + readonly uri: vscode.Uri; readonly watcher: vscode.FileSystemWatcher; refCount: number; }>(uri => uri.toString(), { onCaseInsensitiveFileSystem: false }); + constructor( + private readonly logger: Logger, + ) { } + create(id: number, uri: vscode.Uri, watchParentDirs: boolean, isRecursive: boolean, listeners: { create?: (uri: vscode.Uri) => void; change?: (uri: vscode.Uri) => void; delete?: (uri: vscode.Uri) => void }): void { + this.logger.trace(`Creating file watcher for ${uri.toString()}`); + const watcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(uri, isRecursive ? '**' : '*'), !listeners.create, !listeners.change, !listeners.delete); const parentDirWatchers: DirWatcherEntry[] = []; - this._fileWatchers.set(id, { watcher, dirWatchers: parentDirWatchers }); + this._fileWatchers.set(id, { uri, watcher, dirWatchers: parentDirWatchers }); if (listeners.create) { watcher.onDidCreate(listeners.create); } if (listeners.change) { watcher.onDidChange(listeners.change); } @@ -43,9 +52,10 @@ export class FileWatcherManager { let parentDirWatcher = this._dirWatchers.get(dirUri); if (!parentDirWatcher) { + this.logger.trace(`Creating parent dir watcher for ${dirUri.toString()}`); const glob = new vscode.RelativePattern(Utils.dirname(dirUri), Utils.basename(dirUri)); const parentWatcher = vscode.workspace.createFileSystemWatcher(glob, !listeners.create, true, !listeners.delete); - parentDirWatcher = { refCount: 0, watcher: parentWatcher }; + parentDirWatcher = { uri: dirUri, refCount: 0, watcher: parentWatcher }; this._dirWatchers.set(dirUri, parentDirWatcher); } parentDirWatcher.refCount++; @@ -75,15 +85,19 @@ export class FileWatcherManager { } } + delete(id: number): void { const entry = this._fileWatchers.get(id); if (entry) { + this.logger.trace(`Deleting file watcher for ${entry.uri}`); + for (const dirWatcher of entry.dirWatchers) { disposeAll(dirWatcher.listeners); const dirWatcherEntry = this._dirWatchers.get(dirWatcher.uri); if (dirWatcherEntry) { if (--dirWatcherEntry.refCount <= 0) { + this.logger.trace(`Deleting parent dir ${dirWatcherEntry.uri}`); dirWatcherEntry.watcher.dispose(); this._dirWatchers.delete(dirWatcher.uri); } diff --git a/extensions/typescript-language-features/src/tsServer/serverProcess.browser.ts b/extensions/typescript-language-features/src/tsServer/serverProcess.browser.ts index cd54bdca2fb..06681cb5b82 100644 --- a/extensions/typescript-language-features/src/tsServer/serverProcess.browser.ts +++ b/extensions/typescript-language-features/src/tsServer/serverProcess.browser.ts @@ -7,6 +7,7 @@ import { ServiceConnection } from '@vscode/sync-api-common/browser'; import { ApiService, Requests } from '@vscode/sync-api-service'; import * as vscode from 'vscode'; import { TypeScriptServiceConfiguration } from '../configuration/configuration'; +import { Logger } from '../logging/logger'; import { FileWatcherManager } from './fileWatchingManager'; import type * as Proto from './protocol/protocol'; import { TsServerLog, TsServerProcess, TsServerProcessFactory, TsServerProcessKind } from './server'; @@ -30,6 +31,7 @@ type BrowserWatchEvent = { export class WorkerServerProcessFactory implements TsServerProcessFactory { constructor( private readonly _extensionUri: vscode.Uri, + private readonly _logger: Logger, ) { } public fork( @@ -47,7 +49,7 @@ export class WorkerServerProcessFactory implements TsServerProcessFactory { // Explicitly give TS Server its path so it can // load local resources '--executingFilePath', tsServerPath, - ], tsServerLog); + ], tsServerLog, this._logger); } } @@ -60,7 +62,7 @@ class WorkerServerProcess implements TsServerProcess { private readonly _onDataHandlers = new Set<(data: Proto.Response) => void>(); private readonly _onErrorHandlers = new Set<(err: Error) => void>(); private readonly _onExitHandlers = new Set<(code: number | null, signal: string | null) => void>(); - private readonly watches = new FileWatcherManager(); + private readonly _watches: FileWatcherManager; private readonly worker: Worker; @@ -77,9 +79,12 @@ class WorkerServerProcess implements TsServerProcess { extensionUri: vscode.Uri, args: readonly string[], private readonly tsServerLog: TsServerLog | undefined, + logger: Logger, ) { this.worker = new Worker(tsServerPath, { name: `TS ${kind} server #${this.id}` }); + this._watches = new FileWatcherManager(logger); + const tsserverChannel = new MessageChannel(); const watcherChannel = new MessageChannel(); const syncChannel = new MessageChannel(); @@ -100,12 +105,12 @@ class WorkerServerProcess implements TsServerProcess { this.watcher.onmessage = (event: MessageEvent) => { switch (event.data.type) { case 'dispose': { - this.watches.delete(event.data.id); + this._watches.delete(event.data.id); break; } case 'watchDirectory': case 'watchFile': { - this.watches.create(event.data.id, vscode.Uri.from(event.data.uri), /*watchParentDirs*/ true, !!event.data.recursive, { + this._watches.create(event.data.id, vscode.Uri.from(event.data.uri), /*watchParentDirs*/ true, !!event.data.recursive, { change: uri => this.watcher.postMessage({ type: 'watch', event: 'change', uri }), create: uri => this.watcher.postMessage({ type: 'watch', event: 'create', uri }), delete: uri => this.watcher.postMessage({ type: 'watch', event: 'delete', uri }), diff --git a/extensions/typescript-language-features/web/webServer.ts b/extensions/typescript-language-features/web/webServer.ts index dd8b9d401a5..6580a84995c 100644 --- a/extensions/typescript-language-features/web/webServer.ts +++ b/extensions/typescript-language-features/web/webServer.ts @@ -46,24 +46,34 @@ function fromResource(extensionUri: URI, uri: URI) { } return `/${uri.scheme}/${uri.authority}${uri.path}`; } + function updateWatch(event: 'create' | 'change' | 'delete', uri: URI, extensionUri: URI) { - const kind = event === 'create' ? ts.FileWatcherEventKind.Created - : event === 'change' ? ts.FileWatcherEventKind.Changed - : event === 'delete' ? ts.FileWatcherEventKind.Deleted - : ts.FileWatcherEventKind.Changed; + const kind = toTsWatcherKind(event); const path = fromResource(extensionUri, uri); - if (watchFiles.has(path)) { - watchFiles.get(path)!.callback(path, kind); + + const fileWatcher = watchFiles.get(path); + if (fileWatcher) { + fileWatcher.callback(path, kind); return; } - let found = false; + for (const watch of Array.from(watchDirectories.keys()).filter(dir => path.startsWith(dir))) { watchDirectories.get(watch)!.callback(path); - found = true; + return; } - if (!found) { - console.error(`no watcher found for ${path}`); + + console.error(`no watcher found for ${path}`); +} + +function toTsWatcherKind(event: 'create' | 'change' | 'delete') { + if (event === 'create') { + return ts.FileWatcherEventKind.Created; + } else if (event === 'change') { + return ts.FileWatcherEventKind.Changed; + } else if (event === 'delete') { + return ts.FileWatcherEventKind.Deleted; } + throw new Error(`Unknown event: ${event}`); } type ServerHostWithImport = ts.server.ServerHost & { importPlugin(root: string, moduleName: string): Promise };