@@ -21,7 +21,7 @@ import { TypeScriptVersionManager } from './tsServer/versionManager';
21
21
import { ITypeScriptVersionProvider , TypeScriptVersion } from './tsServer/versionProvider' ;
22
22
import { ClientCapabilities , ClientCapability , ExecConfig , ITypeScriptServiceClient , ServerResponse , TypeScriptRequests } from './typescriptService' ;
23
23
import { ServiceConfigurationProvider , SyntaxServerConfiguration , TsServerLogLevel , TypeScriptServiceConfiguration , areServiceConfigurationsEqual } from './configuration/configuration' ;
24
- import { Disposable } from './utils/dispose' ;
24
+ import { Disposable , DisposableStore , disposeAll } from './utils/dispose' ;
25
25
import * as fileSchemes from './configuration/fileSchemes' ;
26
26
import { Logger } from './logging/logger' ;
27
27
import { isWeb , isWebAndHasSharedArrayBuffers } from './utils/platform' ;
@@ -97,6 +97,12 @@ export const emptyAuthority = 'ts-nul-authority';
97
97
98
98
export const inMemoryResourcePrefix = '^' ;
99
99
100
+ interface WatchEvent {
101
+ updated ?: Set < string > ;
102
+ created ?: Set < string > ;
103
+ deleted ?: Set < string > ;
104
+ }
105
+
100
106
export default class TypeScriptServiceClient extends Disposable implements ITypeScriptServiceClient {
101
107
102
108
@@ -128,6 +134,10 @@ export default class TypeScriptServiceClient extends Disposable implements IType
128
134
private readonly versionProvider : ITypeScriptVersionProvider ;
129
135
private readonly processFactory : TsServerProcessFactory ;
130
136
137
+ private readonly watches = new Map < number , Disposable > ( ) ;
138
+ private readonly watchEvents = new Map < number , WatchEvent > ( ) ;
139
+ private watchChangeTimeout : NodeJS . Timeout | undefined ;
140
+
131
141
constructor (
132
142
private readonly context : vscode . ExtensionContext ,
133
143
onCaseInsenitiveFileSystem : boolean ,
@@ -298,6 +308,8 @@ export default class TypeScriptServiceClient extends Disposable implements IType
298
308
}
299
309
300
310
this . loadingIndicator . reset ( ) ;
311
+
312
+ this . resetWatchers ( ) ;
301
313
}
302
314
303
315
public restartTsServer ( fromUserAction = false ) : void {
@@ -401,6 +413,8 @@ export default class TypeScriptServiceClient extends Disposable implements IType
401
413
this . info ( `Using Node installation from ${ nodePath } to run TS Server` ) ;
402
414
}
403
415
416
+ this . resetWatchers ( ) ;
417
+
404
418
const apiVersion = version . apiVersion || API . defaultVersion ;
405
419
const mytoken = ++ this . token ;
406
420
const handle = this . typescriptServerSpawner . spawn ( version , this . capabilities , this . configuration , this . pluginManager , this . cancellerFactory , {
@@ -493,6 +507,11 @@ export default class TypeScriptServiceClient extends Disposable implements IType
493
507
return this . serverState ;
494
508
}
495
509
510
+ private resetWatchers ( ) {
511
+ clearTimeout ( this . watchChangeTimeout ) ;
512
+ disposeAll ( Array . from ( this . watches . values ( ) ) ) ;
513
+ }
514
+
496
515
public async showVersionPicker ( ) : Promise < void > {
497
516
this . _versionManager . promptUserForVersion ( ) ;
498
517
}
@@ -594,6 +613,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType
594
613
}
595
614
596
615
private serviceExited ( restart : boolean ) : void {
616
+ this . resetWatchers ( ) ;
597
617
this . loadingIndicator . reset ( ) ;
598
618
599
619
const previousState = this . serverState ;
@@ -973,6 +993,120 @@ export default class TypeScriptServiceClient extends Disposable implements IType
973
993
case EventName . projectLoadingFinish :
974
994
this . loadingIndicator . finishedLoadingProject ( ( event as Proto . ProjectLoadingFinishEvent ) . body . projectName ) ;
975
995
break ;
996
+
997
+ case EventName . createDirectoryWatcher :
998
+ this . createFileSystemWatcher (
999
+ ( event . body as Proto . CreateDirectoryWatcherEventBody ) . id ,
1000
+ new vscode . RelativePattern (
1001
+ vscode . Uri . file ( ( event . body as Proto . CreateDirectoryWatcherEventBody ) . path ) ,
1002
+ ( event . body as Proto . CreateDirectoryWatcherEventBody ) . recursive ? '**' : '*'
1003
+ ) ,
1004
+ ( event . body as Proto . CreateDirectoryWatcherEventBody ) . ignoreUpdate
1005
+ ) ;
1006
+ break ;
1007
+
1008
+ case EventName . createFileWatcher :
1009
+ this . createFileSystemWatcher (
1010
+ ( event . body as Proto . CreateFileWatcherEventBody ) . id ,
1011
+ new vscode . RelativePattern (
1012
+ vscode . Uri . file ( ( event . body as Proto . CreateFileWatcherEventBody ) . path ) ,
1013
+ '*'
1014
+ )
1015
+ ) ;
1016
+ break ;
1017
+
1018
+ case EventName . closeFileWatcher :
1019
+ this . closeFileSystemWatcher ( event . body . id ) ;
1020
+ break ;
1021
+ }
1022
+ }
1023
+
1024
+ private scheduleExecuteWatchChangeRequest ( ) {
1025
+ if ( ! this . watchChangeTimeout ) {
1026
+ this . watchChangeTimeout = setTimeout ( ( ) => {
1027
+ this . watchChangeTimeout = undefined ;
1028
+ const allEvents = Array . from ( this . watchEvents , ( [ id , event ] ) => ( {
1029
+ id,
1030
+ updated : event . updated && Array . from ( event . updated ) ,
1031
+ created : event . created && Array . from ( event . created ) ,
1032
+ deleted : event . deleted && Array . from ( event . deleted )
1033
+ } ) ) ;
1034
+ this . watchEvents . clear ( ) ;
1035
+ this . executeWithoutWaitingForResponse ( 'watchChange' , allEvents ) ;
1036
+ } , 100 ) ; /* aggregate events over 100ms to reduce client<->server IPC overhead */
1037
+ }
1038
+ }
1039
+
1040
+ private addWatchEvent ( id : number , eventType : keyof WatchEvent , path : string ) {
1041
+ let event = this . watchEvents . get ( id ) ;
1042
+ const removeEvent = ( typeOfEventToRemove : keyof WatchEvent ) => {
1043
+ if ( event ?. [ typeOfEventToRemove ] ?. delete ( path ) && event [ typeOfEventToRemove ] . size === 0 ) {
1044
+ event [ typeOfEventToRemove ] = undefined ;
1045
+ }
1046
+ } ;
1047
+ const aggregateEvent = ( ) => {
1048
+ if ( ! event ) {
1049
+ this . watchEvents . set ( id , event = { } ) ;
1050
+ }
1051
+ ( event [ eventType ] ??= new Set ( ) ) . add ( path ) ;
1052
+ } ;
1053
+ switch ( eventType ) {
1054
+ case 'created' :
1055
+ removeEvent ( 'deleted' ) ;
1056
+ removeEvent ( 'updated' ) ;
1057
+ aggregateEvent ( ) ;
1058
+ break ;
1059
+ case 'deleted' :
1060
+ removeEvent ( 'created' ) ;
1061
+ removeEvent ( 'updated' ) ;
1062
+ aggregateEvent ( ) ;
1063
+ break ;
1064
+ case 'updated' :
1065
+ if ( event ?. created ?. has ( path ) ) {
1066
+ return ;
1067
+ }
1068
+ removeEvent ( 'deleted' ) ;
1069
+ aggregateEvent ( ) ;
1070
+ break ;
1071
+ }
1072
+ this . scheduleExecuteWatchChangeRequest ( ) ;
1073
+ }
1074
+
1075
+ private createFileSystemWatcher (
1076
+ id : number ,
1077
+ pattern : vscode . RelativePattern ,
1078
+ ignoreChangeEvents ?: boolean ,
1079
+ ) {
1080
+ const disposable = new DisposableStore ( ) ;
1081
+ const watcher = disposable . add ( vscode . workspace . createFileSystemWatcher ( pattern , { excludes : [ ] /* TODO:: need to fill in excludes list */ , ignoreChangeEvents } ) ) ;
1082
+ disposable . add ( watcher . onDidChange ( changeFile =>
1083
+ this . addWatchEvent ( id , 'updated' , changeFile . fsPath )
1084
+ ) ) ;
1085
+ disposable . add ( watcher . onDidCreate ( createFile =>
1086
+ this . addWatchEvent ( id , 'created' , createFile . fsPath )
1087
+ ) ) ;
1088
+ disposable . add ( watcher . onDidDelete ( deletedFile =>
1089
+ this . addWatchEvent ( id , 'deleted' , deletedFile . fsPath )
1090
+ ) ) ;
1091
+ disposable . add ( {
1092
+ dispose : ( ) => {
1093
+ this . watchEvents . delete ( id ) ;
1094
+ this . watches . delete ( id ) ;
1095
+ }
1096
+ } ) ;
1097
+
1098
+ if ( this . watches . has ( id ) ) {
1099
+ this . closeFileSystemWatcher ( id ) ;
1100
+ }
1101
+ this . watches . set ( id , disposable ) ;
1102
+ }
1103
+
1104
+ private closeFileSystemWatcher (
1105
+ id : number ,
1106
+ ) {
1107
+ const existing = this . watches . get ( id ) ;
1108
+ if ( existing ) {
1109
+ existing . dispose ( ) ;
976
1110
}
977
1111
}
978
1112
0 commit comments