diff --git a/src/vs/workbench/api/browser/mainThreadWorkspace.ts b/src/vs/workbench/api/browser/mainThreadWorkspace.ts index 910e1342416..1f2e2807b7a 100644 --- a/src/vs/workbench/api/browser/mainThreadWorkspace.ts +++ b/src/vs/workbench/api/browser/mainThreadWorkspace.ts @@ -141,7 +141,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { // --- search --- - $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { + $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { const includeFolder = URI.revive(_includeFolder); const workspace = this._contextService.getWorkspace(); @@ -160,7 +160,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { }); } - $startTextSearch(pattern: IPatternInfo, _folder: UriComponents | null, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise { + $startTextSearch(pattern: IPatternInfo, _folder: UriComponents | null, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise { const folder = URI.revive(_folder); const workspace = this._contextService.getWorkspace(); const folders = folder ? [folder] : workspace.folders.map(folder => folder.uri); diff --git a/src/vs/workbench/api/common/extHostWorkspace.ts b/src/vs/workbench/api/common/extHostWorkspace.ts index 6afbf57de40..abb49a3cc27 100644 --- a/src/vs/workbench/api/common/extHostWorkspace.ts +++ b/src/vs/workbench/api/common/extHostWorkspace.ts @@ -987,9 +987,9 @@ interface IExtensionListener { (e: E): any; } -function globsToISearchPatternBuilder(excludes: vscode.GlobPattern[] | undefined): ISearchPatternBuilder[] { +function globsToISearchPatternBuilder(excludes: vscode.GlobPattern[] | undefined): ISearchPatternBuilder[] { return ( - excludes?.map((exclude): ISearchPatternBuilder | undefined => { + excludes?.map((exclude): ISearchPatternBuilder | undefined => { if (typeof exclude === 'string') { if (exclude === '') { return undefined; @@ -997,7 +997,7 @@ function globsToISearchPatternBuilder(excludes: vscode.GlobPattern[] | undefined return { pattern: exclude, uri: undefined - } satisfies ISearchPatternBuilder; + } satisfies ISearchPatternBuilder; } else { const parsedExclude = parseSearchExcludeInclude(exclude); if (!parsedExclude) { @@ -1006,8 +1006,8 @@ function globsToISearchPatternBuilder(excludes: vscode.GlobPattern[] | undefined return { pattern: parsedExclude.pattern, uri: parsedExclude.folder - } satisfies ISearchPatternBuilder; + } satisfies ISearchPatternBuilder; } }) ?? [] - ).filter((e): e is ISearchPatternBuilder => !!e); + ).filter((e): e is ISearchPatternBuilder => !!e); } diff --git a/src/vs/workbench/api/test/browser/mainThreadWorkspace.test.ts b/src/vs/workbench/api/test/browser/mainThreadWorkspace.test.ts index be9618f4672..2199a99d557 100644 --- a/src/vs/workbench/api/test/browser/mainThreadWorkspace.test.ts +++ b/src/vs/workbench/api/test/browser/mainThreadWorkspace.test.ts @@ -13,6 +13,7 @@ import { MainThreadWorkspace } from '../../browser/mainThreadWorkspace.js'; import { SingleProxyRPCProtocol } from '../common/testRPCProtocol.js'; import { IFileQuery, ISearchService } from '../../../services/search/common/search.js'; import { workbenchInstantiationService } from '../../../test/browser/workbenchTestServices.js'; +import { URI, UriComponents } from '../../../../base/common/uri.js'; suite('MainThreadWorkspace', () => { const disposables = ensureNoDisposablesAreLeakedInTestSuite(); @@ -123,4 +124,23 @@ suite('MainThreadWorkspace', () => { const mtw = disposables.add(instantiationService.createInstance(MainThreadWorkspace, SingleProxyRPCProtocol({ $initializeWorkspace: () => { } }))); return mtw.$startFileSearch(null, { maxResults: 10, includePattern: '', excludePattern: [{ pattern: 'exclude/**' }], disregardSearchExcludeSettings: true }, CancellationToken.None); }); + test('Valid revived URI after moving to EH', () => { + const uriComponents: UriComponents = { + scheme: 'test', + path: '/Users/username/Downloads', + }; + instantiationService.stub(ISearchService, { + fileSearch(query: IFileQuery) { + assert.strictEqual(query.folderQueries?.length, 1); + assert.ok(URI.isUri(query.folderQueries[0].folder)); + assert.strictEqual(query.folderQueries[0].folder.path, '/Users/username/Downloads'); + assert.strictEqual(query.folderQueries[0].folder.scheme, 'test'); + + return Promise.resolve({ results: [], messages: [] }); + } + }); + + const mtw = disposables.add(instantiationService.createInstance(MainThreadWorkspace, SingleProxyRPCProtocol({ $initializeWorkspace: () => { } }))); + return mtw.$startFileSearch(uriComponents, { filePattern: '*.md' }, CancellationToken.None); + }); }); diff --git a/src/vs/workbench/api/test/node/extHostSearch.test.ts b/src/vs/workbench/api/test/node/extHostSearch.test.ts index 90cba26ee70..75e6b2f34c6 100644 --- a/src/vs/workbench/api/test/node/extHostSearch.test.ts +++ b/src/vs/workbench/api/test/node/extHostSearch.test.ts @@ -263,6 +263,28 @@ suite('ExtHostSearch', () => { assert(!results.length); }); + test('session cancellation should work', async () => { + let numSessionCancelled = 0; + const disposables: (vscode.Disposable | undefined)[] = []; + await registerTestFileSearchProvider({ + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, token: vscode.CancellationToken): Promise { + + disposables.push(options.session?.onCancellationRequested(() => { + numSessionCancelled++; + })); + + return Promise.resolve([]); + } + }); + + + await runFileSearch({ ...getSimpleQuery(), cacheKey: '1' }, true); + await runFileSearch({ ...getSimpleQuery(), cacheKey: '2' }, true); + extHostSearch.$clearCache('1'); + assert.strictEqual(numSessionCancelled, 1); + disposables.forEach(d => d?.dispose()); + }); + test('provider returns null', async () => { await registerTestFileSearchProvider({ provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, token: vscode.CancellationToken): Promise { @@ -702,6 +724,24 @@ suite('ExtHostSearch', () => { const { results } = await runFileSearch(query); compareURIs(results, reportedResults); }); + test('if onlyFileScheme is set, do not call custom schemes', async () => { + let fancySchemeCalled = false; + await registerTestFileSearchProvider({ + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, token: vscode.CancellationToken): Promise { + fancySchemeCalled = true; + return Promise.resolve([]); + } + }, fancyScheme); + + const query: ISearchQuery = { + type: QueryType.File, + filePattern: '', + folderQueries: [] + }; + + await runFileSearch(query); + assert(!fancySchemeCalled); + }); }); suite('Text:', () => { diff --git a/src/vs/workbench/services/search/common/fileSearchManager.ts b/src/vs/workbench/services/search/common/fileSearchManager.ts index 3358bd56415..e195f31bde8 100644 --- a/src/vs/workbench/services/search/common/fileSearchManager.ts +++ b/src/vs/workbench/services/search/common/fileSearchManager.ts @@ -13,7 +13,6 @@ import { URI } from '../../../../base/common/uri.js'; import { IFileMatch, IFileSearchProviderStats, IFolderQuery, ISearchCompleteStats, IFileQuery, QueryGlobTester, resolvePatternsForProvider, hasSiblingFn, excludeToGlobPattern, DEFAULT_MAX_SEARCH_RESULTS } from './search.js'; import { FileSearchProviderFolderOptions, FileSearchProviderNew, FileSearchProviderOptions } from './searchExtTypes.js'; import { TernarySearchTree } from '../../../../base/common/ternarySearchTree.js'; -import { Disposable } from '../../../../base/common/lifecycle.js'; import { OldFileSearchProviderConverter } from './searchExtConversionTypes.js'; interface IInternalFileMatch { @@ -307,19 +306,26 @@ interface IInternalSearchComplete { /** * For backwards compatibility, store both a cancellation token and a session object. The session object is the new implementation, where */ -class SessionLifecycle extends Disposable { - public readonly obj: object; +class SessionLifecycle { + private _obj: object | undefined; public readonly tokenSource: CancellationTokenSource; constructor() { - super(); - this.obj = new Object(); + this._obj = new Object(); this.tokenSource = new CancellationTokenSource(); } - public override dispose(): void { + public get obj() { + if (this._obj) { + return this._obj; + } + + throw new Error('Session object has been dereferenced.'); + } + + cancel() { this.tokenSource.cancel(); - super.dispose(); + this._obj = undefined; // dereference } } @@ -356,7 +362,7 @@ export class FileSearchManager { clearCache(cacheKey: string): void { // cancel the token - this.sessions.get(cacheKey)?.dispose(); + this.sessions.get(cacheKey)?.cancel(); // with no reference to this, it will be removed from WeakMaps this.sessions.delete(cacheKey); } diff --git a/src/vs/workbench/services/search/common/queryBuilder.ts b/src/vs/workbench/services/search/common/queryBuilder.ts index 339f41ca9f3..995bf7cf919 100644 --- a/src/vs/workbench/services/search/common/queryBuilder.ts +++ b/src/vs/workbench/services/search/common/queryBuilder.ts @@ -13,7 +13,7 @@ import * as path from '../../../../base/common/path.js'; import { isEqual, basename, relativePath, isAbsolutePath } from '../../../../base/common/resources.js'; import * as strings from '../../../../base/common/strings.js'; import { assertIsDefined, isDefined } from '../../../../base/common/types.js'; -import { URI, URI as uri } from '../../../../base/common/uri.js'; +import { URI, URI as uri, UriComponents } from '../../../../base/common/uri.js'; import { isMultilineRegexSource } from '../../../../editor/common/model/textModelSearch.js'; import * as nls from '../../../../nls.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; @@ -43,16 +43,16 @@ export interface ISearchPathPattern { type ISearchPathPatternBuilder = string | string[]; -export interface ISearchPatternBuilder { - uri?: uri; +export interface ISearchPatternBuilder { + uri?: U; pattern: ISearchPathPatternBuilder; } -export function isISearchPatternBuilder(object: ISearchPatternBuilder | ISearchPathPatternBuilder): object is ISearchPatternBuilder { +export function isISearchPatternBuilder(object: ISearchPatternBuilder | ISearchPathPatternBuilder): object is ISearchPatternBuilder { return (typeof object === 'object' && 'uri' in object && 'pattern' in object); } -export function globPatternToISearchPatternBuilder(globPattern: GlobPattern): ISearchPatternBuilder { +export function globPatternToISearchPatternBuilder(globPattern: GlobPattern): ISearchPatternBuilder { if (typeof globPattern === 'string') { return { @@ -74,11 +74,11 @@ export interface ISearchPathsInfo { pattern?: glob.IExpression; } -interface ICommonQueryBuilderOptions { +interface ICommonQueryBuilderOptions { _reason?: string; - excludePattern?: ISearchPatternBuilder[]; + excludePattern?: ISearchPatternBuilder[]; includePattern?: ISearchPathPatternBuilder; - extraFileResources?: uri[]; + extraFileResources?: U[]; /** Parse the special ./ syntax supported by the searchview, and expand foo to ** /foo */ expandPatterns?: boolean; @@ -95,7 +95,7 @@ interface ICommonQueryBuilderOptions { onlyFileScheme?: boolean; } -export interface IFileQueryBuilderOptions extends ICommonQueryBuilderOptions { +export interface IFileQueryBuilderOptions extends ICommonQueryBuilderOptions { filePattern?: string; exists?: boolean; sortByScore?: boolean; @@ -103,7 +103,7 @@ export interface IFileQueryBuilderOptions extends ICommonQueryBuilderOptions { shouldGlobSearch?: boolean; } -export interface ITextQueryBuilderOptions extends ICommonQueryBuilderOptions { +export interface ITextQueryBuilderOptions extends ICommonQueryBuilderOptions { previewOptions?: ITextSearchPreviewOptions; fileEncoding?: string; surroundingContext?: number;