Add explorer.autorevealExclude setting (#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
This commit is contained in:
Robert Jin 2022-11-14 20:50:55 +00:00 committed by GitHub
parent bdf8dd00e4
commit fbaacfb921
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 89 additions and 10 deletions

View file

@ -26,8 +26,8 @@ export class SettingsDocument {
return this.provideFilesAssociationsCompletionItems(location, position);
}
// files.exclude, search.exclude
if (location.path[0] === 'files.exclude' || location.path[0] === 'search.exclude') {
// files.exclude, search.exclude, explorer.autoRevealExclude
if (location.path[0] === 'files.exclude' || location.path[0] === 'search.exclude' || location.path[0] === 'explorer.autoRevealExclude') {
return this.provideExcludeCompletionItems(location, position);
}

View file

@ -87,7 +87,10 @@ export class ResourceGlobMatcher extends Disposable {
}
}
matches(resource: URI): boolean {
matches(
resource: URI,
hasSibling?: (name: string) => boolean
): boolean {
const folder = this.contextService.getWorkspaceFolder(resource);
let expressionForRoot: ParsedExpression | undefined;
@ -108,6 +111,6 @@ export class ResourceGlobMatcher extends Disposable {
resourcePathToMatch = resource.fsPath; // TODO@isidor: support non-file URIs
}
return !!expressionForRoot && typeof resourcePathToMatch === 'string' && !!expressionForRoot(resourcePathToMatch);
return !!expressionForRoot && typeof resourcePathToMatch === 'string' && !!expressionForRoot(resourcePathToMatch, undefined, hasSibling);
}
}

View file

@ -23,6 +23,8 @@ import { IProgressService, ProgressLocation, IProgressNotificationOptions, IProg
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { RunOnceScheduler } from 'vs/base/common/async';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IExpression } from 'vs/base/common/glob';
import { ResourceGlobMatcher } from 'vs/workbench/common/resources';
export const UNDO_REDO_SOURCE = new UndoRedoSource();
@ -39,6 +41,7 @@ export class ExplorerService implements IExplorerService {
private model: ExplorerModel;
private onFileChangesScheduler: RunOnceScheduler;
private fileChangeEvents: FileChangesEvent[] = [];
private revealExcludeMatcher: ResourceGlobMatcher;
constructor(
@IFileService private fileService: IFileService,
@ -130,6 +133,11 @@ export class ExplorerService implements IExplorerService {
this.refresh(false);
}
}));
this.revealExcludeMatcher = new ResourceGlobMatcher(
(uri) => getRevealExcludes(configurationService.getValue<IFilesConfiguration>({ resource: uri })),
(event) => event.affectsConfiguration('explorer.autoRevealExclude'),
contextService, configurationService);
this.disposables.add(this.revealExcludeMatcher);
}
get roots(): ExplorerItem[] {
@ -254,8 +262,14 @@ export class ExplorerService implements IExplorerService {
return;
}
// If file or parent matches exclude patterns, do not reveal unless reveal argument is 'force'
const ignoreRevealExcludes = reveal === 'force';
const fileStat = this.findClosest(resource);
if (fileStat) {
if (!this.shouldAutoRevealItem(fileStat, ignoreRevealExcludes)) {
return;
}
await this.view.selectResource(fileStat.resource, reveal);
return Promise.resolve(undefined);
}
@ -277,7 +291,10 @@ export class ExplorerService implements IExplorerService {
const item = root.find(resource);
await this.view.refresh(true, root);
// Select and Reveal
// Once item is resolved, check again if folder should be expanded
if (item && !this.shouldAutoRevealItem(item, ignoreRevealExcludes)) {
return;
}
await this.view.selectResource(item ? item.resource : undefined, reveal);
} catch (error) {
root.isError = true;
@ -395,6 +412,28 @@ export class ExplorerService implements IExplorerService {
}
}
// Check if an item matches a explorer.autoRevealExclude pattern
private shouldAutoRevealItem(item: ExplorerItem | undefined, ignore: boolean): boolean {
if (item === undefined || ignore) {
return true;
}
if (this.revealExcludeMatcher.matches(item.resource, name => !!(item.parent && item.parent.getChild(name)))) {
return false;
}
const root = item.root;
let currentItem = item.parent;
while (currentItem !== root) {
if (currentItem === undefined) {
return true;
}
if (this.revealExcludeMatcher.matches(currentItem.resource)) {
return false;
}
currentItem = currentItem.parent;
}
return true;
}
private async onConfigurationUpdated(configuration: IFilesConfiguration, event?: IConfigurationChangeEvent): Promise<void> {
let shouldRefresh = false;
@ -440,3 +479,13 @@ function doesFileEventAffect(item: ExplorerItem, view: IExplorerView, events: Fi
return false;
}
function getRevealExcludes(configuration: IFilesConfiguration): IExpression {
const revealExcludes = configuration && configuration.explorer && configuration.explorer.autoRevealExclude;
if (!revealExcludes) {
return {};
}
return revealExcludes;
}

View file

@ -330,7 +330,7 @@ CommandsRegistry.registerCommand({
const explorerView = viewlet.getExplorerView();
if (explorerView) {
explorerView.setExpanded(true);
await explorerService.select(uri, true);
await explorerService.select(uri, 'force');
explorerView.focus();
}
} else {

View file

@ -371,6 +371,30 @@ configurationRegistry.registerConfiguration({
],
'description': nls.localize('autoReveal', "Controls whether the explorer should automatically reveal and select files when opening them.")
},
'explorer.autoRevealExclude': {
'type': 'object',
'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)."),
'default': { '**/node_modules': true, '**/bower_components': true },
'additionalProperties': {
'anyOf': [
{
'type': 'boolean',
'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."),
},
{
type: 'object',
properties: {
when: {
type: 'string', // expression ({ "**/*.js": { "when": "$(basename).js" } })
pattern: '\\w*\\$\\(basename\\)\\w*',
default: '$(basename).ext',
description: nls.localize('explorer.autoRevealExclude.when', 'Additional check on the siblings of a matching file. Use $(basename) as variable for the matching file name.')
}
}
}
]
}
},
'explorer.enableDragAndDrop': {
'type': 'boolean',
'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."),

View file

@ -181,7 +181,7 @@ export class ExplorerView extends ViewPane implements IExplorerView {
private horizontalScrolling: boolean | undefined;
private dragHandler!: DelayedDragHandler;
private autoReveal: boolean | 'focusNoScroll' = false;
private autoReveal: boolean | 'force' | 'focusNoScroll' = false;
private decorationsProvider: ExplorerDecorationsProvider | undefined;
constructor(
@ -725,7 +725,7 @@ export class ExplorerView extends ViewPane implements IExplorerView {
}
public async selectResource(resource: URI | undefined, reveal = this.autoReveal, retry = 0): Promise<void> {
// do no retry more than once to prevent inifinite loops in cases of inconsistent model
// do no retry more than once to prevent infinite loops in cases of inconsistent model
if (retry === 2) {
return;
}
@ -766,7 +766,7 @@ export class ExplorerView extends ViewPane implements IExplorerView {
await this.tree.expand(item.nestedParent);
}
if (reveal === true && this.tree.getRelativeTop(item) === null) {
if ((reveal === true || reveal === 'force') && this.tree.getRelativeTop(item) === null) {
// Don't scroll to the item if it's already visible, or if set not to.
this.tree.reveal(item, 0.5);
}

View file

@ -604,7 +604,7 @@ interface CachedParsedExpression {
}
/**
* Respectes files.exclude setting in filtering out content from the explorer.
* Respects files.exclude setting in filtering out content from the explorer.
* Makes sure that visible editors are always shown in the explorer even if they are filtered out by settings.
*/
export class FilesFilter implements ITreeFilter<ExplorerItem, FuzzyScore> {

View file

@ -21,6 +21,7 @@ import { once } from 'vs/base/common/functional';
import { ITextEditorOptions } from 'vs/platform/editor/common/editor';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { localize } from 'vs/nls';
import { IExpression } from 'vs/base/common/glob';
/**
* Explorer viewlet id.
@ -88,6 +89,7 @@ export interface IFilesConfiguration extends PlatformIFilesConfiguration, IWorkb
sortOrder: 'editorOrder' | 'alphabetical' | 'fullPath';
};
autoReveal: boolean | 'focusNoScroll';
autoRevealExclude: IExpression;
enableDragAndDrop: boolean;
confirmDelete: boolean;
enableUndo: boolean;

View file

@ -739,6 +739,7 @@ export function isExcludeSetting(setting: ISetting): boolean {
return setting.key === 'files.exclude' ||
setting.key === 'search.exclude' ||
setting.key === 'workbench.localHistory.exclude' ||
setting.key === 'explorer.autoRevealExclude' ||
setting.key === 'files.watcherExclude';
}