From 0288f0fd4fac47603137ef618f67ffe9cf727d23 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Fri, 6 Jan 2023 14:23:29 -0800 Subject: [PATCH] Switch to markdown extension's FileWatcherManager I'm not sure if it's OK to depend on a module from another extension; it's probably better to include the files from a central place instead. --- .../src/client/client.ts | 2 +- .../src/client/fileWatchingManager.ts | 6 ++-- .../src/tsServer/serverProcess.browser.ts | 29 +++++++++---------- .../web/README.md | 5 +++- .../web/webServer.ts | 29 +++++++++++++------ 5 files changed, 41 insertions(+), 30 deletions(-) diff --git a/extensions/markdown-language-features/src/client/client.ts b/extensions/markdown-language-features/src/client/client.ts index 82fbc7edaed2c..dfec3a870e300 100644 --- a/extensions/markdown-language-features/src/client/client.ts +++ b/extensions/markdown-language-features/src/client/client.ts @@ -126,7 +126,7 @@ export async function startClient(factory: LanguageClientConstructor, parser: IM client.sendRequest(proto.fs_watcher_onChange, { id, uri: params.uri, kind }); }; - watchers.create(id, uri, params.watchParentDirs, { + watchers.create(id, uri, params.watchParentDirs, /*isRecursive*/ false, { create: params.options.ignoreCreate ? undefined : () => sendWatcherChange('create'), change: params.options.ignoreChange ? undefined : () => sendWatcherChange('change'), delete: params.options.ignoreDelete ? undefined : () => sendWatcherChange('delete'), diff --git a/extensions/markdown-language-features/src/client/fileWatchingManager.ts b/extensions/markdown-language-features/src/client/fileWatchingManager.ts index dabb8f1fc880b..29e42449367a0 100644 --- a/extensions/markdown-language-features/src/client/fileWatchingManager.ts +++ b/extensions/markdown-language-features/src/client/fileWatchingManager.ts @@ -27,8 +27,8 @@ export class FileWatcherManager { refCount: number; }>(); - create(id: number, uri: vscode.Uri, watchParentDirs: boolean, listeners: { create?: () => void; change?: () => void; delete?: () => void }): void { - const watcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(uri, '*'), !listeners.create, !listeners.change, !listeners.delete); + 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 { + 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 }); @@ -56,7 +56,7 @@ export class FileWatcherManager { try { const stat = await vscode.workspace.fs.stat(uri); if (stat.type === vscode.FileType.File) { - listeners.create!(); + listeners.create!(uri); } } catch { // Noop diff --git a/extensions/typescript-language-features/src/tsServer/serverProcess.browser.ts b/extensions/typescript-language-features/src/tsServer/serverProcess.browser.ts index 03813b16d5b02..8a806c68ba366 100644 --- a/extensions/typescript-language-features/src/tsServer/serverProcess.browser.ts +++ b/extensions/typescript-language-features/src/tsServer/serverProcess.browser.ts @@ -12,14 +12,19 @@ import { TypeScriptVersion } from './versionProvider'; import { ServiceConnection } from '@vscode/sync-api-common/browser'; import { Requests, ApiService } from '@vscode/sync-api-service'; import { TypeScriptVersionManager } from './versionManager'; +import { FileWatcherManager } from '../../../markdown-language-features/src/client/fileWatchingManager'; type BrowserWatchEvent = { - type: 'dispose' | 'watchDirectory' | 'watchFile'; + type: 'watchDirectory' | 'watchFile'; recursive?: boolean; uri: { scheme: string; authority: string; path: string; }; + id: number; +} | { + type: 'dispose'; + id: number; }; export class WorkerServerProcess implements TsServerProcess { @@ -45,7 +50,7 @@ export 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 fsWatchers = new Map(); + private readonly watches = new FileWatcherManager(); /** For communicating with TS server synchronously */ private readonly tsserver: MessagePort; /** For communicating watches asynchronously */ @@ -77,24 +82,16 @@ export class WorkerServerProcess implements TsServerProcess { this.watcher.onmessage = (event: MessageEvent) => { switch (event.data.type) { case 'dispose': { - const uri = vscode.Uri.from(event.data.uri); - const fsWatcher = this.fsWatchers.get(uri); - if (fsWatcher) { - fsWatcher.dispose(); - this.fsWatchers.delete(uri); - } else { - console.error(`tried to dispose watcher for unwatched path: ${JSON.stringify(event.data)}`); - } + this.watches.delete(event.data.id); break; } case 'watchDirectory': case 'watchFile': { - const uri = vscode.Uri.from(event.data.uri); - const fsWatcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(uri, event.data.recursive ? '**' : '')); - fsWatcher.onDidChange(e => this.watcher.postMessage({ type: 'watch', event: 'change', uri: e })); - fsWatcher.onDidCreate(e => this.watcher.postMessage({ type: 'watch', event: 'create', uri: e })); - fsWatcher.onDidDelete(e => this.watcher.postMessage({ type: 'watch', event: 'delete', uri: e })); - this.fsWatchers.set(uri, fsWatcher); + 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 }), + }); break; } default: diff --git a/extensions/typescript-language-features/web/README.md b/extensions/typescript-language-features/web/README.md index 6a486ad6d4418..6a0b9b7c71870 100644 --- a/extensions/typescript-language-features/web/README.md +++ b/extensions/typescript-language-features/web/README.md @@ -43,11 +43,14 @@ Language server host for typescript using vscode's sync-api in the browser - looks like `isWeb()` is a way to check for being on the web - [x] create multiple watchers - on-demand instead of watching everything and checking on watch firing -- [ ] get file watching to work +- [x] get file watching to work - it could *already* work, I just don't know how to test it - look at extensions/markdown-language-features/src/client/fileWatchingManager.ts to see if I can use that - later: it is OK. its main difference is that you can watch files in not-yet-created directories, and it maintains a web of directory watches that then check whether the file is eventually created. + - even later: well, it works even though it is similar to my code. + I'm not sure what is different. + - [x] Find out scheme the web actually uses instead of vscode-test-web (or switch over entirely to isWeb) - [x] Need to parse and pass args through so that the syntax server isn't hard-coded to actually be another semantic server - [ ] clear out TODOs diff --git a/extensions/typescript-language-features/web/webServer.ts b/extensions/typescript-language-features/web/webServer.ts index 969b5c7a15a8e..90b3445a6edb3 100644 --- a/extensions/typescript-language-features/web/webServer.ts +++ b/extensions/typescript-language-features/web/webServer.ts @@ -24,7 +24,8 @@ function fromResource(extensionUri: URI, uri: URI) { } return `/${uri.scheme}/${uri.authority}${uri.path}` } -function createServerHost(extensionUri: URI, logger: ts.server.Logger, apiClient: ApiClient, args: string[], fsWatcher: MessagePort): ts.server.ServerHost { +type ServerHostWithImport = ts.server.ServerHost & { importPlugin(root: string, moduleName: string): Promise }; +function createServerHost(extensionUri: URI, logger: ts.server.Logger, apiClient: ApiClient, args: string[], fsWatcher: MessagePort): ServerHostWithImport { /** * Copied from toResource in typescriptServiceClient.ts */ @@ -45,6 +46,7 @@ function createServerHost(extensionUri: URI, logger: ts.server.Logger, apiClient const fs = apiClient.vscode.workspace.fileSystem // TODO: Remove all this logging when I'm confident it's working logger.info(`starting serverhost`) + let watchId = 0 return { /** * @param pollingInterval ignored in native filewatchers; only used in polling watchers @@ -52,24 +54,24 @@ function createServerHost(extensionUri: URI, logger: ts.server.Logger, apiClient watchFile(path: string, callback: ts.FileWatcherCallback, pollingInterval?: number, options?: ts.WatchOptions): ts.FileWatcher { logger.info(`calling watchFile on ${path} (${watchFiles.has(path) ? 'OLD' : 'new'})`) watchFiles.set(path, { path, callback, pollingInterval, options }) - const uri = toResource(path) - fsWatcher.postMessage({ type: 'watchFile', uri }) + watchId++ + fsWatcher.postMessage({ type: 'watchFile', uri: toResource(path), id: watchId }) return { close() { watchFiles.delete(path) - fsWatcher.postMessage({ type: "dispose", uri }) + fsWatcher.postMessage({ type: "dispose", id: watchId }) } } }, watchDirectory(path: string, callback: ts.DirectoryWatcherCallback, recursive?: boolean, options?: ts.WatchOptions): ts.FileWatcher { logger.info(`calling watchDirectory on ${path} (${watchDirectories.has(path) ? 'OLD' : 'new'})`) watchDirectories.set(path, { path, callback, recursive, options }) - const uri = toResource(path) - fsWatcher.postMessage({ type: 'watchDirectory', recursive, uri }) + watchId++ + fsWatcher.postMessage({ type: 'watchDirectory', recursive, uri: toResource(path), id: watchId }) return { close() { watchDirectories.delete(path) - fsWatcher.postMessage({ type: "dispose", uri }) + fsWatcher.postMessage({ type: "dispose", id: watchId }) } } }, @@ -94,9 +96,18 @@ function createServerHost(extensionUri: URI, logger: ts.server.Logger, apiClient this.clearTimeout(timeoutId) }, trace: logger.info, - // require?(initialPath: string, moduleName: string): ModuleImportResult {}, + // require?(initialPath: string, moduleName: string): ts.server.ModuleImportResult {}, // TODO: This definitely needs to be implemented - // importServicePlugin?(root: string, moduleName: string): Promise {}, + // Jake says that vscode has an implementation called serverCreateWebSystem + importPlugin(root, moduleName) { + return Promise.resolve({ + module: undefined, + error: { + stack: root, + message: moduleName, + }, + }) + }, args, newLine: '\n', useCaseSensitiveFileNames: true,