@@ -28,6 +28,7 @@ import {
2828 Diagnostic ,
2929 directorySeparator ,
3030 DirectoryStructureHost ,
31+ DirectoryWatcherCallback ,
3132 DocumentPosition ,
3233 DocumentPositionMapper ,
3334 DocumentRegistry ,
@@ -38,6 +39,7 @@ import {
3839 FileExtensionInfo ,
3940 fileExtensionIs ,
4041 FileWatcher ,
42+ FileWatcherCallback ,
4143 FileWatcherEventKind ,
4244 find ,
4345 flatMap ,
@@ -128,6 +130,7 @@ import {
128130 version ,
129131 WatchDirectoryFlags ,
130132 WatchFactory ,
133+ WatchFactoryHost ,
131134 WatchLogLevel ,
132135 WatchOptions ,
133136 WatchType ,
@@ -194,6 +197,9 @@ export const ConfigFileDiagEvent = "configFileDiag";
194197export const ProjectLanguageServiceStateEvent = "projectLanguageServiceState" ;
195198export const ProjectInfoTelemetryEvent = "projectInfo" ;
196199export const OpenFileInfoTelemetryEvent = "openFileInfo" ;
200+ export const CreateFileWatcherEvent : protocol . CreateFileWatcherEventName = "createFileWatcher" ;
201+ export const CreateDirectoryWatcherEvent : protocol . CreateDirectoryWatcherEventName = "createDirectoryWatcher" ;
202+ export const CloseFileWatcherEvent : protocol . CloseFileWatcherEventName = "closeFileWatcher" ;
197203const ensureProjectForOpenFileSchedule = "*ensureProjectForOpenFiles*" ;
198204
199205export interface ProjectsUpdatedInBackgroundEvent {
@@ -321,6 +327,21 @@ export interface OpenFileInfo {
321327 readonly checkJs : boolean ;
322328}
323329
330+ export interface CreateFileWatcherEvent {
331+ readonly eventName : protocol . CreateFileWatcherEventName ;
332+ readonly data : protocol . CreateFileWatcherEventBody ;
333+ }
334+
335+ export interface CreateDirectoryWatcherEvent {
336+ readonly eventName : protocol . CreateDirectoryWatcherEventName ;
337+ readonly data : protocol . CreateDirectoryWatcherEventBody ;
338+ }
339+
340+ export interface CloseFileWatcherEvent {
341+ readonly eventName : protocol . CloseFileWatcherEventName ;
342+ readonly data : protocol . CloseFileWatcherEventBody ;
343+ }
344+
324345export type ProjectServiceEvent =
325346 | LargeFileReferencedEvent
326347 | ProjectsUpdatedInBackgroundEvent
@@ -329,7 +350,10 @@ export type ProjectServiceEvent =
329350 | ConfigFileDiagEvent
330351 | ProjectLanguageServiceStateEvent
331352 | ProjectInfoTelemetryEvent
332- | OpenFileInfoTelemetryEvent ;
353+ | OpenFileInfoTelemetryEvent
354+ | CreateFileWatcherEvent
355+ | CreateDirectoryWatcherEvent
356+ | CloseFileWatcherEvent ;
333357
334358export type ProjectServiceEventHandler = ( event : ProjectServiceEvent ) => void ;
335359
@@ -584,6 +608,7 @@ export interface ProjectServiceOptions {
584608 useInferredProjectPerProjectRoot : boolean ;
585609 typingsInstaller ?: ITypingsInstaller ;
586610 eventHandler ?: ProjectServiceEventHandler ;
611+ canUseWatchEvents ?: boolean ;
587612 suppressDiagnosticEvents ?: boolean ;
588613 throttleWaitMilliseconds ?: number ;
589614 globalPlugins ?: readonly string [ ] ;
@@ -858,6 +883,109 @@ function createProjectNameFactoryWithCounter(nameFactory: (counter: number) => s
858883 return ( ) => nameFactory ( nextId ++ ) ;
859884}
860885
886+ interface HostWatcherMap < T > {
887+ idToCallbacks : Map < number , Set < T > > ;
888+ pathToId : Map < Path , number > ;
889+ }
890+
891+ function getHostWatcherMap < T > ( ) : HostWatcherMap < T > {
892+ return { idToCallbacks : new Map ( ) , pathToId : new Map ( ) } ;
893+ }
894+
895+ function createWatchFactoryHostUsingWatchEvents ( service : ProjectService , canUseWatchEvents : boolean | undefined ) : WatchFactoryHost | undefined {
896+ if ( ! canUseWatchEvents || ! service . eventHandler || ! service . session ) return undefined ;
897+ const watchedFiles = getHostWatcherMap < FileWatcherCallback > ( ) ;
898+ const watchedDirectories = getHostWatcherMap < DirectoryWatcherCallback > ( ) ;
899+ const watchedDirectoriesRecursive = getHostWatcherMap < DirectoryWatcherCallback > ( ) ;
900+ let ids = 1 ;
901+ service . session . addProtocolHandler ( protocol . CommandTypes . WatchChange , req => {
902+ onWatchChange ( ( req as protocol . WatchChangeRequest ) . arguments ) ;
903+ return { responseRequired : false } ;
904+ } ) ;
905+ return {
906+ watchFile,
907+ watchDirectory,
908+ getCurrentDirectory : ( ) => service . host . getCurrentDirectory ( ) ,
909+ useCaseSensitiveFileNames : service . host . useCaseSensitiveFileNames ,
910+ } ;
911+ function watchFile ( path : string , callback : FileWatcherCallback ) : FileWatcher {
912+ return getOrCreateFileWatcher (
913+ watchedFiles ,
914+ path ,
915+ callback ,
916+ id => ( { eventName : CreateFileWatcherEvent , data : { id, path } } ) ,
917+ ) ;
918+ }
919+ function watchDirectory ( path : string , callback : DirectoryWatcherCallback , recursive ?: boolean ) : FileWatcher {
920+ return getOrCreateFileWatcher (
921+ recursive ? watchedDirectoriesRecursive : watchedDirectories ,
922+ path ,
923+ callback ,
924+ id => ( { eventName : CreateDirectoryWatcherEvent , data : { id, path, recursive : ! ! recursive } } ) ,
925+ ) ;
926+ }
927+ function getOrCreateFileWatcher < T > (
928+ { pathToId, idToCallbacks } : HostWatcherMap < T > ,
929+ path : string ,
930+ callback : T ,
931+ event : ( id : number ) => CreateFileWatcherEvent | CreateDirectoryWatcherEvent ,
932+ ) {
933+ const key = service . toPath ( path ) ;
934+ let id = pathToId . get ( key ) ;
935+ if ( ! id ) pathToId . set ( key , id = ids ++ ) ;
936+ let callbacks = idToCallbacks . get ( id ) ;
937+ if ( ! callbacks ) {
938+ idToCallbacks . set ( id , callbacks = new Set ( ) ) ;
939+ // Add watcher
940+ service . eventHandler ! ( event ( id ) ) ;
941+ }
942+ callbacks . add ( callback ) ;
943+ return {
944+ close ( ) {
945+ const callbacks = idToCallbacks . get ( id ! ) ;
946+ if ( ! callbacks ?. delete ( callback ) ) return ;
947+ if ( callbacks . size ) return ;
948+ idToCallbacks . delete ( id ! ) ;
949+ pathToId . delete ( key ) ;
950+ service . eventHandler ! ( { eventName : CloseFileWatcherEvent , data : { id : id ! } } ) ;
951+ } ,
952+ } ;
953+ }
954+ function onWatchChange ( { id, path, eventType } : protocol . WatchChangeRequestArgs ) {
955+ // console.log(`typescript-vscode-watcher:: Invoke:: ${id}:: ${path}:: ${eventType}`);
956+ onFileWatcherCallback ( id , path , eventType ) ;
957+ onDirectoryWatcherCallback ( watchedDirectories , id , path , eventType ) ;
958+ onDirectoryWatcherCallback ( watchedDirectoriesRecursive , id , path , eventType ) ;
959+ }
960+
961+ function onFileWatcherCallback (
962+ id : number ,
963+ eventPath : string ,
964+ eventType : "create" | "delete" | "update" ,
965+ ) {
966+ watchedFiles . idToCallbacks . get ( id ) ?. forEach ( callback => {
967+ const eventKind = eventType === "create" ?
968+ FileWatcherEventKind . Created :
969+ eventType === "delete" ?
970+ FileWatcherEventKind . Deleted :
971+ FileWatcherEventKind . Changed ;
972+ callback ( eventPath , eventKind ) ;
973+ } ) ;
974+ }
975+
976+ function onDirectoryWatcherCallback (
977+ { idToCallbacks } : HostWatcherMap < DirectoryWatcherCallback > ,
978+ id : number ,
979+ eventPath : string ,
980+ eventType : "create" | "delete" | "update" ,
981+ ) {
982+ if ( eventType === "update" ) return ;
983+ idToCallbacks . get ( id ) ?. forEach ( callback => {
984+ callback ( eventPath ) ;
985+ } ) ;
986+ }
987+ }
988+
861989export class ProjectService {
862990 /** @internal */
863991 readonly typingsCache : TypingsCache ;
@@ -962,7 +1090,8 @@ export class ProjectService {
9621090 public readonly typingsInstaller : ITypingsInstaller ;
9631091 private readonly globalCacheLocationDirectoryPath : Path | undefined ;
9641092 public readonly throttleWaitMilliseconds ?: number ;
965- private readonly eventHandler ?: ProjectServiceEventHandler ;
1093+ /** @internal */
1094+ readonly eventHandler ?: ProjectServiceEventHandler ;
9661095 private readonly suppressDiagnosticEvents ?: boolean ;
9671096
9681097 public readonly globalPlugins : readonly string [ ] ;
@@ -1068,7 +1197,12 @@ export class ProjectService {
10681197 watchFile : returnNoopFileWatcher ,
10691198 watchDirectory : returnNoopFileWatcher ,
10701199 } :
1071- getWatchFactory ( this . host , watchLogLevel , log , getDetailWatchInfo ) ;
1200+ getWatchFactory (
1201+ createWatchFactoryHostUsingWatchEvents ( this , opts . canUseWatchEvents ) || this . host ,
1202+ watchLogLevel ,
1203+ log ,
1204+ getDetailWatchInfo ,
1205+ ) ;
10721206
10731207 this . pnpWatcher = this . watchPnpFile ( ) ;
10741208 opts . incrementalVerifier ?.( this ) ;
0 commit comments