introduce findFiles2 API (#203844)

* introduce first version of FindFiles2 API
This commit is contained in:
Andrea Mah 2024-02-07 16:23:46 -06:00 committed by GitHub
parent 6c7362fe4f
commit 20d18171b3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 390 additions and 43 deletions

View file

@ -22,6 +22,7 @@
"extensionsAny",
"externalUriOpener",
"fileSearchProvider",
"findFiles2",
"findTextInFiles",
"fsChunks",
"interactive",

View file

@ -597,6 +597,45 @@ suite('vscode API - workspace', () => {
});
});
test('`findFiles2`', () => {
return vscode.workspace.findFiles2('*image.png').then((res) => {
assert.strictEqual(res.length, 4);
// TODO: see why this is fuzzy matching
});
});
test('findFiles2 - null exclude', async () => {
await vscode.workspace.findFiles2('**/file.txt', { useDefaultExcludes: true, useDefaultSearchExcludes: false }).then((res) => {
// file.exclude folder is still searched, search.exclude folder is not
assert.strictEqual(res.length, 1);
assert.strictEqual(basename(vscode.workspace.asRelativePath(res[0])), 'file.txt');
});
await vscode.workspace.findFiles2('**/file.txt', { useDefaultExcludes: false, useDefaultSearchExcludes: false }).then((res) => {
// search.exclude and files.exclude folders are both searched
assert.strictEqual(res.length, 2);
assert.strictEqual(basename(vscode.workspace.asRelativePath(res[0])), 'file.txt');
});
});
test('findFiles2, exclude', () => {
return vscode.workspace.findFiles2('*image.png', { exclude: '**/sub/**' }).then((res) => {
assert.strictEqual(res.length, 3);
// TODO: see why this is fuzzy matching
});
});
test('findFiles2, cancellation', () => {
const source = new vscode.CancellationTokenSource();
const token = source.token; // just to get an instance first
source.cancel();
return vscode.workspace.findFiles2('*.js', {}, token).then((res) => {
assert.deepStrictEqual(res, []);
});
});
test('findTextInFiles', async () => {
const options: vscode.FindTextInFilesOptions = {
include: '*.ts',

View file

@ -19,7 +19,7 @@ import { WorkspaceTrustRequestOptions, IWorkspaceTrustManagementService, IWorksp
import { IWorkspace, IWorkspaceContextService, WorkbenchState, isUntitledWorkspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';
import { checkGlobFileExists } from 'vs/workbench/services/extensions/common/workspaceContains';
import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/services/search/common/queryBuilder';
import { IFileQueryBuilderOptions, ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/services/search/common/queryBuilder';
import { IEditorService, ISaveEditorsResult } from 'vs/workbench/services/editor/common/editorService';
import { IFileMatch, IPatternInfo, ISearchProgressItem, ISearchService } from 'vs/workbench/services/search/common/search';
import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing';
@ -140,21 +140,14 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
// --- search ---
$startFileSearch(includePattern: string | null, _includeFolder: UriComponents | null, excludePatternOrDisregardExcludes: string | false | null, maxResults: number | null, token: CancellationToken): Promise<UriComponents[] | null> {
$startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise<UriComponents[] | null> {
const includeFolder = URI.revive(_includeFolder);
const workspace = this._contextService.getWorkspace();
const query = this._queryBuilder.file(
includeFolder ? [includeFolder] : workspace.folders,
{
maxResults: maxResults ?? undefined,
disregardExcludeSettings: (excludePatternOrDisregardExcludes === false) || undefined,
disregardSearchExcludeSettings: true,
disregardIgnoreFiles: true,
includePattern: includePattern ?? undefined,
excludePattern: typeof excludePatternOrDisregardExcludes === 'string' ? excludePatternOrDisregardExcludes : undefined,
_reason: 'startFileSearch'
});
options
);
return this._searchService.fileSearch(query, token).then(result => {
return result.results.map(m => m.resource);

View file

@ -938,6 +938,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
// Note, undefined/null have different meanings on "exclude"
return extHostWorkspace.findFiles(include, exclude, maxResults, extension.identifier, token);
},
findFiles2: (filePattern, options?, token?) => {
return extHostWorkspace.findFiles2(filePattern, options, extension.identifier, token);
},
findTextInFiles: (query: vscode.TextSearchQuery, optionsOrCallback: vscode.FindTextInFilesOptions | ((result: vscode.TextSearchResult) => void), callbackOrToken?: vscode.CancellationToken | ((result: vscode.TextSearchResult) => void), token?: vscode.CancellationToken) => {
checkProposedApiEnabled(extension, 'findTextInFiles');
let options: vscode.FindTextInFilesOptions;

View file

@ -78,7 +78,7 @@ import { Dto, IRPCProtocol, SerializableObjectWithBuffers, createProxyIdentifier
import { ILanguageStatus } from 'vs/workbench/services/languageStatus/common/languageStatusService';
import { OutputChannelUpdateMode } from 'vs/workbench/services/output/common/output';
import { CandidatePort } from 'vs/workbench/services/remote/common/tunnelModel';
import { ITextQueryBuilderOptions } from 'vs/workbench/services/search/common/queryBuilder';
import { IFileQueryBuilderOptions, ITextQueryBuilderOptions } from 'vs/workbench/services/search/common/queryBuilder';
import * as search from 'vs/workbench/services/search/common/search';
import { ISaveProfileResult } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
@ -1341,7 +1341,7 @@ export interface ITextSearchComplete {
}
export interface MainThreadWorkspaceShape extends IDisposable {
$startFileSearch(includePattern: string | null, includeFolder: UriComponents | null, excludePatternOrDisregardExcludes: string | false | null, maxResults: number | null, token: CancellationToken): Promise<UriComponents[] | null>;
$startFileSearch(includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise<UriComponents[] | null>;
$startTextSearch(query: search.IPatternInfo, folder: UriComponents | null, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise<ITextSearchComplete | null>;
$checkExists(folders: readonly UriComponents[], includes: string[], token: CancellationToken): Promise<boolean>;
$save(uri: UriComponents, options: { saveAs: boolean }): Promise<UriComponents | undefined>;

View file

@ -28,7 +28,7 @@ import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { GlobPattern } from 'vs/workbench/api/common/extHostTypeConverters';
import { Range } from 'vs/workbench/api/common/extHostTypes';
import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService';
import { ITextQueryBuilderOptions } from 'vs/workbench/services/search/common/queryBuilder';
import { IFileQueryBuilderOptions, ITextQueryBuilderOptions } from 'vs/workbench/services/search/common/queryBuilder';
import { IRawFileMatch2, ITextSearchResult, resultIsMatch } from 'vs/workbench/services/search/common/search';
import * as vscode from 'vscode';
import { ExtHostWorkspaceShape, IRelativePatternDto, IWorkspaceData, MainContext, MainThreadMessageOptions, MainThreadMessageServiceShape, MainThreadWorkspaceShape } from './extHost.protocol';
@ -446,27 +446,73 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac
findFiles(include: vscode.GlobPattern | undefined, exclude: vscode.GlobPattern | null | undefined, maxResults: number | undefined, extensionId: ExtensionIdentifier, token: vscode.CancellationToken = CancellationToken.None): Promise<vscode.Uri[]> {
this._logService.trace(`extHostWorkspace#findFiles: fileSearch, extension: ${extensionId.value}, entryPoint: findFiles`);
let excludePatternOrDisregardExcludes: string | false | undefined = undefined;
let excludeString: string = '';
let useFileExcludes = true;
if (exclude === null) {
excludePatternOrDisregardExcludes = false;
} else if (exclude) {
useFileExcludes = false;
} else if (exclude !== undefined) {
if (typeof exclude === 'string') {
excludePatternOrDisregardExcludes = exclude;
excludeString = exclude;
} else {
excludePatternOrDisregardExcludes = exclude.pattern;
excludeString = exclude.pattern;
}
}
return this._findFilesImpl(include, undefined, {
exclude: excludeString,
maxResults,
useDefaultExcludes: useFileExcludes,
useDefaultSearchExcludes: false,
useIgnoreFiles: true
}, token);
}
findFiles2(filePattern: vscode.GlobPattern | undefined,
options: vscode.FindFiles2Options = {},
extensionId: ExtensionIdentifier,
token: vscode.CancellationToken = CancellationToken.None): Promise<vscode.Uri[]> {
this._logService.trace(`extHostWorkspace#findFiles2: fileSearch, extension: ${extensionId.value}, entryPoint: findFiles2`);
return this._findFilesImpl(undefined, filePattern, options, token);
}
private async _findFilesImpl(
// the old `findFiles` used `include` to query, but the new `findFiles2` uses `filePattern` to query.
// `filePattern` is the proper way to handle this, since it takes less precedence than the ignore files.
include: vscode.GlobPattern | undefined,
filePattern: vscode.GlobPattern | undefined,
options: vscode.FindFiles2Options,
token: vscode.CancellationToken = CancellationToken.None): Promise<vscode.Uri[]> {
if (token && token.isCancellationRequested) {
return Promise.resolve([]);
}
const { includePattern, folder } = parseSearchInclude(GlobPattern.from(include));
const excludePattern = (typeof options.exclude === 'string') ? options.exclude :
options.exclude ? options.exclude.pattern : undefined;
const fileQueries = <IFileQueryBuilderOptions>{
ignoreSymlinks: typeof options.followSymlinks === 'boolean' ? !options.followSymlinks : undefined,
disregardIgnoreFiles: typeof options.useIgnoreFiles === 'boolean' ? !options.useIgnoreFiles : undefined,
disregardGlobalIgnoreFiles: typeof options.useGlobalIgnoreFiles === 'boolean' ? !options.useGlobalIgnoreFiles : undefined,
disregardParentIgnoreFiles: typeof options.useParentIgnoreFiles === 'boolean' ? !options.useParentIgnoreFiles : undefined,
disregardExcludeSettings: typeof options.useDefaultExcludes === 'boolean' ? !options.useDefaultExcludes : false,
disregardSearchExcludeSettings: typeof options.useDefaultSearchExcludes === 'boolean' ? !options.useDefaultSearchExcludes : false,
maxResults: options.maxResults,
excludePattern: excludePattern,
_reason: 'startFileSearch'
};
let folderToUse: URI | undefined;
if (include) {
const { includePattern, folder } = parseSearchInclude(GlobPattern.from(include));
folderToUse = folder;
fileQueries.includePattern = includePattern;
} else {
const { includePattern, folder } = parseSearchInclude(GlobPattern.from(filePattern));
folderToUse = folder;
fileQueries.filePattern = includePattern;
}
return this._proxy.$startFileSearch(
includePattern ?? null,
folder ?? null,
excludePatternOrDisregardExcludes ?? null,
maxResults ?? null,
folderToUse ?? null,
fileQueries,
token
)
.then(data => Array.isArray(data) ? data.map(d => URI.revive(d)) : []);

View file

@ -18,7 +18,7 @@ import { mock } from 'vs/base/test/common/mock';
import { TestRPCProtocol } from 'vs/workbench/api/test/common/testRPCProtocol';
import { ExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
import { ITextQueryBuilderOptions } from 'vs/workbench/services/search/common/queryBuilder';
import { IFileQueryBuilderOptions, ITextQueryBuilderOptions } from 'vs/workbench/services/search/common/queryBuilder';
import { IPatternInfo } from 'vs/workbench/services/search/common/search';
import { isLinux, isWindows } from 'vs/base/common/platform';
import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo';
@ -583,12 +583,13 @@ suite('ExtHostWorkspace', function () {
let mainThreadCalled = false;
rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock<MainThreadWorkspace>() {
override $startFileSearch(includePattern: string, _includeFolder: UriComponents | null, excludePatternOrDisregardExcludes: string | false, maxResults: number, token: CancellationToken): Promise<URI[] | null> {
override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise<URI[] | null> {
mainThreadCalled = true;
assert.strictEqual(includePattern, 'foo');
assert.strictEqual(options.includePattern, 'foo');
assert.strictEqual(_includeFolder, null);
assert.strictEqual(excludePatternOrDisregardExcludes, null);
assert.strictEqual(maxResults, 10);
assert.strictEqual(options.excludePattern, '');
assert.strictEqual(options.disregardExcludeSettings, false);
assert.strictEqual(options.maxResults, 10);
return Promise.resolve(null);
}
});
@ -605,11 +606,12 @@ suite('ExtHostWorkspace', function () {
let mainThreadCalled = false;
rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock<MainThreadWorkspace>() {
override $startFileSearch(includePattern: string, _includeFolder: UriComponents | null, excludePatternOrDisregardExcludes: string | false, maxResults: number, token: CancellationToken): Promise<URI[] | null> {
override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise<URI[] | null> {
mainThreadCalled = true;
assert.strictEqual(includePattern, 'glob/**');
assert.strictEqual(options.includePattern, 'glob/**');
assert.deepStrictEqual(_includeFolder ? URI.from(_includeFolder).toJSON() : null, URI.file('/other/folder').toJSON());
assert.strictEqual(excludePatternOrDisregardExcludes, null);
assert.strictEqual(options.excludePattern, '');
assert.strictEqual(options.disregardExcludeSettings, false);
return Promise.resolve(null);
}
});
@ -634,11 +636,12 @@ suite('ExtHostWorkspace', function () {
let mainThreadCalled = false;
rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock<MainThreadWorkspace>() {
override $startFileSearch(includePattern: string, _includeFolder: UriComponents | null, excludePatternOrDisregardExcludes: string | false, maxResults: number, token: CancellationToken): Promise<URI[] | null> {
override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise<URI[] | null> {
mainThreadCalled = true;
assert.strictEqual(includePattern, 'glob/**');
assert.strictEqual(options.includePattern, 'glob/**');
assert.deepStrictEqual(URI.revive(_includeFolder!).toString(), URI.file('/other/folder').toString());
assert.strictEqual(excludePatternOrDisregardExcludes, false);
assert.strictEqual(options.excludePattern, '');
assert.strictEqual(options.disregardExcludeSettings, true);
return Promise.resolve(null);
}
});
@ -655,7 +658,7 @@ suite('ExtHostWorkspace', function () {
let mainThreadCalled = false;
rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock<MainThreadWorkspace>() {
override $startFileSearch(includePattern: string, _includeFolder: UriComponents | null, excludePatternOrDisregardExcludes: string | false, maxResults: number, token: CancellationToken): Promise<URI[] | null> {
override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise<URI[] | null> {
mainThreadCalled = true;
return Promise.resolve(null);
}
@ -675,9 +678,10 @@ suite('ExtHostWorkspace', function () {
let mainThreadCalled = false;
rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock<MainThreadWorkspace>() {
override $startFileSearch(includePattern: string, _includeFolder: UriComponents | null, excludePatternOrDisregardExcludes: string | false, maxResults: number, token: CancellationToken): Promise<URI[] | null> {
override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise<URI[] | null> {
mainThreadCalled = true;
assert(excludePatternOrDisregardExcludes, 'glob/**'); // Note that the base portion is ignored, see #52651
assert.strictEqual(options.disregardExcludeSettings, false);
assert.strictEqual(options.excludePattern, 'glob/**'); // Note that the base portion is ignored, see #52651
return Promise.resolve(null);
}
});
@ -687,6 +691,163 @@ suite('ExtHostWorkspace', function () {
assert(mainThreadCalled, 'mainThreadCalled');
});
});
test('findFiles2 - string include', () => {
const root = '/project/foo';
const rpcProtocol = new TestRPCProtocol();
let mainThreadCalled = false;
rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock<MainThreadWorkspace>() {
override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise<URI[] | null> {
mainThreadCalled = true;
assert.strictEqual(options.filePattern, 'foo');
assert.strictEqual(options.includePattern, undefined);
assert.strictEqual(_includeFolder, null);
assert.strictEqual(options.excludePattern, undefined);
assert.strictEqual(options.disregardExcludeSettings, false);
assert.strictEqual(options.maxResults, 10);
return Promise.resolve(null);
}
});
const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService());
return ws.findFiles2('foo', { maxResults: 10, useDefaultExcludes: true }, new ExtensionIdentifier('test')).then(() => {
assert(mainThreadCalled, 'mainThreadCalled');
});
});
function testFindFiles2Include(pattern: RelativePattern) {
const root = '/project/foo';
const rpcProtocol = new TestRPCProtocol();
let mainThreadCalled = false;
rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock<MainThreadWorkspace>() {
override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise<URI[] | null> {
mainThreadCalled = true;
assert.strictEqual(options.filePattern, 'glob/**');
assert.strictEqual(options.includePattern, undefined);
assert.deepStrictEqual(_includeFolder ? URI.from(_includeFolder).toJSON() : null, URI.file('/other/folder').toJSON());
assert.strictEqual(options.excludePattern, undefined);
assert.strictEqual(options.disregardExcludeSettings, false);
return Promise.resolve(null);
}
});
const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService());
return ws.findFiles2(pattern, { maxResults: 10 }, new ExtensionIdentifier('test')).then(() => {
assert(mainThreadCalled, 'mainThreadCalled');
});
}
test('findFiles2 - RelativePattern include (string)', () => {
return testFindFiles2Include(new RelativePattern('/other/folder', 'glob/**'));
});
test('findFiles2 - RelativePattern include (URI)', () => {
return testFindFiles2Include(new RelativePattern(URI.file('/other/folder'), 'glob/**'));
});
test('findFiles2 - no excludes', () => {
const root = '/project/foo';
const rpcProtocol = new TestRPCProtocol();
let mainThreadCalled = false;
rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock<MainThreadWorkspace>() {
override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise<URI[] | null> {
mainThreadCalled = true;
assert.strictEqual(options.filePattern, 'glob/**');
assert.strictEqual(options.includePattern, undefined);
assert.deepStrictEqual(URI.revive(_includeFolder!).toString(), URI.file('/other/folder').toString());
assert.strictEqual(options.excludePattern, undefined);
assert.strictEqual(options.disregardExcludeSettings, false);
return Promise.resolve(null);
}
});
const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService());
return ws.findFiles2(new RelativePattern('/other/folder', 'glob/**'), {}, new ExtensionIdentifier('test')).then(() => {
assert(mainThreadCalled, 'mainThreadCalled');
});
});
test('findFiles2 - with cancelled token', () => {
const root = '/project/foo';
const rpcProtocol = new TestRPCProtocol();
let mainThreadCalled = false;
rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock<MainThreadWorkspace>() {
override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise<URI[] | null> {
mainThreadCalled = true;
return Promise.resolve(null);
}
});
const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService());
const token = CancellationToken.Cancelled;
return ws.findFiles2(new RelativePattern('/other/folder', 'glob/**'), {}, new ExtensionIdentifier('test'), token).then(() => {
assert(!mainThreadCalled, '!mainThreadCalled');
});
});
test('findFiles2 - RelativePattern exclude', () => {
const root = '/project/foo';
const rpcProtocol = new TestRPCProtocol();
let mainThreadCalled = false;
rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock<MainThreadWorkspace>() {
override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise<URI[] | null> {
mainThreadCalled = true;
assert.strictEqual(options.disregardExcludeSettings, false);
assert.strictEqual(options.excludePattern, 'glob/**'); // Note that the base portion is ignored, see #52651
return Promise.resolve(null);
}
});
const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService());
return ws.findFiles2('', { exclude: new RelativePattern(root, 'glob/**') }, new ExtensionIdentifier('test')).then(() => {
assert(mainThreadCalled, 'mainThreadCalled');
});
});
test('findFiles2 - useIgnoreFiles', () => {
const root = '/project/foo';
const rpcProtocol = new TestRPCProtocol();
let mainThreadCalled = false;
rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock<MainThreadWorkspace>() {
override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise<URI[] | null> {
mainThreadCalled = true;
assert.strictEqual(options.disregardExcludeSettings, false);
assert.strictEqual(options.disregardIgnoreFiles, false);
assert.strictEqual(options.disregardGlobalIgnoreFiles, false);
assert.strictEqual(options.disregardParentIgnoreFiles, false);
return Promise.resolve(null);
}
});
const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService());
return ws.findFiles2('', { useIgnoreFiles: true, useParentIgnoreFiles: true, useGlobalIgnoreFiles: true }, new ExtensionIdentifier('test')).then(() => {
assert(mainThreadCalled, 'mainThreadCalled');
});
});
test('findFiles2 - use symlinks', () => {
const root = '/project/foo';
const rpcProtocol = new TestRPCProtocol();
let mainThreadCalled = false;
rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock<MainThreadWorkspace>() {
override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise<URI[] | null> {
mainThreadCalled = true;
assert.strictEqual(options.ignoreSymlinks, false);
return Promise.resolve(null);
}
});
const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService());
return ws.findFiles2('', { followSymlinks: true }, new ExtensionIdentifier('test')).then(() => {
assert(mainThreadCalled, 'mainThreadCalled');
});
});
test('findTextInFiles - no include', async () => {
const root = '/project/foo';

View file

@ -41,7 +41,7 @@ suite('MainThreadWorkspace', () => {
});
const mtw = disposables.add(instantiationService.createInstance(MainThreadWorkspace, SingleProxyRPCProtocol({ $initializeWorkspace: () => { } })));
return mtw.$startFileSearch('foo', null, null, 10, new CancellationTokenSource().token);
return mtw.$startFileSearch(null, { maxResults: 10, includePattern: 'foo', disregardSearchExcludeSettings: true }, new CancellationTokenSource().token);
});
test('exclude defaults', () => {
@ -63,7 +63,7 @@ suite('MainThreadWorkspace', () => {
});
const mtw = disposables.add(instantiationService.createInstance(MainThreadWorkspace, SingleProxyRPCProtocol({ $initializeWorkspace: () => { } })));
return mtw.$startFileSearch('', null, null, 10, new CancellationTokenSource().token);
return mtw.$startFileSearch(null, { maxResults: 10, includePattern: '', disregardSearchExcludeSettings: true }, new CancellationTokenSource().token);
});
test('disregard excludes', () => {
@ -76,7 +76,7 @@ suite('MainThreadWorkspace', () => {
instantiationService.stub(ISearchService, {
fileSearch(query: IFileQuery) {
assert.strictEqual(query.folderQueries[0].excludePattern, undefined);
assert.deepStrictEqual(query.folderQueries[0].excludePattern, undefined);
assert.deepStrictEqual(query.excludePattern, undefined);
return Promise.resolve({ results: [], messages: [] });
@ -84,7 +84,29 @@ suite('MainThreadWorkspace', () => {
});
const mtw = disposables.add(instantiationService.createInstance(MainThreadWorkspace, SingleProxyRPCProtocol({ $initializeWorkspace: () => { } })));
return mtw.$startFileSearch('', null, false, 10, new CancellationTokenSource().token);
return mtw.$startFileSearch(null, { maxResults: 10, includePattern: '', disregardSearchExcludeSettings: true, disregardExcludeSettings: true }, new CancellationTokenSource().token);
});
test('do not disregard anything if disregardExcludeSettings is true', () => {
configService.setUserConfiguration('search', {
'exclude': { 'searchExclude': true }
});
configService.setUserConfiguration('files', {
'exclude': { 'filesExclude': true }
});
instantiationService.stub(ISearchService, {
fileSearch(query: IFileQuery) {
assert.strictEqual(query.folderQueries.length, 1);
assert.strictEqual(query.folderQueries[0].disregardIgnoreFiles, true);
assert.deepStrictEqual(query.folderQueries[0].excludePattern, undefined);
return Promise.resolve({ results: [], messages: [] });
}
});
const mtw = disposables.add(instantiationService.createInstance(MainThreadWorkspace, SingleProxyRPCProtocol({ $initializeWorkspace: () => { } })));
return mtw.$startFileSearch(null, { maxResults: 10, includePattern: '', disregardExcludeSettings: true, disregardSearchExcludeSettings: false }, new CancellationTokenSource().token);
});
test('exclude string', () => {
@ -98,6 +120,6 @@ suite('MainThreadWorkspace', () => {
});
const mtw = disposables.add(instantiationService.createInstance(MainThreadWorkspace, SingleProxyRPCProtocol({ $initializeWorkspace: () => { } })));
return mtw.$startFileSearch('', null, 'exclude/**', 10, new CancellationTokenSource().token);
return mtw.$startFileSearch(null, { maxResults: 10, includePattern: '', excludePattern: 'exclude/**', disregardSearchExcludeSettings: true }, new CancellationTokenSource().token);
});
});

View file

@ -58,6 +58,7 @@ export const allApiProposals = Object.freeze({
externalUriOpener: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.externalUriOpener.d.ts',
fileComments: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.fileComments.d.ts',
fileSearchProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.fileSearchProvider.d.ts',
findFiles2: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.findFiles2.d.ts',
findTextInFiles: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.findTextInFiles.d.ts',
fsChunks: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.fsChunks.d.ts',
handleIssueUri: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.handleIssueUri.d.ts',

View file

@ -0,0 +1,81 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
declare module 'vscode' {
export interface FindFiles2Options {
// note: this is just FindTextInFilesOptions without select properties (include, previewOptions, beforeContext, afterContext)
/**
* A {@link GlobPattern glob pattern} that defines files and folders to exclude. The glob pattern
* will be matched against the file paths of resulting matches relative to their workspace. When `undefined`, default excludes will
* apply.
*/
exclude?: GlobPattern;
/**
* Whether to use the values for files.exclude. Defaults to true.
*/
useDefaultExcludes?: boolean;
/**
* Whether to use the values for search.exclude. Defaults to true. Will not be followed if `useDefaultExcludes` is set to `false`.
*/
useDefaultSearchExcludes?: boolean;
/**
* The maximum number of results to search for
*/
maxResults?: number;
/**
* Whether external files that exclude files, like .gitignore, should be respected.
* See the vscode setting `"search.useIgnoreFiles"`.
*/
useIgnoreFiles?: boolean;
/**
* Whether global files that exclude files, like .gitignore, should be respected.
* Must set `useIgnoreFiles` to `true` to use this.
* See the vscode setting `"search.useGlobalIgnoreFiles"`.
*/
useGlobalIgnoreFiles?: boolean;
/**
* Whether files in parent directories that exclude files, like .gitignore, should be respected.
* Must set `useIgnoreFiles` to `true` to use this.
* See the vscode setting `"search.useParentIgnoreFiles"`.
*/
useParentIgnoreFiles?: boolean;
/**
* Whether symlinks should be followed while searching.
* See the vscode setting `"search.followSymlinks"`.
*/
followSymlinks?: boolean;
}
/**
* Represents a session of a currently logged in user.
*/
export namespace workspace {
/**
* Find files across all {@link workspace.workspaceFolders workspace folders} in the workspace.
*
* @example
* findFiles('**/*.js', {exclude: '**/out/**', useIgnoreFiles: true}, 10)
*
* @param filePattern A {@link GlobPattern glob pattern} that defines the files to search for. The glob pattern
* will be matched against the file paths of resulting matches relative to their workspace. Use a {@link RelativePattern relative pattern}
* to restrict the search results to a {@link WorkspaceFolder workspace folder}.
* @param options A set of {@link FindFiles2Options FindFiles2Options} that defines where and how to search (e.g. exclude settings).
* If enabled, any ignore files will take prescendence over any files found in the `filePattern` parameter.
* @param token A token that can be used to signal cancellation to the underlying search engine.
* @returns A thenable that resolves to an array of resource identifiers. Will return no results if no
* {@link workspace.workspaceFolders workspace folders} are opened.
*/
export function findFiles2(filePattern: GlobPattern, options?: FindFiles2Options, token?: CancellationToken): Thenable<Uri[]>;
}
}