Skip to content

Commit 20d1817

Browse files
authored
introduce findFiles2 API (microsoft#203844)
* introduce first version of FindFiles2 API
1 parent 6c7362f commit 20d1817

File tree

10 files changed

+391
-44
lines changed

10 files changed

+391
-44
lines changed

extensions/vscode-api-tests/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"extensionsAny",
2323
"externalUriOpener",
2424
"fileSearchProvider",
25+
"findFiles2",
2526
"findTextInFiles",
2627
"fsChunks",
2728
"interactive",

extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,45 @@ suite('vscode API - workspace', () => {
597597
});
598598
});
599599

600+
test('`findFiles2`', () => {
601+
return vscode.workspace.findFiles2('*image.png').then((res) => {
602+
assert.strictEqual(res.length, 4);
603+
// TODO: see why this is fuzzy matching
604+
});
605+
});
606+
607+
test('findFiles2 - null exclude', async () => {
608+
await vscode.workspace.findFiles2('**/file.txt', { useDefaultExcludes: true, useDefaultSearchExcludes: false }).then((res) => {
609+
// file.exclude folder is still searched, search.exclude folder is not
610+
assert.strictEqual(res.length, 1);
611+
assert.strictEqual(basename(vscode.workspace.asRelativePath(res[0])), 'file.txt');
612+
});
613+
614+
await vscode.workspace.findFiles2('**/file.txt', { useDefaultExcludes: false, useDefaultSearchExcludes: false }).then((res) => {
615+
// search.exclude and files.exclude folders are both searched
616+
assert.strictEqual(res.length, 2);
617+
assert.strictEqual(basename(vscode.workspace.asRelativePath(res[0])), 'file.txt');
618+
});
619+
});
620+
621+
test('findFiles2, exclude', () => {
622+
return vscode.workspace.findFiles2('*image.png', { exclude: '**/sub/**' }).then((res) => {
623+
assert.strictEqual(res.length, 3);
624+
// TODO: see why this is fuzzy matching
625+
});
626+
});
627+
628+
test('findFiles2, cancellation', () => {
629+
630+
const source = new vscode.CancellationTokenSource();
631+
const token = source.token; // just to get an instance first
632+
source.cancel();
633+
634+
return vscode.workspace.findFiles2('*.js', {}, token).then((res) => {
635+
assert.deepStrictEqual(res, []);
636+
});
637+
});
638+
600639
test('findTextInFiles', async () => {
601640
const options: vscode.FindTextInFilesOptions = {
602641
include: '*.ts',

src/vs/workbench/api/browser/mainThreadWorkspace.ts

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { WorkspaceTrustRequestOptions, IWorkspaceTrustManagementService, IWorksp
1919
import { IWorkspace, IWorkspaceContextService, WorkbenchState, isUntitledWorkspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace';
2020
import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';
2121
import { checkGlobFileExists } from 'vs/workbench/services/extensions/common/workspaceContains';
22-
import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/services/search/common/queryBuilder';
22+
import { IFileQueryBuilderOptions, ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/services/search/common/queryBuilder';
2323
import { IEditorService, ISaveEditorsResult } from 'vs/workbench/services/editor/common/editorService';
2424
import { IFileMatch, IPatternInfo, ISearchProgressItem, ISearchService } from 'vs/workbench/services/search/common/search';
2525
import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing';
@@ -140,21 +140,14 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
140140

141141
// --- search ---
142142

143-
$startFileSearch(includePattern: string | null, _includeFolder: UriComponents | null, excludePatternOrDisregardExcludes: string | false | null, maxResults: number | null, token: CancellationToken): Promise<UriComponents[] | null> {
143+
$startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise<UriComponents[] | null> {
144144
const includeFolder = URI.revive(_includeFolder);
145145
const workspace = this._contextService.getWorkspace();
146146

147147
const query = this._queryBuilder.file(
148148
includeFolder ? [includeFolder] : workspace.folders,
149-
{
150-
maxResults: maxResults ?? undefined,
151-
disregardExcludeSettings: (excludePatternOrDisregardExcludes === false) || undefined,
152-
disregardSearchExcludeSettings: true,
153-
disregardIgnoreFiles: true,
154-
includePattern: includePattern ?? undefined,
155-
excludePattern: typeof excludePatternOrDisregardExcludes === 'string' ? excludePatternOrDisregardExcludes : undefined,
156-
_reason: 'startFileSearch'
157-
});
149+
options
150+
);
158151

159152
return this._searchService.fileSearch(query, token).then(result => {
160153
return result.results.map(m => m.resource);

src/vs/workbench/api/common/extHost.api.impl.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -938,6 +938,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
938938
// Note, undefined/null have different meanings on "exclude"
939939
return extHostWorkspace.findFiles(include, exclude, maxResults, extension.identifier, token);
940940
},
941+
findFiles2: (filePattern, options?, token?) => {
942+
return extHostWorkspace.findFiles2(filePattern, options, extension.identifier, token);
943+
},
941944
findTextInFiles: (query: vscode.TextSearchQuery, optionsOrCallback: vscode.FindTextInFilesOptions | ((result: vscode.TextSearchResult) => void), callbackOrToken?: vscode.CancellationToken | ((result: vscode.TextSearchResult) => void), token?: vscode.CancellationToken) => {
942945
checkProposedApiEnabled(extension, 'findTextInFiles');
943946
let options: vscode.FindTextInFilesOptions;

src/vs/workbench/api/common/extHost.protocol.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ import { Dto, IRPCProtocol, SerializableObjectWithBuffers, createProxyIdentifier
7878
import { ILanguageStatus } from 'vs/workbench/services/languageStatus/common/languageStatusService';
7979
import { OutputChannelUpdateMode } from 'vs/workbench/services/output/common/output';
8080
import { CandidatePort } from 'vs/workbench/services/remote/common/tunnelModel';
81-
import { ITextQueryBuilderOptions } from 'vs/workbench/services/search/common/queryBuilder';
81+
import { IFileQueryBuilderOptions, ITextQueryBuilderOptions } from 'vs/workbench/services/search/common/queryBuilder';
8282
import * as search from 'vs/workbench/services/search/common/search';
8383
import { ISaveProfileResult } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
8484

@@ -1341,7 +1341,7 @@ export interface ITextSearchComplete {
13411341
}
13421342

13431343
export interface MainThreadWorkspaceShape extends IDisposable {
1344-
$startFileSearch(includePattern: string | null, includeFolder: UriComponents | null, excludePatternOrDisregardExcludes: string | false | null, maxResults: number | null, token: CancellationToken): Promise<UriComponents[] | null>;
1344+
$startFileSearch(includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise<UriComponents[] | null>;
13451345
$startTextSearch(query: search.IPatternInfo, folder: UriComponents | null, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise<ITextSearchComplete | null>;
13461346
$checkExists(folders: readonly UriComponents[], includes: string[], token: CancellationToken): Promise<boolean>;
13471347
$save(uri: UriComponents, options: { saveAs: boolean }): Promise<UriComponents | undefined>;

src/vs/workbench/api/common/extHostWorkspace.ts

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
2828
import { GlobPattern } from 'vs/workbench/api/common/extHostTypeConverters';
2929
import { Range } from 'vs/workbench/api/common/extHostTypes';
3030
import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService';
31-
import { ITextQueryBuilderOptions } from 'vs/workbench/services/search/common/queryBuilder';
31+
import { IFileQueryBuilderOptions, ITextQueryBuilderOptions } from 'vs/workbench/services/search/common/queryBuilder';
3232
import { IRawFileMatch2, ITextSearchResult, resultIsMatch } from 'vs/workbench/services/search/common/search';
3333
import * as vscode from 'vscode';
3434
import { ExtHostWorkspaceShape, IRelativePatternDto, IWorkspaceData, MainContext, MainThreadMessageOptions, MainThreadMessageServiceShape, MainThreadWorkspaceShape } from './extHost.protocol';
@@ -446,27 +446,73 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac
446446
findFiles(include: vscode.GlobPattern | undefined, exclude: vscode.GlobPattern | null | undefined, maxResults: number | undefined, extensionId: ExtensionIdentifier, token: vscode.CancellationToken = CancellationToken.None): Promise<vscode.Uri[]> {
447447
this._logService.trace(`extHostWorkspace#findFiles: fileSearch, extension: ${extensionId.value}, entryPoint: findFiles`);
448448

449-
let excludePatternOrDisregardExcludes: string | false | undefined = undefined;
449+
let excludeString: string = '';
450+
let useFileExcludes = true;
450451
if (exclude === null) {
451-
excludePatternOrDisregardExcludes = false;
452-
} else if (exclude) {
452+
useFileExcludes = false;
453+
} else if (exclude !== undefined) {
453454
if (typeof exclude === 'string') {
454-
excludePatternOrDisregardExcludes = exclude;
455+
excludeString = exclude;
455456
} else {
456-
excludePatternOrDisregardExcludes = exclude.pattern;
457+
excludeString = exclude.pattern;
457458
}
458459
}
459-
460+
return this._findFilesImpl(include, undefined, {
461+
exclude: excludeString,
462+
maxResults,
463+
useDefaultExcludes: useFileExcludes,
464+
useDefaultSearchExcludes: false,
465+
useIgnoreFiles: true
466+
}, token);
467+
}
468+
469+
findFiles2(filePattern: vscode.GlobPattern | undefined,
470+
options: vscode.FindFiles2Options = {},
471+
extensionId: ExtensionIdentifier,
472+
token: vscode.CancellationToken = CancellationToken.None): Promise<vscode.Uri[]> {
473+
this._logService.trace(`extHostWorkspace#findFiles2: fileSearch, extension: ${extensionId.value}, entryPoint: findFiles2`);
474+
return this._findFilesImpl(undefined, filePattern, options, token);
475+
}
476+
477+
private async _findFilesImpl(
478+
// the old `findFiles` used `include` to query, but the new `findFiles2` uses `filePattern` to query.
479+
// `filePattern` is the proper way to handle this, since it takes less precedence than the ignore files.
480+
include: vscode.GlobPattern | undefined,
481+
filePattern: vscode.GlobPattern | undefined,
482+
options: vscode.FindFiles2Options,
483+
token: vscode.CancellationToken = CancellationToken.None): Promise<vscode.Uri[]> {
460484
if (token && token.isCancellationRequested) {
461485
return Promise.resolve([]);
462486
}
463487

464-
const { includePattern, folder } = parseSearchInclude(GlobPattern.from(include));
488+
const excludePattern = (typeof options.exclude === 'string') ? options.exclude :
489+
options.exclude ? options.exclude.pattern : undefined;
490+
491+
const fileQueries = <IFileQueryBuilderOptions>{
492+
ignoreSymlinks: typeof options.followSymlinks === 'boolean' ? !options.followSymlinks : undefined,
493+
disregardIgnoreFiles: typeof options.useIgnoreFiles === 'boolean' ? !options.useIgnoreFiles : undefined,
494+
disregardGlobalIgnoreFiles: typeof options.useGlobalIgnoreFiles === 'boolean' ? !options.useGlobalIgnoreFiles : undefined,
495+
disregardParentIgnoreFiles: typeof options.useParentIgnoreFiles === 'boolean' ? !options.useParentIgnoreFiles : undefined,
496+
disregardExcludeSettings: typeof options.useDefaultExcludes === 'boolean' ? !options.useDefaultExcludes : false,
497+
disregardSearchExcludeSettings: typeof options.useDefaultSearchExcludes === 'boolean' ? !options.useDefaultSearchExcludes : false,
498+
maxResults: options.maxResults,
499+
excludePattern: excludePattern,
500+
_reason: 'startFileSearch'
501+
};
502+
let folderToUse: URI | undefined;
503+
if (include) {
504+
const { includePattern, folder } = parseSearchInclude(GlobPattern.from(include));
505+
folderToUse = folder;
506+
fileQueries.includePattern = includePattern;
507+
} else {
508+
const { includePattern, folder } = parseSearchInclude(GlobPattern.from(filePattern));
509+
folderToUse = folder;
510+
fileQueries.filePattern = includePattern;
511+
}
512+
465513
return this._proxy.$startFileSearch(
466-
includePattern ?? null,
467-
folder ?? null,
468-
excludePatternOrDisregardExcludes ?? null,
469-
maxResults ?? null,
514+
folderToUse ?? null,
515+
fileQueries,
470516
token
471517
)
472518
.then(data => Array.isArray(data) ? data.map(d => URI.revive(d)) : []);

0 commit comments

Comments
 (0)