Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fall back to chokidar file watcher if the lsp client doesn't support capabilities.workspace.didChangeWatchedFiles.dynamicRegistration #1011

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
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';

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);
}

Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,13 @@ export interface WorkspaceServices {
backgroundAnalysis: BackgroundAnalysisBase | undefined;
}

export interface ServerOptions {
export interface ServerOptions<T extends FileWatcherHandler = FileWatcherHandler> {
productName: string;
rootDirectory: Uri;
version: string;
cancellationProvider: CancellationProvider;
serviceProvider: ServiceProvider;
fileWatcherHandler: FileWatcherHandler;
fileWatcherHandler: T;
maxAnalysisTimeInForeground?: MaxAnalysisTime;
disableChecker?: boolean;
supportedCommands?: string[];
Expand Down
33 changes: 33 additions & 0 deletions packages/pyright-internal/src/common/realFileSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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() {
Expand All @@ -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
Expand Down
8 changes: 5 additions & 3 deletions packages/pyright-internal/src/languageServerBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<T extends FileWatcherHandler = FileWatcherHandler>
implements LanguageServerInterface, Disposable
{
// We support running only one "find all reference" at a time.
private _pendingFindAllRefsCancellationSource: AbstractCancellationTokenSource | undefined;

Expand Down Expand Up @@ -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<T>, protected connection: Connection) {
// Stash the base directory into a global variable.
// This must happen before fs.getModulePath().
(global as any).__rootDirectory = serverOptions.rootDirectory.getFilePath();
Expand Down
6 changes: 4 additions & 2 deletions packages/pyright-internal/src/realLanguageServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,17 @@ 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<T> {
protected controller: CommandController;
constructor(
connection: Connection,
maxWorkers: number,
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 || '';
Expand Down
16 changes: 14 additions & 2 deletions packages/pyright-internal/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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<WorkspaceFileWatcherProvider> {
constructor(connection: Connection, maxWorkers: number, realFileSystem?: FileSystem) {
const tempFile = new RealTempFile();
const console = new ConsoleWithLogLevel(connection.console);
Expand All @@ -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<InitializeResult> {
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);
}
Expand Down
Loading