diff --git a/packages/pyright-internal/src/common/chokidarFileWatcherProvider.ts b/packages/pyright-internal/src/common/chokidarFileWatcherProvider.ts index 1f952f0ba..b45328fb1 100644 --- a/packages/pyright-internal/src/common/chokidarFileWatcherProvider.ts +++ b/packages/pyright-internal/src/common/chokidarFileWatcherProvider.ts @@ -9,7 +9,7 @@ import * as chokidar from 'chokidar'; import { ConsoleInterface } from './console'; -import { FileWatcher, FileWatcherEventHandler, FileWatcherProvider } from './fileWatcher'; +import { FileWatcherEventHandler, FileWatcherProvider } from './fileWatcher'; const _isMacintosh = process.platform === 'darwin'; const _isLinux = process.platform === 'linux'; @@ -17,7 +17,7 @@ const _isLinux = process.platform === 'linux'; export class ChokidarFileWatcherProvider implements FileWatcherProvider { constructor(private _console?: ConsoleInterface) {} - createFileWatcher(paths: string[], listener: FileWatcherEventHandler): FileWatcher { + createFileWatcher(paths: string[], listener: FileWatcherEventHandler): chokidar.FSWatcher { return this._createFileSystemWatcher(paths).on('all', listener); } @@ -56,8 +56,8 @@ export class ChokidarFileWatcherProvider implements FileWatcherProvider { watcherOptions.ignored = excludes; const watcher = chokidar.watch(paths, watcherOptions); - watcher.on('error', (_) => { - this._console?.error('Error returned from file system watcher.'); + watcher.on('error', (e) => { + this._console?.error(`Error returned from file system watcher: ${e}`); }); // Detect if for some reason the native watcher library fails to load diff --git a/packages/pyright-internal/src/common/realFileSystem.ts b/packages/pyright-internal/src/common/realFileSystem.ts index 8fea1bf2b..41f3fea48 100644 --- a/packages/pyright-internal/src/common/realFileSystem.ts +++ b/packages/pyright-internal/src/common/realFileSystem.ts @@ -26,6 +26,8 @@ import { FileUri, FileUriSchema } from './uri/fileUri'; import { Uri } from './uri/uri'; import { getRootUri } from './uri/uriUtils'; import { isMainThread } from './workersHost'; +import { ChokidarFileWatcherProvider } from './chokidarFileWatcherProvider'; +import { FSWatcher } from 'chokidar'; // Automatically remove files created by tmp at process exit. tmp.setGracefulCleanup(); @@ -466,10 +468,38 @@ interface WorkspaceFileWatcher extends FileWatcher { eventHandler: FileWatcherEventHandler; } +/** + * file watcher provider for lsp clients that support `capabilities.workspace.didChangeWatchedFiles.dynamicRegistration`. + * + * this class also contains functionality to convert its file watchers to chokidar ones. this is a bit gross but necessary + * because this class is created before we know which kid of file watcher the lsp client requires. + */ export class WorkspaceFileWatcherProvider implements FileWatcherProvider, FileWatcherHandler { private _fileWatchers: WorkspaceFileWatcher[] = []; + private _chokidarFileWatchers?: FSWatcher[]; + private _chokidarFileWatcherProvider?: ChokidarFileWatcherProvider; + + /** + * converts all file watchers created with {@link createFileWatcher} to chokidar file watchers. + */ + convertToChokidar = (console: ConsoleInterface) => { + console.info(`existing file watchers: ${this._fileWatchers}`); + if (this._chokidarFileWatchers) { + return; + } + this._chokidarFileWatcherProvider = new ChokidarFileWatcherProvider(console); + this._chokidarFileWatchers = this._fileWatchers.map((fileWatcher) => { + return this._chokidarFileWatcherProvider!.createFileWatcher( + fileWatcher.workspacePaths, + fileWatcher.eventHandler + ); + }); + }; createFileWatcher(workspacePaths: string[], listener: FileWatcherEventHandler): FileWatcher { + if (this._chokidarFileWatcherProvider) { + return this._chokidarFileWatcherProvider.createFileWatcher(workspacePaths, listener); + } const self = this; const fileWatcher: WorkspaceFileWatcher = { close() { @@ -487,6 +517,10 @@ export class WorkspaceFileWatcherProvider implements FileWatcherProvider, FileWa } onFileChange(eventType: FileWatcherEventType, fileUri: Uri): void { + if (this._chokidarFileWatchers) { + this._chokidarFileWatchers.forEach((fileWatcher) => fileWatcher.emit(eventType, fileUri.getFilePath())); + return; + } // Since file watcher is a server wide service, we don't know which watcher is // for which workspace (for multi workspace case), also, we don't know which watcher // is for source or library. so we need to solely rely on paths that can cause us diff --git a/packages/pyright-internal/src/languageServerBase.ts b/packages/pyright-internal/src/languageServerBase.ts index 49a2b9196..a9f51aff0 100644 --- a/packages/pyright-internal/src/languageServerBase.ts +++ b/packages/pyright-internal/src/languageServerBase.ts @@ -146,6 +146,7 @@ import { SemanticTokensProvider, SemanticTokensProviderLegend } from './language import { RenameUsageFinder } from './analyzer/renameUsageFinder'; import { BaselineHandler } from './baseline'; import { assert } from './common/debug'; +import { WorkspaceFileWatcherProvider } from './common/realFileSystem'; export abstract class LanguageServerBase implements LanguageServerInterface, Disposable { // We support running only one "find all reference" at a time. @@ -594,6 +595,8 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis this.workspaceFactory ) ); + } else if (this.serverOptions.fileWatcherHandler instanceof WorkspaceFileWatcherProvider) { + this.serverOptions.fileWatcherHandler.convertToChokidar(this.console); } const result: InitializeResult = { capabilities: {