2
2
* This code's groundwork is taken from https://github.com/vuejs/vetur/tree/master/vti
3
3
*/
4
4
5
- import { watch } from 'chokidar' ;
5
+ import { watch , FSWatcher } from 'chokidar' ;
6
6
import * as fs from 'fs' ;
7
7
import { fdir } from 'fdir' ;
8
8
import * as path from 'path' ;
@@ -143,51 +143,121 @@ async function getDiagnostics(
143
143
}
144
144
}
145
145
146
+ const FILE_ENDING_REGEX = / \. ( s v e l t e | d \. t s | t s | j s | j s x | t s x | m j s | c j s | m t s | c t s ) $ / ;
147
+ const VITE_CONFIG_REGEX = / v i t e \. c o n f i g \. ( j s | t s ) \. t i m e s t a m p - / ;
148
+
146
149
class DiagnosticsWatcher {
147
150
private updateDiagnostics : any ;
151
+ private watcher : FSWatcher ;
152
+ private currentWatchedDirs = new Set < string > ( ) ;
153
+ private userIgnored : Array < ( path : string ) => boolean > ;
154
+ private pendingWatcherUpdate : any ;
148
155
149
156
constructor (
150
157
private workspaceUri : URI ,
151
158
private svelteCheck : SvelteCheck ,
152
159
private writer : Writer ,
153
160
filePathsToIgnore : string [ ] ,
154
- ignoreInitialAdd : boolean
161
+ private ignoreInitialAdd : boolean
155
162
) {
156
- const fileEnding = / \. ( s v e l t e | d \. t s | t s | j s | j s x | t s x | m j s | c j s | m t s | c t s ) $ / ;
157
- const viteConfigRegex = / v i t e \. c o n f i g \. ( j s | t s ) \. t i m e s t a m p - / ;
158
- const userIgnored = createIgnored ( filePathsToIgnore ) ;
159
- const offset = workspaceUri . fsPath . length + 1 ;
163
+ this . userIgnored = createIgnored ( filePathsToIgnore ) ;
160
164
161
- watch ( workspaceUri . fsPath , {
165
+ // Create watcher with initial paths
166
+ this . watcher = watch ( [ ] , {
162
167
ignored : ( path , stats ) => {
163
168
if (
164
169
path . includes ( 'node_modules' ) ||
165
170
path . includes ( '.git' ) ||
166
- ( stats ?. isFile ( ) && ( ! fileEnding . test ( path ) || viteConfigRegex . test ( path ) ) )
171
+ ( stats ?. isFile ( ) &&
172
+ ( ! FILE_ENDING_REGEX . test ( path ) || VITE_CONFIG_REGEX . test ( path ) ) )
167
173
) {
168
174
return true ;
169
175
}
170
176
171
- if ( userIgnored . length !== 0 ) {
172
- path = path . slice ( offset ) ;
173
- for ( const i of userIgnored ) {
174
- if ( i ( path ) ) {
177
+ if ( this . userIgnored . length !== 0 ) {
178
+ // Make path relative to workspace for user ignores
179
+ const workspaceRelative = path . startsWith ( this . workspaceUri . fsPath )
180
+ ? path . slice ( this . workspaceUri . fsPath . length + 1 )
181
+ : path ;
182
+ for ( const i of this . userIgnored ) {
183
+ if ( i ( workspaceRelative ) ) {
175
184
return true ;
176
185
}
177
186
}
178
187
}
179
188
180
189
return false ;
181
190
} ,
182
- ignoreInitial : ignoreInitialAdd
191
+ ignoreInitial : this . ignoreInitialAdd
183
192
} )
184
193
. on ( 'add' , ( path ) => this . updateDocument ( path , true ) )
185
194
. on ( 'unlink' , ( path ) => this . removeDocument ( path ) )
186
195
. on ( 'change' , ( path ) => this . updateDocument ( path , false ) ) ;
187
196
188
- if ( ignoreInitialAdd ) {
189
- this . scheduleDiagnostics ( ) ;
197
+ this . updateWatchedDirectories ( ) ;
198
+ if ( this . ignoreInitialAdd ) {
199
+ getDiagnostics ( this . workspaceUri , this . writer , this . svelteCheck ) ;
200
+ }
201
+ }
202
+
203
+ private isSubdir ( candidate : string , parent : string ) {
204
+ const c = path . resolve ( candidate ) ;
205
+ const p = path . resolve ( parent ) ;
206
+ return c === p || c . startsWith ( p + path . sep ) ;
207
+ }
208
+
209
+ private minimizeDirs ( dirs : string [ ] ) : string [ ] {
210
+ const sorted = [ ...new Set ( dirs . map ( ( d ) => path . resolve ( d ) ) ) ] . sort ( ) ;
211
+ const result : string [ ] = [ ] ;
212
+ for ( const dir of sorted ) {
213
+ if ( ! result . some ( ( p ) => this . isSubdir ( dir , p ) ) ) {
214
+ result . push ( dir ) ;
215
+ }
216
+ }
217
+ return result ;
218
+ }
219
+
220
+ addWatchDirectory ( dir : string ) {
221
+ if ( ! dir ) return ;
222
+ // Skip if already covered by an existing watched directory
223
+ for ( const existing of this . currentWatchedDirs ) {
224
+ if ( this . isSubdir ( dir , existing ) ) {
225
+ return ;
226
+ }
190
227
}
228
+ // If new dir is a parent of existing ones, unwatch children
229
+ const toRemove : string [ ] = [ ] ;
230
+ for ( const existing of this . currentWatchedDirs ) {
231
+ if ( this . isSubdir ( existing , dir ) ) {
232
+ toRemove . push ( existing ) ;
233
+ }
234
+ }
235
+ if ( toRemove . length ) {
236
+ this . watcher . unwatch ( toRemove ) ;
237
+ for ( const r of toRemove ) this . currentWatchedDirs . delete ( r ) ;
238
+ }
239
+ this . watcher . add ( dir ) ;
240
+ this . currentWatchedDirs . add ( dir ) ;
241
+ }
242
+
243
+ private async updateWatchedDirectories ( ) {
244
+ const watchDirs = await this . svelteCheck . getWatchDirectories ( ) ;
245
+ const desired = this . minimizeDirs (
246
+ ( watchDirs ?. map ( ( d ) => d . path ) || [ this . workspaceUri . fsPath ] ) . map ( ( p ) =>
247
+ path . resolve ( p )
248
+ )
249
+ ) ;
250
+
251
+ const current = new Set ( [ ...this . currentWatchedDirs ] . map ( ( p ) => path . resolve ( p ) ) ) ;
252
+ const desiredSet = new Set ( desired ) ;
253
+
254
+ const toAdd = desired . filter ( ( d ) => ! current . has ( d ) ) ;
255
+ const toRemove = [ ...current ] . filter ( ( d ) => ! desiredSet . has ( d ) ) ;
256
+
257
+ if ( toAdd . length ) this . watcher . add ( toAdd ) ;
258
+ if ( toRemove . length ) this . watcher . unwatch ( toRemove ) ;
259
+
260
+ this . currentWatchedDirs = new Set ( desired ) ;
191
261
}
192
262
193
263
private async updateDocument ( path : string , isNew : boolean ) {
@@ -210,6 +280,11 @@ class DiagnosticsWatcher {
210
280
this . scheduleDiagnostics ( ) ;
211
281
}
212
282
283
+ updateWatchers ( ) {
284
+ clearTimeout ( this . pendingWatcherUpdate ) ;
285
+ this . pendingWatcherUpdate = setTimeout ( ( ) => this . updateWatchedDirectories ( ) , 1000 ) ;
286
+ }
287
+
213
288
scheduleDiagnostics ( ) {
214
289
clearTimeout ( this . updateDiagnostics ) ;
215
290
this . updateDiagnostics = setTimeout (
@@ -264,8 +339,17 @@ parseOptions(async (opts) => {
264
339
} ;
265
340
266
341
if ( opts . watch ) {
267
- svelteCheckOptions . onProjectReload = ( ) => watcher . scheduleDiagnostics ( ) ;
268
- const watcher = new DiagnosticsWatcher (
342
+ // Wire callbacks that can reference the watcher instance created below
343
+ let watcher : DiagnosticsWatcher ;
344
+ svelteCheckOptions . onProjectReload = ( ) => {
345
+ watcher . updateWatchers ( ) ;
346
+ watcher . scheduleDiagnostics ( ) ;
347
+ } ;
348
+ svelteCheckOptions . onFileSnapshotCreated = ( filePath : string ) => {
349
+ const dirPath = path . dirname ( filePath ) ;
350
+ watcher . addWatchDirectory ( dirPath ) ;
351
+ } ;
352
+ watcher = new DiagnosticsWatcher (
269
353
opts . workspaceUri ,
270
354
new SvelteCheck ( opts . workspaceUri . fsPath , svelteCheckOptions ) ,
271
355
writer ,
0 commit comments