Skip to content

Commit

Permalink
Switch to markdown extension's FileWatcherManager
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
sandersn committed Jan 6, 2023
1 parent a07f98c commit 0288f0f
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 30 deletions.
2 changes: 1 addition & 1 deletion extensions/markdown-language-features/src/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 });

Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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<vscode.Uri, vscode.FileSystemWatcher>();
private readonly watches = new FileWatcherManager();
/** For communicating with TS server synchronously */
private readonly tsserver: MessagePort;
/** For communicating watches asynchronously */
Expand Down Expand Up @@ -77,24 +82,16 @@ export class WorkerServerProcess implements TsServerProcess {
this.watcher.onmessage = (event: MessageEvent<BrowserWatchEvent>) => {
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:
Expand Down
5 changes: 4 additions & 1 deletion extensions/typescript-language-features/web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
29 changes: 20 additions & 9 deletions extensions/typescript-language-features/web/webServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ts.server.ModuleImportResult> };
function createServerHost(extensionUri: URI, logger: ts.server.Logger, apiClient: ApiClient, args: string[], fsWatcher: MessagePort): ServerHostWithImport {
/**
* Copied from toResource in typescriptServiceClient.ts
*/
Expand All @@ -45,31 +46,32 @@ 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
*/
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 })
}
}
},
Expand All @@ -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<ModuleImportResult> {},
// 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,
Expand Down

0 comments on commit 0288f0f

Please sign in to comment.