@@ -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 ,
@@ -127,6 +129,7 @@ import {
127
129
version ,
128
130
WatchDirectoryFlags ,
129
131
WatchFactory ,
132
+ WatchFactoryHost ,
130
133
WatchLogLevel ,
131
134
WatchOptions ,
132
135
WatchType ,
@@ -193,6 +196,9 @@ export const ConfigFileDiagEvent = "configFileDiag";
193
196
export const ProjectLanguageServiceStateEvent = "projectLanguageServiceState" ;
194
197
export const ProjectInfoTelemetryEvent = "projectInfo" ;
195
198
export const OpenFileInfoTelemetryEvent = "openFileInfo" ;
199
+ export const CreateFileWatcherEvent : protocol . CreateFileWatcherEventName = "createFileWatcher" ;
200
+ export const CreateDirectoryWatcherEvent : protocol . CreateDirectoryWatcherEventName = "createDirectoryWatcher" ;
201
+ export const CloseFileWatcherEvent : protocol . CloseFileWatcherEventName = "closeFileWatcher" ;
196
202
const ensureProjectForOpenFileSchedule = "*ensureProjectForOpenFiles*" ;
197
203
198
204
export interface ProjectsUpdatedInBackgroundEvent {
@@ -320,6 +326,21 @@ export interface OpenFileInfo {
320
326
readonly checkJs : boolean ;
321
327
}
322
328
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
+
323
344
export type ProjectServiceEvent =
324
345
| LargeFileReferencedEvent
325
346
| ProjectsUpdatedInBackgroundEvent
@@ -328,7 +349,10 @@ export type ProjectServiceEvent =
328
349
| ConfigFileDiagEvent
329
350
| ProjectLanguageServiceStateEvent
330
351
| ProjectInfoTelemetryEvent
331
- | OpenFileInfoTelemetryEvent ;
352
+ | OpenFileInfoTelemetryEvent
353
+ | CreateFileWatcherEvent
354
+ | CreateDirectoryWatcherEvent
355
+ | CloseFileWatcherEvent ;
332
356
333
357
export type ProjectServiceEventHandler = ( event : ProjectServiceEvent ) => void ;
334
358
@@ -583,6 +607,7 @@ export interface ProjectServiceOptions {
583
607
useInferredProjectPerProjectRoot : boolean ;
584
608
typingsInstaller ?: ITypingsInstaller ;
585
609
eventHandler ?: ProjectServiceEventHandler ;
610
+ canUseWatchEvents ?: boolean ;
586
611
suppressDiagnosticEvents ?: boolean ;
587
612
throttleWaitMilliseconds ?: number ;
588
613
globalPlugins ?: readonly string [ ] ;
@@ -857,6 +882,109 @@ function createProjectNameFactoryWithCounter(nameFactory: (counter: number) => s
857
882
return ( ) => nameFactory ( nextId ++ ) ;
858
883
}
859
884
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
+
860
988
export class ProjectService {
861
989
/** @internal */
862
990
readonly typingsCache : TypingsCache ;
@@ -961,7 +1089,8 @@ export class ProjectService {
961
1089
public readonly typingsInstaller : ITypingsInstaller ;
962
1090
private readonly globalCacheLocationDirectoryPath : Path | undefined ;
963
1091
public readonly throttleWaitMilliseconds ?: number ;
964
- private readonly eventHandler ?: ProjectServiceEventHandler ;
1092
+ /** @internal */
1093
+ readonly eventHandler ?: ProjectServiceEventHandler ;
965
1094
private readonly suppressDiagnosticEvents ?: boolean ;
966
1095
967
1096
public readonly globalPlugins : readonly string [ ] ;
@@ -1065,7 +1194,12 @@ export class ProjectService {
1065
1194
watchFile : returnNoopFileWatcher ,
1066
1195
watchDirectory : returnNoopFileWatcher ,
1067
1196
} :
1068
- getWatchFactory ( this . host , watchLogLevel , log , getDetailWatchInfo ) ;
1197
+ getWatchFactory (
1198
+ createWatchFactoryHostUsingWatchEvents ( this , opts . canUseWatchEvents ) || this . host ,
1199
+ watchLogLevel ,
1200
+ log ,
1201
+ getDetailWatchInfo ,
1202
+ ) ;
1069
1203
opts . incrementalVerifier ?.( this ) ;
1070
1204
}
1071
1205
0 commit comments