Skip to content

Commit fbaacfb

Browse files
authored
Add explorer.autorevealExclude setting (microsoft#136905)
* Add explorer.autorevealExclude setting * Update setting name, only check sibling once * linting * Correct boolean order and add catch for force reveal * Check for force instead of converting * Do not make revealexcludes inherit from file.excludes * Linting
1 parent bdf8dd0 commit fbaacfb

File tree

9 files changed

+89
-10
lines changed

9 files changed

+89
-10
lines changed

extensions/configuration-editing/src/settingsDocumentHelper.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ export class SettingsDocument {
2626
return this.provideFilesAssociationsCompletionItems(location, position);
2727
}
2828

29-
// files.exclude, search.exclude
30-
if (location.path[0] === 'files.exclude' || location.path[0] === 'search.exclude') {
29+
// files.exclude, search.exclude, explorer.autoRevealExclude
30+
if (location.path[0] === 'files.exclude' || location.path[0] === 'search.exclude' || location.path[0] === 'explorer.autoRevealExclude') {
3131
return this.provideExcludeCompletionItems(location, position);
3232
}
3333

src/vs/workbench/common/resources.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,10 @@ export class ResourceGlobMatcher extends Disposable {
8787
}
8888
}
8989

90-
matches(resource: URI): boolean {
90+
matches(
91+
resource: URI,
92+
hasSibling?: (name: string) => boolean
93+
): boolean {
9194
const folder = this.contextService.getWorkspaceFolder(resource);
9295

9396
let expressionForRoot: ParsedExpression | undefined;
@@ -108,6 +111,6 @@ export class ResourceGlobMatcher extends Disposable {
108111
resourcePathToMatch = resource.fsPath; // TODO@isidor: support non-file URIs
109112
}
110113

111-
return !!expressionForRoot && typeof resourcePathToMatch === 'string' && !!expressionForRoot(resourcePathToMatch);
114+
return !!expressionForRoot && typeof resourcePathToMatch === 'string' && !!expressionForRoot(resourcePathToMatch, undefined, hasSibling);
112115
}
113116
}

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

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ 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 } from 'vs/base/common/glob';
27+
import { ResourceGlobMatcher } from 'vs/workbench/common/resources';
2628

2729
export const UNDO_REDO_SOURCE = new UndoRedoSource();
2830

@@ -39,6 +41,7 @@ export class ExplorerService implements IExplorerService {
3941
private model: ExplorerModel;
4042
private onFileChangesScheduler: RunOnceScheduler;
4143
private fileChangeEvents: FileChangesEvent[] = [];
44+
private revealExcludeMatcher: ResourceGlobMatcher;
4245

4346
constructor(
4447
@IFileService private fileService: IFileService,
@@ -130,6 +133,11 @@ export class ExplorerService implements IExplorerService {
130133
this.refresh(false);
131134
}
132135
}));
136+
this.revealExcludeMatcher = new ResourceGlobMatcher(
137+
(uri) => getRevealExcludes(configurationService.getValue<IFilesConfiguration>({ resource: uri })),
138+
(event) => event.affectsConfiguration('explorer.autoRevealExclude'),
139+
contextService, configurationService);
140+
this.disposables.add(this.revealExcludeMatcher);
133141
}
134142

135143
get roots(): ExplorerItem[] {
@@ -254,8 +262,14 @@ export class ExplorerService implements IExplorerService {
254262
return;
255263
}
256264

265+
// If file or parent matches exclude patterns, do not reveal unless reveal argument is 'force'
266+
const ignoreRevealExcludes = reveal === 'force';
267+
257268
const fileStat = this.findClosest(resource);
258269
if (fileStat) {
270+
if (!this.shouldAutoRevealItem(fileStat, ignoreRevealExcludes)) {
271+
return;
272+
}
259273
await this.view.selectResource(fileStat.resource, reveal);
260274
return Promise.resolve(undefined);
261275
}
@@ -277,7 +291,10 @@ export class ExplorerService implements IExplorerService {
277291
const item = root.find(resource);
278292
await this.view.refresh(true, root);
279293

280-
// Select and Reveal
294+
// Once item is resolved, check again if folder should be expanded
295+
if (item && !this.shouldAutoRevealItem(item, ignoreRevealExcludes)) {
296+
return;
297+
}
281298
await this.view.selectResource(item ? item.resource : undefined, reveal);
282299
} catch (error) {
283300
root.isError = true;
@@ -395,6 +412,28 @@ export class ExplorerService implements IExplorerService {
395412
}
396413
}
397414

415+
// Check if an item matches a explorer.autoRevealExclude pattern
416+
private shouldAutoRevealItem(item: ExplorerItem | undefined, ignore: boolean): boolean {
417+
if (item === undefined || ignore) {
418+
return true;
419+
}
420+
if (this.revealExcludeMatcher.matches(item.resource, name => !!(item.parent && item.parent.getChild(name)))) {
421+
return false;
422+
}
423+
const root = item.root;
424+
let currentItem = item.parent;
425+
while (currentItem !== root) {
426+
if (currentItem === undefined) {
427+
return true;
428+
}
429+
if (this.revealExcludeMatcher.matches(currentItem.resource)) {
430+
return false;
431+
}
432+
currentItem = currentItem.parent;
433+
}
434+
return true;
435+
}
436+
398437
private async onConfigurationUpdated(configuration: IFilesConfiguration, event?: IConfigurationChangeEvent): Promise<void> {
399438
let shouldRefresh = false;
400439

@@ -440,3 +479,13 @@ function doesFileEventAffect(item: ExplorerItem, view: IExplorerView, events: Fi
440479

441480
return false;
442481
}
482+
483+
function getRevealExcludes(configuration: IFilesConfiguration): IExpression {
484+
const revealExcludes = configuration && configuration.explorer && configuration.explorer.autoRevealExclude;
485+
486+
if (!revealExcludes) {
487+
return {};
488+
}
489+
490+
return revealExcludes;
491+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ CommandsRegistry.registerCommand({
330330
const explorerView = viewlet.getExplorerView();
331331
if (explorerView) {
332332
explorerView.setExpanded(true);
333-
await explorerService.select(uri, true);
333+
await explorerService.select(uri, 'force');
334334
explorerView.focus();
335335
}
336336
} else {

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,30 @@ configurationRegistry.registerConfiguration({
371371
],
372372
'description': nls.localize('autoReveal', "Controls whether the explorer should automatically reveal and select files when opening them.")
373373
},
374+
'explorer.autoRevealExclude': {
375+
'type': 'object',
376+
'markdownDescription': nls.localize('autoRevealExclude', "Configure glob patterns for excluding files and folders from being revealed and selected in the explorer when they are opened. Read more about glob patterns [here](https://code.visualstudio.com/docs/editor/codebasics#_advanced-search-options)."),
377+
'default': { '**/node_modules': true, '**/bower_components': true },
378+
'additionalProperties': {
379+
'anyOf': [
380+
{
381+
'type': 'boolean',
382+
'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."),
383+
},
384+
{
385+
type: 'object',
386+
properties: {
387+
when: {
388+
type: 'string', // expression ({ "**/*.js": { "when": "$(basename).js" } })
389+
pattern: '\\w*\\$\\(basename\\)\\w*',
390+
default: '$(basename).ext',
391+
description: nls.localize('explorer.autoRevealExclude.when', 'Additional check on the siblings of a matching file. Use $(basename) as variable for the matching file name.')
392+
}
393+
}
394+
}
395+
]
396+
}
397+
},
374398
'explorer.enableDragAndDrop': {
375399
'type': 'boolean',
376400
'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: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ export class ExplorerView extends ViewPane implements IExplorerView {
181181
private horizontalScrolling: boolean | undefined;
182182

183183
private dragHandler!: DelayedDragHandler;
184-
private autoReveal: boolean | 'focusNoScroll' = false;
184+
private autoReveal: boolean | 'force' | 'focusNoScroll' = false;
185185
private decorationsProvider: ExplorerDecorationsProvider | undefined;
186186

187187
constructor(
@@ -725,7 +725,7 @@ export class ExplorerView extends ViewPane implements IExplorerView {
725725
}
726726

727727
public async selectResource(resource: URI | undefined, reveal = this.autoReveal, retry = 0): Promise<void> {
728-
// do no retry more than once to prevent inifinite loops in cases of inconsistent model
728+
// do no retry more than once to prevent infinite loops in cases of inconsistent model
729729
if (retry === 2) {
730730
return;
731731
}
@@ -766,7 +766,7 @@ export class ExplorerView extends ViewPane implements IExplorerView {
766766
await this.tree.expand(item.nestedParent);
767767
}
768768

769-
if (reveal === true && this.tree.getRelativeTop(item) === null) {
769+
if ((reveal === true || reveal === 'force') && this.tree.getRelativeTop(item) === null) {
770770
// Don't scroll to the item if it's already visible, or if set not to.
771771
this.tree.reveal(item, 0.5);
772772
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -604,7 +604,7 @@ interface CachedParsedExpression {
604604
}
605605

606606
/**
607-
* Respectes files.exclude setting in filtering out content from the explorer.
607+
* Respects files.exclude setting in filtering out content from the explorer.
608608
* Makes sure that visible editors are always shown in the explorer even if they are filtered out by settings.
609609
*/
610610
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.
@@ -88,6 +89,7 @@ export interface IFilesConfiguration extends PlatformIFilesConfiguration, IWorkb
8889
sortOrder: 'editorOrder' | 'alphabetical' | 'fullPath';
8990
};
9091
autoReveal: boolean | 'focusNoScroll';
92+
autoRevealExclude: IExpression;
9193
enableDragAndDrop: boolean;
9294
confirmDelete: boolean;
9395
enableUndo: boolean;

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,7 @@ export function isExcludeSetting(setting: ISetting): boolean {
739739
return setting.key === 'files.exclude' ||
740740
setting.key === 'search.exclude' ||
741741
setting.key === 'workbench.localHistory.exclude' ||
742+
setting.key === 'explorer.autoRevealExclude' ||
742743
setting.key === 'files.watcherExclude';
743744
}
744745

0 commit comments

Comments
 (0)