@@ -27,6 +27,7 @@ import {
2727 Diagnostic ,
2828 directorySeparator ,
2929 DirectoryStructureHost ,
30+ DirectoryWatcherCallback ,
3031 DocumentPosition ,
3132 DocumentPositionMapper ,
3233 DocumentRegistry ,
@@ -37,6 +38,7 @@ import {
3738 FileExtensionInfo ,
3839 fileExtensionIs ,
3940 FileWatcher ,
41+ FileWatcherCallback ,
4042 FileWatcherEventKind ,
4143 find ,
4244 flatMap ,
@@ -127,6 +129,7 @@ import {
127129 version ,
128130 WatchDirectoryFlags ,
129131 WatchFactory ,
132+ WatchFactoryHost ,
130133 WatchLogLevel ,
131134 WatchOptions ,
132135 WatchType ,
@@ -193,6 +196,9 @@ export const ConfigFileDiagEvent = "configFileDiag";
193196export const ProjectLanguageServiceStateEvent = "projectLanguageServiceState" ;
194197export const ProjectInfoTelemetryEvent = "projectInfo" ;
195198export const OpenFileInfoTelemetryEvent = "openFileInfo" ;
199+ export const CreateFileWatcherEvent : protocol . CreateFileWatcherEventName = "createFileWatcher" ;
200+ export const CreateDirectoryWatcherEvent : protocol . CreateDirectoryWatcherEventName = "createDirectoryWatcher" ;
201+ export const CloseFileWatcherEvent : protocol . CloseFileWatcherEventName = "closeFileWatcher" ;
196202const ensureProjectForOpenFileSchedule = "*ensureProjectForOpenFiles*" ;
197203
198204export interface ProjectsUpdatedInBackgroundEvent {
@@ -320,6 +326,21 @@ export interface OpenFileInfo {
320326 readonly checkJs : boolean ;
321327}
322328
329+ export interface CreateFileWatcherEvent {
330+ readonly eventName : protocol . CreateFileWatcherEventName ;
331+ readonly data : protocol . CreateFileWatcherEventBody ;
332+ }
333+
334+ export interface CreateDirectoryWatcherEvent {
335+ readonly eventName : protocol . CreateDirectoryWatcherEventName ;
336+ readonly data : protocol . CreateDirectoryWatcherEventBody ;
337+ }
338+
339+ export interface CloseFileWatcherEvent {
340+ readonly eventName : protocol . CloseFileWatcherEventName ;
341+ readonly data : protocol . CloseFileWatcherEventBody ;
342+ }
343+
323344export type ProjectServiceEvent =
324345 | LargeFileReferencedEvent
325346 | ProjectsUpdatedInBackgroundEvent
@@ -328,7 +349,10 @@ export type ProjectServiceEvent =
328349 | ConfigFileDiagEvent
329350 | ProjectLanguageServiceStateEvent
330351 | ProjectInfoTelemetryEvent
331- | OpenFileInfoTelemetryEvent ;
352+ | OpenFileInfoTelemetryEvent
353+ | CreateFileWatcherEvent
354+ | CreateDirectoryWatcherEvent
355+ | CloseFileWatcherEvent ;
332356
333357export type ProjectServiceEventHandler = ( event : ProjectServiceEvent ) => void ;
334358
@@ -583,6 +607,7 @@ export interface ProjectServiceOptions {
583607 useInferredProjectPerProjectRoot : boolean ;
584608 typingsInstaller ?: ITypingsInstaller ;
585609 eventHandler ?: ProjectServiceEventHandler ;
610+ canUseWatchEvents ?: boolean ;
586611 suppressDiagnosticEvents ?: boolean ;
587612 throttleWaitMilliseconds ?: number ;
588613 globalPlugins ?: readonly string [ ] ;
@@ -857,6 +882,109 @@ function createProjectNameFactoryWithCounter(nameFactory: (counter: number) => s
857882 return ( ) => nameFactory ( nextId ++ ) ;
858883}
859884
885+ interface HostWatcherMap < T > {
886+ idToCallbacks : Map < number , Set < T > > ;
887+ pathToId : Map < Path , number > ;
888+ }
889+
890+ function getHostWatcherMap < T > ( ) : HostWatcherMap < T > {
891+ return { idToCallbacks : new Map ( ) , pathToId : new Map ( ) } ;
892+ }
893+
894+ function createWatchFactoryHostUsingWatchEvents ( service : ProjectService , canUseWatchEvents : boolean | undefined ) : WatchFactoryHost | undefined {
895+ if ( ! canUseWatchEvents || ! service . eventHandler || ! service . session ) return undefined ;
896+ const watchedFiles = getHostWatcherMap < FileWatcherCallback > ( ) ;
897+ const watchedDirectories = getHostWatcherMap < DirectoryWatcherCallback > ( ) ;
898+ const watchedDirectoriesRecursive = getHostWatcherMap < DirectoryWatcherCallback > ( ) ;
899+ let ids = 1 ;
900+ service . session . addProtocolHandler ( protocol . CommandTypes . WatchChange , req => {
901+ onWatchChange ( ( req as protocol . WatchChangeRequest ) . arguments ) ;
902+ return { responseRequired : false } ;
903+ } ) ;
904+ return {
905+ watchFile,
906+ watchDirectory,
907+ getCurrentDirectory : ( ) => service . host . getCurrentDirectory ( ) ,
908+ useCaseSensitiveFileNames : service . host . useCaseSensitiveFileNames ,
909+ } ;
910+ function watchFile ( path : string , callback : FileWatcherCallback ) : FileWatcher {
911+ return getOrCreateFileWatcher (
912+ watchedFiles ,
913+ path ,
914+ callback ,
915+ id => ( { eventName : CreateFileWatcherEvent , data : { id, path } } ) ,
916+ ) ;
917+ }
918+ function watchDirectory ( path : string , callback : DirectoryWatcherCallback , recursive ?: boolean ) : FileWatcher {
919+ return getOrCreateFileWatcher (
920+ recursive ? watchedDirectoriesRecursive : watchedDirectories ,
921+ path ,
922+ callback ,
923+ id => ( { eventName : CreateDirectoryWatcherEvent , data : { id, path, recursive : ! ! recursive } } ) ,
924+ ) ;
925+ }
926+ function getOrCreateFileWatcher < T > (
927+ { pathToId, idToCallbacks } : HostWatcherMap < T > ,
928+ path : string ,
929+ callback : T ,
930+ event : ( id : number ) => CreateFileWatcherEvent | CreateDirectoryWatcherEvent ,
931+ ) {
932+ const key = service . toPath ( path ) ;
933+ let id = pathToId . get ( key ) ;
934+ if ( ! id ) pathToId . set ( key , id = ids ++ ) ;
935+ let callbacks = idToCallbacks . get ( id ) ;
936+ if ( ! callbacks ) {
937+ idToCallbacks . set ( id , callbacks = new Set ( ) ) ;
938+ // Add watcher
939+ service . eventHandler ! ( event ( id ) ) ;
940+ }
941+ callbacks . add ( callback ) ;
942+ return {
943+ close ( ) {
944+ const callbacks = idToCallbacks . get ( id ! ) ;
945+ if ( ! callbacks ?. delete ( callback ) ) return ;
946+ if ( callbacks . size ) return ;
947+ idToCallbacks . delete ( id ! ) ;
948+ pathToId . delete ( key ) ;
949+ service . eventHandler ! ( { eventName : CloseFileWatcherEvent , data : { id : id ! } } ) ;
950+ } ,
951+ } ;
952+ }
953+ function onWatchChange ( { id, path, eventType } : protocol . WatchChangeRequestArgs ) {
954+ // console.log(`typescript-vscode-watcher:: Invoke:: ${id}:: ${path}:: ${eventType}`);
955+ onFileWatcherCallback ( id , path , eventType ) ;
956+ onDirectoryWatcherCallback ( watchedDirectories , id , path , eventType ) ;
957+ onDirectoryWatcherCallback ( watchedDirectoriesRecursive , id , path , eventType ) ;
958+ }
959+
960+ function onFileWatcherCallback (
961+ id : number ,
962+ eventPath : string ,
963+ eventType : "create" | "delete" | "update" ,
964+ ) {
965+ watchedFiles . idToCallbacks . get ( id ) ?. forEach ( callback => {
966+ const eventKind = eventType === "create" ?
967+ FileWatcherEventKind . Created :
968+ eventType === "delete" ?
969+ FileWatcherEventKind . Deleted :
970+ FileWatcherEventKind . Changed ;
971+ callback ( eventPath , eventKind ) ;
972+ } ) ;
973+ }
974+
975+ function onDirectoryWatcherCallback (
976+ { idToCallbacks } : HostWatcherMap < DirectoryWatcherCallback > ,
977+ id : number ,
978+ eventPath : string ,
979+ eventType : "create" | "delete" | "update" ,
980+ ) {
981+ if ( eventType === "update" ) return ;
982+ idToCallbacks . get ( id ) ?. forEach ( callback => {
983+ callback ( eventPath ) ;
984+ } ) ;
985+ }
986+ }
987+
860988export class ProjectService {
861989 /** @internal */
862990 readonly typingsCache : TypingsCache ;
@@ -961,7 +1089,8 @@ export class ProjectService {
9611089 public readonly typingsInstaller : ITypingsInstaller ;
9621090 private readonly globalCacheLocationDirectoryPath : Path | undefined ;
9631091 public readonly throttleWaitMilliseconds ?: number ;
964- private readonly eventHandler ?: ProjectServiceEventHandler ;
1092+ /** @internal */
1093+ readonly eventHandler ?: ProjectServiceEventHandler ;
9651094 private readonly suppressDiagnosticEvents ?: boolean ;
9661095
9671096 public readonly globalPlugins : readonly string [ ] ;
@@ -1065,7 +1194,12 @@ export class ProjectService {
10651194 watchFile : returnNoopFileWatcher ,
10661195 watchDirectory : returnNoopFileWatcher ,
10671196 } :
1068- getWatchFactory ( this . host , watchLogLevel , log , getDetailWatchInfo ) ;
1197+ getWatchFactory (
1198+ createWatchFactoryHostUsingWatchEvents ( this , opts . canUseWatchEvents ) || this . host ,
1199+ watchLogLevel ,
1200+ log ,
1201+ getDetailWatchInfo ,
1202+ ) ;
10691203 opts . incrementalVerifier ?.( this ) ;
10701204 }
10711205
0 commit comments