@@ -28,6 +28,7 @@ import {
28
28
Diagnostic ,
29
29
directorySeparator ,
30
30
DirectoryStructureHost ,
31
+ DirectoryWatcherCallback ,
31
32
DocumentPosition ,
32
33
DocumentPositionMapper ,
33
34
DocumentRegistry ,
@@ -38,6 +39,7 @@ import {
38
39
FileExtensionInfo ,
39
40
fileExtensionIs ,
40
41
FileWatcher ,
42
+ FileWatcherCallback ,
41
43
FileWatcherEventKind ,
42
44
find ,
43
45
flatMap ,
@@ -128,6 +130,7 @@ import {
128
130
version ,
129
131
WatchDirectoryFlags ,
130
132
WatchFactory ,
133
+ WatchFactoryHost ,
131
134
WatchLogLevel ,
132
135
WatchOptions ,
133
136
WatchType ,
@@ -194,6 +197,9 @@ export const ConfigFileDiagEvent = "configFileDiag";
194
197
export const ProjectLanguageServiceStateEvent = "projectLanguageServiceState" ;
195
198
export const ProjectInfoTelemetryEvent = "projectInfo" ;
196
199
export const OpenFileInfoTelemetryEvent = "openFileInfo" ;
200
+ export const CreateFileWatcherEvent : protocol . CreateFileWatcherEventName = "createFileWatcher" ;
201
+ export const CreateDirectoryWatcherEvent : protocol . CreateDirectoryWatcherEventName = "createDirectoryWatcher" ;
202
+ export const CloseFileWatcherEvent : protocol . CloseFileWatcherEventName = "closeFileWatcher" ;
197
203
const ensureProjectForOpenFileSchedule = "*ensureProjectForOpenFiles*" ;
198
204
199
205
export interface ProjectsUpdatedInBackgroundEvent {
@@ -321,6 +327,21 @@ export interface OpenFileInfo {
321
327
readonly checkJs : boolean ;
322
328
}
323
329
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
+
324
345
export type ProjectServiceEvent =
325
346
| LargeFileReferencedEvent
326
347
| ProjectsUpdatedInBackgroundEvent
@@ -329,7 +350,10 @@ export type ProjectServiceEvent =
329
350
| ConfigFileDiagEvent
330
351
| ProjectLanguageServiceStateEvent
331
352
| ProjectInfoTelemetryEvent
332
- | OpenFileInfoTelemetryEvent ;
353
+ | OpenFileInfoTelemetryEvent
354
+ | CreateFileWatcherEvent
355
+ | CreateDirectoryWatcherEvent
356
+ | CloseFileWatcherEvent ;
333
357
334
358
export type ProjectServiceEventHandler = ( event : ProjectServiceEvent ) => void ;
335
359
@@ -584,6 +608,7 @@ export interface ProjectServiceOptions {
584
608
useInferredProjectPerProjectRoot : boolean ;
585
609
typingsInstaller ?: ITypingsInstaller ;
586
610
eventHandler ?: ProjectServiceEventHandler ;
611
+ canUseWatchEvents ?: boolean ;
587
612
suppressDiagnosticEvents ?: boolean ;
588
613
throttleWaitMilliseconds ?: number ;
589
614
globalPlugins ?: readonly string [ ] ;
@@ -858,6 +883,109 @@ function createProjectNameFactoryWithCounter(nameFactory: (counter: number) => s
858
883
return ( ) => nameFactory ( nextId ++ ) ;
859
884
}
860
885
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
+
861
989
export class ProjectService {
862
990
/** @internal */
863
991
readonly typingsCache : TypingsCache ;
@@ -962,7 +1090,8 @@ export class ProjectService {
962
1090
public readonly typingsInstaller : ITypingsInstaller ;
963
1091
private readonly globalCacheLocationDirectoryPath : Path | undefined ;
964
1092
public readonly throttleWaitMilliseconds ?: number ;
965
- private readonly eventHandler ?: ProjectServiceEventHandler ;
1093
+ /** @internal */
1094
+ readonly eventHandler ?: ProjectServiceEventHandler ;
966
1095
private readonly suppressDiagnosticEvents ?: boolean ;
967
1096
968
1097
public readonly globalPlugins : readonly string [ ] ;
@@ -1068,7 +1197,12 @@ export class ProjectService {
1068
1197
watchFile : returnNoopFileWatcher ,
1069
1198
watchDirectory : returnNoopFileWatcher ,
1070
1199
} :
1071
- getWatchFactory ( this . host , watchLogLevel , log , getDetailWatchInfo ) ;
1200
+ getWatchFactory (
1201
+ createWatchFactoryHostUsingWatchEvents ( this , opts . canUseWatchEvents ) || this . host ,
1202
+ watchLogLevel ,
1203
+ log ,
1204
+ getDetailWatchInfo ,
1205
+ ) ;
1072
1206
1073
1207
this . pnpWatcher = this . watchPnpFile ( ) ;
1074
1208
opts . incrementalVerifier ?.( this ) ;
0 commit comments