@@ -27,6 +27,7 @@ import {
27
27
Diagnostic ,
28
28
directorySeparator ,
29
29
DirectoryStructureHost ,
30
+ DirectoryWatcherCallback ,
30
31
DocumentPosition ,
31
32
DocumentPositionMapper ,
32
33
DocumentRegistry ,
@@ -37,6 +38,7 @@ import {
37
38
FileExtensionInfo ,
38
39
fileExtensionIs ,
39
40
FileWatcher ,
41
+ FileWatcherCallback ,
40
42
FileWatcherEventKind ,
41
43
find ,
42
44
flatMap ,
@@ -126,6 +128,7 @@ import {
126
128
version ,
127
129
WatchDirectoryFlags ,
128
130
WatchFactory ,
131
+ WatchFactoryHost ,
129
132
WatchLogLevel ,
130
133
WatchOptions ,
131
134
WatchType ,
@@ -192,6 +195,9 @@ export const ConfigFileDiagEvent = "configFileDiag";
192
195
export const ProjectLanguageServiceStateEvent = "projectLanguageServiceState" ;
193
196
export const ProjectInfoTelemetryEvent = "projectInfo" ;
194
197
export const OpenFileInfoTelemetryEvent = "openFileInfo" ;
198
+ export const CreateFileWatcherEvent : protocol . CreateFileWatcherEventName = "createFileWatcher" ;
199
+ export const CreateDirectoryWatcherEvent : protocol . CreateDirectoryWatcherEventName = "createDirectoryWatcher" ;
200
+ export const CloseFileWatcherEvent : protocol . CloseFileWatcherEventName = "closeFileWatcher" ;
195
201
const ensureProjectForOpenFileSchedule = "*ensureProjectForOpenFiles*" ;
196
202
197
203
export interface ProjectsUpdatedInBackgroundEvent {
@@ -319,6 +325,21 @@ export interface OpenFileInfo {
319
325
readonly checkJs : boolean ;
320
326
}
321
327
328
+ export interface CreateFileWatcherEvent {
329
+ readonly eventName : protocol . CreateFileWatcherEventName ;
330
+ readonly data : protocol . CreateFileWatcherEventBody ;
331
+ }
332
+
333
+ export interface CreateDirectoryWatcherEvent {
334
+ readonly eventName : protocol . CreateDirectoryWatcherEventName ;
335
+ readonly data : protocol . CreateDirectoryWatcherEventBody ;
336
+ }
337
+
338
+ export interface CloseFileWatcherEvent {
339
+ readonly eventName : protocol . CloseFileWatcherEventName ;
340
+ readonly data : protocol . CloseFileWatcherEventBody ;
341
+ }
342
+
322
343
export type ProjectServiceEvent =
323
344
LargeFileReferencedEvent
324
345
| ProjectsUpdatedInBackgroundEvent
@@ -327,7 +348,10 @@ export type ProjectServiceEvent =
327
348
| ConfigFileDiagEvent
328
349
| ProjectLanguageServiceStateEvent
329
350
| ProjectInfoTelemetryEvent
330
- | OpenFileInfoTelemetryEvent ;
351
+ | OpenFileInfoTelemetryEvent
352
+ | CreateFileWatcherEvent
353
+ | CreateDirectoryWatcherEvent
354
+ | CloseFileWatcherEvent ;
331
355
332
356
export type ProjectServiceEventHandler = ( event : ProjectServiceEvent ) => void ;
333
357
@@ -582,6 +606,7 @@ export interface ProjectServiceOptions {
582
606
useInferredProjectPerProjectRoot : boolean ;
583
607
typingsInstaller ?: ITypingsInstaller ;
584
608
eventHandler ?: ProjectServiceEventHandler ;
609
+ canUseWatchEvents ?: boolean ;
585
610
suppressDiagnosticEvents ?: boolean ;
586
611
throttleWaitMilliseconds ?: number ;
587
612
globalPlugins ?: readonly string [ ] ;
@@ -852,6 +877,109 @@ function createProjectNameFactoryWithCounter(nameFactory: (counter: number) => s
852
877
return ( ) => nameFactory ( nextId ++ ) ;
853
878
}
854
879
880
+ interface HostWatcherMap < T > {
881
+ idToCallbacks : Map < number , Set < T > > ;
882
+ pathToId : Map < Path , number > ;
883
+ }
884
+
885
+ function getHostWatcherMap < T > ( ) : HostWatcherMap < T > {
886
+ return { idToCallbacks : new Map ( ) , pathToId : new Map ( ) } ;
887
+ }
888
+
889
+ function createWatchFactoryHostUsingWatchEvents ( service : ProjectService , canUseWatchEvents : boolean | undefined ) : WatchFactoryHost | undefined {
890
+ if ( ! canUseWatchEvents || ! service . eventHandler || ! service . session ) return undefined ;
891
+ const watchedFiles = getHostWatcherMap < FileWatcherCallback > ( ) ;
892
+ const watchedDirectories = getHostWatcherMap < DirectoryWatcherCallback > ( ) ;
893
+ const watchedDirectoriesRecursive = getHostWatcherMap < DirectoryWatcherCallback > ( ) ;
894
+ let ids = 1 ;
895
+ service . session . addProtocolHandler ( protocol . CommandTypes . WatchChange , req => {
896
+ onWatchChange ( ( req as protocol . WatchChangeRequest ) . arguments ) ;
897
+ return { responseRequired : false } ;
898
+ } ) ;
899
+ return {
900
+ watchFile,
901
+ watchDirectory,
902
+ getCurrentDirectory : ( ) => service . host . getCurrentDirectory ( ) ,
903
+ useCaseSensitiveFileNames : service . host . useCaseSensitiveFileNames ,
904
+ } ;
905
+ function watchFile ( path : string , callback : FileWatcherCallback ) : FileWatcher {
906
+ return getOrCreateFileWatcher (
907
+ watchedFiles ,
908
+ path ,
909
+ callback ,
910
+ id => ( { eventName : CreateFileWatcherEvent , data : { id, path } } )
911
+ ) ;
912
+ }
913
+ function watchDirectory ( path : string , callback : DirectoryWatcherCallback , recursive ?: boolean ) : FileWatcher {
914
+ return getOrCreateFileWatcher (
915
+ recursive ? watchedDirectoriesRecursive : watchedDirectories ,
916
+ path ,
917
+ callback ,
918
+ id => ( { eventName : CreateDirectoryWatcherEvent , data : { id, path, recursive : ! ! recursive } } )
919
+ ) ;
920
+ }
921
+ function getOrCreateFileWatcher < T > (
922
+ { pathToId, idToCallbacks } : HostWatcherMap < T > ,
923
+ path : string ,
924
+ callback : T ,
925
+ event : ( id : number ) => CreateFileWatcherEvent | CreateDirectoryWatcherEvent ,
926
+ ) {
927
+ const key = service . toPath ( path ) ;
928
+ let id = pathToId . get ( key ) ;
929
+ if ( ! id ) pathToId . set ( key , id = ids ++ ) ;
930
+ let callbacks = idToCallbacks . get ( id ) ;
931
+ if ( ! callbacks ) {
932
+ idToCallbacks . set ( id , callbacks = new Set ( ) ) ;
933
+ // Add watcher
934
+ service . eventHandler ! ( event ( id ) ) ;
935
+ }
936
+ callbacks . add ( callback ) ;
937
+ return {
938
+ close ( ) {
939
+ const callbacks = idToCallbacks . get ( id ! ) ;
940
+ if ( ! callbacks ?. delete ( callback ) ) return ;
941
+ if ( callbacks . size ) return ;
942
+ idToCallbacks . delete ( id ! ) ;
943
+ pathToId . delete ( key ) ;
944
+ service . eventHandler ! ( { eventName : CloseFileWatcherEvent , data : { id : id ! } } ) ;
945
+ }
946
+ } ;
947
+ }
948
+ function onWatchChange ( { id, path, eventType } : protocol . WatchChangeRequestArgs ) {
949
+ // console.log(`typescript-vscode-watcher:: Invoke:: ${id}:: ${path}:: ${eventType}`);
950
+ onFileWatcherCallback ( id , path , eventType ) ;
951
+ onDirectoryWatcherCallback ( watchedDirectories , id , path , eventType ) ;
952
+ onDirectoryWatcherCallback ( watchedDirectoriesRecursive , id , path , eventType ) ;
953
+ }
954
+
955
+ function onFileWatcherCallback (
956
+ id : number ,
957
+ eventPath : string ,
958
+ eventType : "create" | "delete" | "update" ,
959
+ ) {
960
+ watchedFiles . idToCallbacks . get ( id ) ?. forEach ( callback => {
961
+ const eventKind = eventType === "create" ?
962
+ FileWatcherEventKind . Created :
963
+ eventType === "delete" ?
964
+ FileWatcherEventKind . Deleted :
965
+ FileWatcherEventKind . Changed ;
966
+ callback ( eventPath , eventKind ) ;
967
+ } ) ;
968
+ }
969
+
970
+ function onDirectoryWatcherCallback (
971
+ { idToCallbacks } : HostWatcherMap < DirectoryWatcherCallback > ,
972
+ id : number ,
973
+ eventPath : string ,
974
+ eventType : "create" | "delete" | "update" ,
975
+ ) {
976
+ if ( eventType === "update" ) return ;
977
+ idToCallbacks . get ( id ) ?. forEach ( callback => {
978
+ callback ( eventPath ) ;
979
+ } ) ;
980
+ }
981
+ }
982
+
855
983
export class ProjectService {
856
984
857
985
/** @internal */
@@ -958,7 +1086,8 @@ export class ProjectService {
958
1086
public readonly typingsInstaller : ITypingsInstaller ;
959
1087
private readonly globalCacheLocationDirectoryPath : Path | undefined ;
960
1088
public readonly throttleWaitMilliseconds ?: number ;
961
- private readonly eventHandler ?: ProjectServiceEventHandler ;
1089
+ /** @internal */
1090
+ readonly eventHandler ?: ProjectServiceEventHandler ;
962
1091
private readonly suppressDiagnosticEvents ?: boolean ;
963
1092
964
1093
public readonly globalPlugins : readonly string [ ] ;
@@ -1058,7 +1187,12 @@ export class ProjectService {
1058
1187
watchFile : returnNoopFileWatcher ,
1059
1188
watchDirectory : returnNoopFileWatcher ,
1060
1189
} :
1061
- getWatchFactory ( this . host , watchLogLevel , log , getDetailWatchInfo ) ;
1190
+ getWatchFactory (
1191
+ createWatchFactoryHostUsingWatchEvents ( this , opts . canUseWatchEvents ) || this . host ,
1192
+ watchLogLevel ,
1193
+ log ,
1194
+ getDetailWatchInfo ,
1195
+ ) ;
1062
1196
opts . incrementalVerifier ?.( this ) ;
1063
1197
}
1064
1198
0 commit comments