Skip to content

Commit 9f45a29

Browse files
committed
Add explorer.autorevealExclude setting
1 parent f8b1720 commit 9f45a29

File tree

7 files changed

+141
-10
lines changed

7 files changed

+141
-10
lines changed

extensions/configuration-editing/src/settingsDocumentHelper.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ export class SettingsDocument {
2828
return this.provideFilesAssociationsCompletionItems(location, range);
2929
}
3030

31-
// files.exclude, search.exclude
32-
if (location.path[0] === 'files.exclude' || location.path[0] === 'search.exclude') {
31+
// files.exclude, search.exclude, explorer.autorevealExclude
32+
if (location.path[0] === 'files.exclude' || location.path[0] === 'search.exclude' || location.path[0] === 'explorer.autorevealExclude') {
3333
return this.provideExcludeCompletionItems(location, range);
3434
}
3535

src/vs/workbench/contrib/files/browser/explorerService.ts

Lines changed: 108 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import { Event } from 'vs/base/common/event';
77
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';
99
import { IFilesConfiguration, ISortOrderConfiguration, SortOrder, LexicographicOptions } from 'vs/workbench/contrib/files/common/files';
1010
import { ExplorerItem, ExplorerModel } from 'vs/workbench/contrib/files/common/explorerModel';
1111
import { URI } from 'vs/base/common/uri';
@@ -23,6 +23,12 @@ import { IProgressService, ProgressLocation, IProgressNotificationOptions, IProg
2323
import { CancellationTokenSource } from 'vs/base/common/cancellation';
2424
import { RunOnceScheduler } from 'vs/base/common/async';
2525
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';
2632

2733
export const UNDO_REDO_SOURCE = new UndoRedoSource();
2834

@@ -40,6 +46,7 @@ export class ExplorerService implements IExplorerService {
4046
private model: ExplorerModel;
4147
private onFileChangesScheduler: RunOnceScheduler;
4248
private fileChangeEvents: FileChangesEvent[] = [];
49+
private revealExcludeMatcher: RevealFilter;
4350

4451
constructor(
4552
@IFileService private fileService: IFileService,
@@ -128,6 +135,10 @@ export class ExplorerService implements IExplorerService {
128135
}));
129136
// Refresh explorer when window gets focus to compensate for missing file events #126817
130137
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()))
131142
}
132143

133144
get roots(): ExplorerItem[] {
@@ -232,7 +243,7 @@ export class ExplorerService implements IExplorerService {
232243
}
233244

234245
const fileStat = this.findClosest(resource);
235-
if (fileStat) {
246+
if (fileStat && this.revealExcludeMatcher.shouldReveal(fileStat)) {
236247
await this.view.selectResource(fileStat.resource, reveal);
237248
return Promise.resolve(undefined);
238249
}
@@ -255,6 +266,9 @@ export class ExplorerService implements IExplorerService {
255266
await this.view.refresh(true, root);
256267

257268
// Select and Reveal
269+
if (item && reveal && !this.revealExcludeMatcher.shouldReveal(item)) {
270+
return;
271+
}
258272
await this.view.selectResource(item ? item.resource : undefined, reveal);
259273
} catch (error) {
260274
root.isError = true;
@@ -399,3 +413,95 @@ function doesFileEventAffect(item: ExplorerItem, view: IExplorerView, events: Fi
399413

400414
return false;
401415
}
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+
}

src/vs/workbench/contrib/files/browser/files.contribution.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,30 @@ configurationRegistry.registerConfiguration({
376376
],
377377
'description': nls.localize('autoReveal', "Controls whether the explorer should automatically reveal and select files when opening them.")
378378
},
379+
'explorer.autoRevealExclude': {
380+
'type': 'object',
381+
'markdownDescription': nls.localize('autoRevealExclude', "Configure glob patterns for excluding files and folders from being revealed and selected in the explorer when they are opened. Inherits all glob patterns from the `#files.exclude#` setting. Read more about glob patterns [here](https://code.visualstudio.com/docs/editor/codebasics#_advanced-search-options)."),
382+
'default': { '**/node_modules': true, '**/bower_components': true, '**/*.code-search': true },
383+
'additionalProperties': {
384+
'anyOf': [
385+
{
386+
'type': 'boolean',
387+
'description': nls.localize('explorer.autoRevealExclude.boolean', "The glob pattern to match file paths against. Set to true or false to enable or disable the pattern."),
388+
},
389+
{
390+
type: 'object',
391+
properties: {
392+
when: {
393+
type: 'string', // expression ({ "**/*.js": { "when": "$(basename).js" } })
394+
pattern: '\\w*\\$\\(basename\\)\\w*',
395+
default: '$(basename).ext',
396+
description: nls.localize('explorer.autoRevealExclude.when', 'Additional check on the siblings of a matching file. Use $(basename) as variable for the matching file name.')
397+
}
398+
}
399+
}
400+
]
401+
}
402+
},
379403
'explorer.enableDragAndDrop': {
380404
'type': 'boolean',
381405
'description': nls.localize('enableDragAndDrop', "Controls whether the explorer should allow to move files and folders via drag and drop. This setting only effects drag and drop from inside the explorer."),

src/vs/workbench/contrib/files/browser/views/explorerView.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -680,7 +680,7 @@ export class ExplorerView extends ViewPane {
680680
}
681681

682682
public async selectResource(resource: URI | undefined, reveal = this.autoReveal, retry = 0): Promise<void> {
683-
// do no retry more than once to prevent inifinite loops in cases of inconsistent model
683+
// do no retry more than once to prevent infinite loops in cases of inconsistent model
684684
if (retry === 2) {
685685
return;
686686
}

src/vs/workbench/contrib/files/browser/views/explorerViewer.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -528,13 +528,13 @@ export class FilesRenderer implements ICompressibleTreeRenderer<ExplorerItem, Fu
528528
}
529529
}
530530

531-
interface CachedParsedExpression {
531+
export interface CachedParsedExpression {
532532
original: glob.IExpression;
533533
parsed: glob.ParsedExpression;
534534
}
535535

536536
/**
537-
* Respectes files.exclude setting in filtering out content from the explorer.
537+
* Respects files.exclude setting in filtering out content from the explorer.
538538
* Makes sure that visible editors are always shown in the explorer even if they are filtered out by settings.
539539
*/
540540
export class FilesFilter implements ITreeFilter<ExplorerItem, FuzzyScore> {

src/vs/workbench/contrib/files/common/files.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { once } from 'vs/base/common/functional';
2121
import { ITextEditorOptions } from 'vs/platform/editor/common/editor';
2222
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
2323
import { localize } from 'vs/nls';
24+
import { IExpression } from 'vs/base/common/glob';
2425

2526
/**
2627
* Explorer viewlet id.
@@ -86,6 +87,7 @@ export interface IFilesConfiguration extends PlatformIFilesConfiguration, IWorkb
8687
sortOrder: 'editorOrder' | 'alphabetical';
8788
};
8889
autoReveal: boolean | 'focusNoScroll';
90+
autoRevealExclude: IExpression;
8991
enableDragAndDrop: boolean;
9092
confirmDelete: boolean;
9193
expandSingleFolderWorkspaces: boolean;

src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -570,9 +570,8 @@ function trimCategoryForGroup(category: string, groupId: string): string {
570570
}
571571

572572
export function isExcludeSetting(setting: ISetting): boolean {
573-
return setting.key === 'files.exclude' ||
574-
setting.key === 'search.exclude' ||
575-
setting.key === 'files.watcherExclude';
573+
return setting.key in
574+
['files.exclude', 'search.exclude', 'files.watcherExclude', 'explorer.autoRevealExclude'];
576575
}
577576

578577
function isObjectRenderableSchema({ type }: IJSONSchema): boolean {

0 commit comments

Comments
 (0)