5
5
6
6
import { Event } from 'vs/base/common/event' ;
7
7
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace' ;
8
- import { DisposableStore } from 'vs/base/common/lifecycle' ;
8
+ import { DisposableStore , dispose } from 'vs/base/common/lifecycle' ;
9
9
import { IFilesConfiguration , ISortOrderConfiguration , SortOrder , LexicographicOptions } from 'vs/workbench/contrib/files/common/files' ;
10
10
import { ExplorerItem , ExplorerModel } from 'vs/workbench/contrib/files/common/explorerModel' ;
11
11
import { URI } from 'vs/base/common/uri' ;
@@ -23,6 +23,12 @@ import { IProgressService, ProgressLocation, IProgressNotificationOptions, IProg
23
23
import { CancellationTokenSource } from 'vs/base/common/cancellation' ;
24
24
import { RunOnceScheduler } from 'vs/base/common/async' ;
25
25
import { IHostService } from 'vs/workbench/services/host/browser/host' ;
26
+ import { IExpression , parse } from 'vs/base/common/glob' ;
27
+ import { mixin , deepClone , equals } from 'vs/base/common/objects' ;
28
+ import { IDisposable } from 'vs/base/common/lifecycle' ;
29
+ import { CachedParsedExpression } from 'vs/workbench/contrib/files/browser/views/explorerViewer' ;
30
+ import { Emitter } from 'vs/base/common/event' ;
31
+ import { relative } from 'path' ;
26
32
27
33
export const UNDO_REDO_SOURCE = new UndoRedoSource ( ) ;
28
34
@@ -40,6 +46,7 @@ export class ExplorerService implements IExplorerService {
40
46
private model : ExplorerModel ;
41
47
private onFileChangesScheduler : RunOnceScheduler ;
42
48
private fileChangeEvents : FileChangesEvent [ ] = [ ] ;
49
+ private revealExcludeMatcher : RevealFilter ;
43
50
44
51
constructor (
45
52
@IFileService private fileService : IFileService ,
@@ -128,6 +135,10 @@ export class ExplorerService implements IExplorerService {
128
135
} ) ) ;
129
136
// Refresh explorer when window gets focus to compensate for missing file events #126817
130
137
this . disposables . add ( hostService . onDidChangeFocus ( hasFocus => hasFocus ? this . refresh ( false ) : undefined ) ) ;
138
+ this . revealExcludeMatcher = new RevealFilter (
139
+ contextService , configurationService ) ;
140
+ this . disposables . add ( this . revealExcludeMatcher ) ;
141
+ this . disposables . add ( this . revealExcludeMatcher . onDidChange ( ( ) => this . refresh ( ) ) )
131
142
}
132
143
133
144
get roots ( ) : ExplorerItem [ ] {
@@ -232,7 +243,7 @@ export class ExplorerService implements IExplorerService {
232
243
}
233
244
234
245
const fileStat = this . findClosest ( resource ) ;
235
- if ( fileStat ) {
246
+ if ( fileStat && this . revealExcludeMatcher . shouldReveal ( fileStat ) ) {
236
247
await this . view . selectResource ( fileStat . resource , reveal ) ;
237
248
return Promise . resolve ( undefined ) ;
238
249
}
@@ -255,6 +266,9 @@ export class ExplorerService implements IExplorerService {
255
266
await this . view . refresh ( true , root ) ;
256
267
257
268
// Select and Reveal
269
+ if ( item && reveal && ! this . revealExcludeMatcher . shouldReveal ( item ) ) {
270
+ return ;
271
+ }
258
272
await this . view . selectResource ( item ? item . resource : undefined , reveal ) ;
259
273
} catch ( error ) {
260
274
root . isError = true ;
@@ -399,3 +413,95 @@ function doesFileEventAffect(item: ExplorerItem, view: IExplorerView, events: Fi
399
413
400
414
return false ;
401
415
}
416
+
417
+ /**
418
+ * Respects explorer.autoRevealExclude setting in filtering out content from being revealed when opened in the editor.
419
+ */
420
+ class RevealFilter implements IDisposable {
421
+ private revealExpressionPerRoot = new Map < string , CachedParsedExpression > ( ) ;
422
+ private _onDidChange = new Emitter < void > ( ) ;
423
+ private toDispose : IDisposable [ ] = [ ] ;
424
+
425
+ constructor (
426
+ @IWorkspaceContextService private readonly contextService : IWorkspaceContextService ,
427
+ @IConfigurationService private readonly configurationService : IConfigurationService ,
428
+ ) {
429
+ this . toDispose . push ( this . contextService . onDidChangeWorkspaceFolders ( ( ) => this . updateConfiguration ( ) ) ) ;
430
+ this . toDispose . push ( this . configurationService . onDidChangeConfiguration ( ( e ) => {
431
+ if ( e . affectsConfiguration ( 'explorer.autoRevealExclude' ) ) {
432
+ this . updateConfiguration ( ) ;
433
+ }
434
+ } ) ) ;
435
+ this . updateConfiguration ( ) ;
436
+ }
437
+
438
+ get onDidChange ( ) : Event < void > {
439
+ return this . _onDidChange . event ;
440
+ }
441
+
442
+ private updateConfiguration ( ) : void {
443
+ let shouldFire = false ;
444
+ this . contextService . getWorkspace ( ) . folders . forEach ( folder => {
445
+ const configuration = this . configurationService . getValue < IFilesConfiguration > ( { resource : folder . uri } ) ;
446
+ const excludesConfig : IExpression = getRevealExcludes ( configuration , true ) || Object . create ( null )
447
+
448
+ if ( ! shouldFire ) {
449
+ const cached = this . revealExpressionPerRoot . get ( folder . uri . toString ( ) ) ;
450
+ shouldFire = ! cached || ! equals ( cached . original , excludesConfig ) ;
451
+ }
452
+
453
+ const excludesConfigCopy = deepClone ( excludesConfig ) ; // do not keep the config, as it gets mutated under our hoods
454
+
455
+ this . revealExpressionPerRoot . set ( folder . uri . toString ( ) , { original : excludesConfigCopy , parsed : parse ( excludesConfigCopy ) } ) ;
456
+ } ) ;
457
+
458
+ if ( shouldFire ) {
459
+ this . _onDidChange . fire ( ) ;
460
+ }
461
+ }
462
+
463
+ shouldReveal ( item : ExplorerItem ) : boolean {
464
+ // Check whether file or parent matches pattern
465
+ const cached = this . revealExpressionPerRoot . get ( item . root . resource . toString ( ) ) ;
466
+ if ( ! cached ) {
467
+ return true ;
468
+ }
469
+ const root = item . root ;
470
+ let currentItem = item ;
471
+ // If any parent up to the root matches pattern, do not reveal
472
+ while ( currentItem != root ) {
473
+ if ( cached . parsed ( relative ( currentItem . root . resource . path , currentItem . resource . path ) , currentItem . name , name => ! ! ( currentItem . parent && currentItem . parent . getChild ( name ) ) ) ) {
474
+ return false ;
475
+ }
476
+ if ( ! currentItem . parent ) {
477
+ return true ;
478
+ } ;
479
+ currentItem = currentItem . parent ;
480
+ }
481
+ return true ;
482
+ }
483
+
484
+ dispose ( ) : void {
485
+ dispose ( this . toDispose ) ;
486
+ }
487
+ }
488
+
489
+ function getRevealExcludes ( configuration : IFilesConfiguration , includeRevealExcludes = true ) : IExpression | undefined {
490
+ const fileExcludes = configuration && configuration . files && configuration . files . exclude ;
491
+ const revealExcludes = includeRevealExcludes && configuration && configuration . explorer && configuration . explorer . autoRevealExclude ;
492
+
493
+ if ( ! fileExcludes && ! revealExcludes ) {
494
+ return undefined ;
495
+ }
496
+
497
+ if ( ! fileExcludes || ! revealExcludes ) {
498
+ return fileExcludes || revealExcludes ;
499
+ }
500
+
501
+ let allExcludes : IExpression = Object . create ( null ) ;
502
+ // clone the config as it could be frozen
503
+ allExcludes = mixin ( allExcludes , deepClone ( fileExcludes ) ) ;
504
+ allExcludes = mixin ( allExcludes , deepClone ( revealExcludes ) , true ) ;
505
+
506
+ return allExcludes ;
507
+ }
0 commit comments