diff --git a/packages/language-server/src/lib/FallbackWatcher.ts b/packages/language-server/src/lib/FallbackWatcher.ts new file mode 100644 index 000000000..9e82e4bfa --- /dev/null +++ b/packages/language-server/src/lib/FallbackWatcher.ts @@ -0,0 +1,44 @@ +import { FSWatcher, watch } from 'chokidar'; +import { join } from 'path'; +import { DidChangeWatchedFilesParams, FileChangeType, FileEvent } from 'vscode-languageserver'; +import { pathToUrl } from '../utils'; + +type DidChangeHandler = (para: DidChangeWatchedFilesParams) => void; + +export class FallbackWatcher { + private readonly watcher: FSWatcher; + private readonly callbacks: DidChangeHandler[] = []; + + constructor(glob: string, workspacePaths: string[]) { + this.watcher = watch(workspacePaths.map((workspacePath) => join(workspacePath, glob))); + + this.watcher + .on('add', (path) => this.callback(path, FileChangeType.Created)) + .on('unlink', (path) => this.callback(path, FileChangeType.Deleted)) + .on('change', (path) => this.callback(path, FileChangeType.Changed)); + } + + private convert(path: string, type: FileChangeType): DidChangeWatchedFilesParams { + const event: FileEvent = { + type, + uri: pathToUrl(path) + }; + + return { + changes: [event] + }; + } + + private callback(path: string, type: FileChangeType) { + const para = this.convert(path, type); + this.callbacks.forEach((callback) => callback(para)); + } + + onDidChangeWatchedFiles(callback: DidChangeHandler) { + this.callbacks.push(callback); + } + + dispose() { + this.watcher.close(); + } +} diff --git a/packages/language-server/src/server.ts b/packages/language-server/src/server.ts index e42e1bee0..9b8d677d9 100644 --- a/packages/language-server/src/server.ts +++ b/packages/language-server/src/server.ts @@ -14,7 +14,8 @@ import { TextDocumentSyncKind, WorkspaceEdit, SemanticTokensRequest, - SemanticTokensRangeRequest + SemanticTokensRangeRequest, + DidChangeWatchedFilesParams } from 'vscode-languageserver'; import { IPCMessageReader, IPCMessageWriter, createConnection } from 'vscode-languageserver/node'; import { DiagnosticsManager } from './lib/DiagnosticsManager'; @@ -31,7 +32,8 @@ import { TypeScriptPlugin, OnWatchFileChangesPara } from './plugins'; -import { urlToPath } from './utils'; +import { isNotNullOrUndefined, urlToPath } from './utils'; +import { FallbackWatcher } from './lib/FallbackWatcher'; namespace TagCloseRequest { export const type: RequestType< @@ -85,6 +87,7 @@ export function startServer(options?: LSOptions) { const configManager = new LSConfigManager(); const pluginHost = new PluginHost(docManager); let sveltePlugin: SveltePlugin = undefined as any; + let watcher: FallbackWatcher | undefined; connection.onInitialize((evt) => { const workspaceUris = evt.workspaceFolders?.map((folder) => folder.uri.toString()) ?? [ @@ -95,6 +98,12 @@ export function startServer(options?: LSOptions) { Logger.error('No workspace path set'); } + if (!evt.capabilities.workspace?.didChangeWatchedFiles) { + const workspacePaths = workspaceUris.map(urlToPath).filter(isNotNullOrUndefined); + watcher = new FallbackWatcher('**/*.{ts,js}', workspacePaths); + watcher.onDidChangeWatchedFiles(onDidChangeWatchedFiles); + } + configManager.update(evt.initializationOptions?.config || {}); configManager.updateTsJsUserPreferences(evt.initializationOptions?.typescriptConfig || {}); configManager.updateEmmetConfig(evt.initializationOptions?.emmetConfig || {}); @@ -199,6 +208,10 @@ export function startServer(options?: LSOptions) { }; }); + connection.onExit(() => { + watcher?.dispose(); + }); + connection.onRenameRequest((req) => pluginHost.rename(req.textDocument, req.position, req.newName) ); @@ -285,7 +298,9 @@ export function startServer(options?: LSOptions) { ); const updateAllDiagnostics = _.debounce(() => diagnosticsManager.updateAll(), 1000); - connection.onDidChangeWatchedFiles((para) => { + + connection.onDidChangeWatchedFiles(onDidChangeWatchedFiles); + function onDidChangeWatchedFiles(para: DidChangeWatchedFilesParams) { const onWatchFileChangesParas = para.changes .map((change) => ({ fileName: urlToPath(change.uri), @@ -296,7 +311,8 @@ export function startServer(options?: LSOptions) { pluginHost.onWatchFileChanges(onWatchFileChangesParas); updateAllDiagnostics(); - }); + } + connection.onDidSaveTextDocument(updateAllDiagnostics); connection.onNotification('$/onDidChangeTsOrJsFile', async (e: any) => { const path = urlToPath(e.uri);