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/languageServerInterface.ts b/packages/pyright-internal/src/common/languageServerInterface.ts index 496964676..ac0bd39ba 100644 --- a/packages/pyright-internal/src/common/languageServerInterface.ts +++ b/packages/pyright-internal/src/common/languageServerInterface.ts @@ -84,13 +84,13 @@ export interface WorkspaceServices { backgroundAnalysis: BackgroundAnalysisBase | undefined; } -export interface ServerOptions { +export interface ServerOptions { productName: string; rootDirectory: Uri; version: string; cancellationProvider: CancellationProvider; serviceProvider: ServiceProvider; - fileWatcherHandler: FileWatcherHandler; + fileWatcherHandler: T; maxAnalysisTimeInForeground?: MaxAnalysisTime; disableChecker?: boolean; supportedCommands?: string[]; diff --git a/packages/pyright-internal/src/common/realFileSystem.ts b/packages/pyright-internal/src/common/realFileSystem.ts index 8fea1bf2b..58d2155d7 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,37 @@ 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) => { + 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 +516,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..096aad180 100644 --- a/packages/pyright-internal/src/languageServerBase.ts +++ b/packages/pyright-internal/src/languageServerBase.ts @@ -106,7 +106,7 @@ import { Diagnostic as AnalyzerDiagnostic, DiagnosticCategory } from './common/d import { DiagnosticRule } from './common/diagnosticRules'; import { FileDiagnostics } from './common/diagnosticSink'; import { FileSystem, ReadOnlyFileSystem } from './common/fileSystem'; -import { FileWatcherEventType } from './common/fileWatcher'; +import { FileWatcherEventType, FileWatcherHandler } from './common/fileWatcher'; import { Host } from './common/host'; import { ClientCapabilities, @@ -147,7 +147,9 @@ import { RenameUsageFinder } from './analyzer/renameUsageFinder'; import { BaselineHandler } from './baseline'; import { assert } from './common/debug'; -export abstract class LanguageServerBase implements LanguageServerInterface, Disposable { +export abstract class LanguageServerBase + implements LanguageServerInterface, Disposable +{ // We support running only one "find all reference" at a time. private _pendingFindAllRefsCancellationSource: AbstractCancellationTokenSource | undefined; @@ -199,7 +201,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis protected readonly dynamicFeatures = new DynamicFeatures(); - constructor(protected serverOptions: ServerOptions, protected connection: Connection) { + constructor(protected serverOptions: ServerOptions, protected connection: Connection) { // Stash the base directory into a global variable. // This must happen before fs.getModulePath(). (global as any).__rootDirectory = serverOptions.rootDirectory.getFilePath(); diff --git a/packages/pyright-internal/src/realLanguageServer.ts b/packages/pyright-internal/src/realLanguageServer.ts index 631353a05..2b175c925 100644 --- a/packages/pyright-internal/src/realLanguageServer.ts +++ b/packages/pyright-internal/src/realLanguageServer.ts @@ -56,7 +56,9 @@ class ErrorNotificationConsole extends ConsoleWithLogLevel { } //TODO: better name. this class is used by both the node and web language server, but not the test one -export abstract class RealLanguageServer extends LanguageServerBase { +export abstract class RealLanguageServer< + T extends FileWatcherHandler = FileWatcherHandler +> extends LanguageServerBase { protected controller: CommandController; constructor( connection: Connection, @@ -64,7 +66,7 @@ export abstract class RealLanguageServer extends LanguageServerBase { realFileSystem: FileSystem, cancellationProvider: CancellationProvider, tempFile: TempFile, - fileWatcherProvider: FileWatcherHandler + fileWatcherProvider: T ) { // eslint-disable-next-line @typescript-eslint/no-var-requires const version = require('../package.json').version || ''; diff --git a/packages/pyright-internal/src/server.ts b/packages/pyright-internal/src/server.ts index ba6c64f72..477177b20 100644 --- a/packages/pyright-internal/src/server.ts +++ b/packages/pyright-internal/src/server.ts @@ -4,7 +4,7 @@ * Implements pyright language server. */ -import { Connection } from 'vscode-languageserver'; +import { Connection, InitializeParams, InitializeResult } from 'vscode-languageserver'; import { BackgroundAnalysis } from './backgroundAnalysis'; import { BackgroundAnalysisBase } from './backgroundAnalysisBase'; @@ -18,7 +18,7 @@ import { Host } from './common/host'; import { RealTempFile, WorkspaceFileWatcherProvider, createFromRealFileSystem } from './common/realFileSystem'; import { RealLanguageServer } from './realLanguageServer'; -export class PyrightServer extends RealLanguageServer { +export class PyrightServer extends RealLanguageServer { constructor(connection: Connection, maxWorkers: number, realFileSystem?: FileSystem) { const tempFile = new RealTempFile(); const console = new ConsoleWithLogLevel(connection.console); @@ -44,6 +44,18 @@ export class PyrightServer extends RealLanguageServer { return new BackgroundAnalysis(this.serverOptions.serviceProvider); } + protected override initialize( + params: InitializeParams, + supportedCommands: string[], + supportedCodeActions: string[] + ): Promise { + const result = super.initialize(params, supportedCommands, supportedCodeActions); + if (!this.client.hasWatchFileCapability) { + this.serverOptions.fileWatcherHandler.convertToChokidar(this.console); + } + return result; + } + protected override createHost(): Host { return new FullAccessHost(this.serverOptions.serviceProvider); }