diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 52532c7bb16..883943af327 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { AsyncIterableObject } from 'vs/base/common/async'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import * as errors from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; @@ -107,7 +106,6 @@ import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/c import { UIKind } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; import { checkProposedApiEnabled, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import { ProxyIdentifier } from 'vs/workbench/services/extensions/common/proxyIdentifier'; -import { oldToNewTextSearchResult } from 'vs/workbench/services/search/common/searchExtConversionTypes'; import { ExcludeSettingOptions, TextSearchCompleteMessageType, TextSearchContextNew, TextSearchMatchNew } from 'vs/workbench/services/search/common/searchExtTypes'; import type * as vscode from 'vscode'; @@ -999,56 +997,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I findTextInFilesNew: (query: vscode.TextSearchQueryNew, options?: vscode.FindTextInFilesOptionsNew, token?: vscode.CancellationToken): vscode.FindTextInFilesResponse => { checkProposedApiEnabled(extension, 'findTextInFilesNew'); checkProposedApiEnabled(extension, 'textSearchProviderNew'); - let oldOptions = {}; - - - if (options) { - oldOptions = { - include: options.include && options.include.length > 0 ? options.include[0] : undefined, - exclude: options.exclude && options.exclude.length > 0 ? options.exclude[0] : undefined, - useDefaultExcludes: options.useExcludeSettings === undefined || (options.useExcludeSettings === ExcludeSettingOptions.FilesExclude || options.useExcludeSettings === ExcludeSettingOptions.SearchAndFilesExclude), - useSearchExclude: options.useExcludeSettings === undefined || (options.useExcludeSettings === ExcludeSettingOptions.SearchAndFilesExclude), - maxResults: options.maxResults, - useIgnoreFiles: options.useIgnoreFiles?.local, - useGlobalIgnoreFiles: options.useIgnoreFiles?.global, - useParentIgnoreFiles: options.useIgnoreFiles?.parent, - followSymlinks: options.followSymlinks, - encoding: options.encoding, - previewOptions: options.previewOptions ? { - matchLines: options.previewOptions?.numMatchLines ?? 100, - charsPerLine: options.previewOptions?.charsPerLine ?? 10000, - } : undefined, - beforeContext: options.surroundingContext, - afterContext: options.surroundingContext, - } satisfies vscode.FindTextInFilesOptions & { useSearchExclude?: boolean }; - } - - const complete: Promise = Promise.resolve(undefined); - - const asyncIterable = new AsyncIterableObject(async emitter => { - const callback = async (result: vscode.TextSearchResult) => { - emitter.emitOne(oldToNewTextSearchResult(result)); - return result; - }; - await complete.then(e => { - return extHostWorkspace.findTextInFiles( - query, - oldOptions, - callback, - extension.identifier, - token - ); - }); - }); - - return { - results: asyncIterable, - complete: complete.then((e) => { - return { - limitHit: e?.limitHit ?? false - }; - }), - }; + return extHostWorkspace.findTextInFilesNew(query, extension.identifier, options || {}, token); }, save: (uri) => { return extHostWorkspace.save(uri); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index f03af6230ba..c3250a3a8aa 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -81,6 +81,7 @@ import { OutputChannelUpdateMode } from 'vs/workbench/services/output/common/out import { CandidatePort } from 'vs/workbench/services/remote/common/tunnelModel'; import { IFileQueryBuilderOptions, ITextQueryBuilderOptions } from 'vs/workbench/services/search/common/queryBuilder'; import * as search from 'vs/workbench/services/search/common/search'; +import { TextSearchCompleteMessage } from 'vs/workbench/services/search/common/searchExtTypes'; import { ISaveProfileResult } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import type { TerminalShellExecutionCommandLineConfidence } from 'vscode'; @@ -1390,6 +1391,7 @@ export interface ExtHostProfileContentHandlersShape { export interface ITextSearchComplete { limitHit?: boolean; + message?: TextSearchCompleteMessage | TextSearchCompleteMessage[]; } export interface MainThreadWorkspaceShape extends IDisposable { diff --git a/src/vs/workbench/api/common/extHostWorkspace.ts b/src/vs/workbench/api/common/extHostWorkspace.ts index 7de20e1b155..a5f2643d3c1 100644 --- a/src/vs/workbench/api/common/extHostWorkspace.ts +++ b/src/vs/workbench/api/common/extHostWorkspace.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { delta as arrayDelta, mapArrayOrNot } from 'vs/base/common/arrays'; -import { Barrier } from 'vs/base/common/async'; +import { AsyncIterableObject, Barrier } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { AsyncEmitter, Emitter, Event } from 'vs/base/common/event'; import { toDisposable } from 'vs/base/common/lifecycle'; @@ -30,10 +30,11 @@ import { Range } from 'vs/workbench/api/common/extHostTypes'; import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService'; 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 type * as vscode from 'vscode'; import { ExtHostWorkspaceShape, IRelativePatternDto, IWorkspaceData, MainContext, MainThreadMessageOptions, MainThreadMessageServiceShape, MainThreadWorkspaceShape } from './extHost.protocol'; import { revive } from 'vs/base/common/marshalling'; import { AuthInfo, Credentials } from 'vs/platform/request/common/request'; +import { ExcludeSettingOptions, TextSearchContextNew, TextSearchMatchNew } from 'vs/workbench/services/search/common/searchExtTypes'; export interface IExtHostWorkspaceProvider { getWorkspaceFolder2(uri: vscode.Uri, resolveParent?: boolean): Promise; @@ -78,6 +79,11 @@ interface MutableWorkspaceFolder extends vscode.WorkspaceFolder { index: number; } +interface QueryOptions { + options: ITextQueryBuilderOptions; + folder: URI | undefined; +} + class ExtHostWorkspaceImpl extends Workspace { static toExtHostWorkspace(data: IWorkspaceData | null, previousConfirmedWorkspace: ExtHostWorkspaceImpl | undefined, previousUnconfirmedWorkspace: ExtHostWorkspaceImpl | undefined, extHostFileSystemInfo: IExtHostFileSystemInfo): { workspace: ExtHostWorkspaceImpl | null; added: vscode.WorkspaceFolder[]; removed: vscode.WorkspaceFolder[] } { @@ -521,12 +527,126 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac ) .then(data => Array.isArray(data) ? data.map(d => URI.revive(d)) : []); } + findTextInFilesNew(query: vscode.TextSearchQueryNew, extensionId: ExtensionIdentifier, options?: vscode.FindTextInFilesOptionsNew, token?: vscode.CancellationToken): vscode.FindTextInFilesResponse { + this._logService.trace(`extHostWorkspace#findTextInFilesNew: textSearch, extension: ${extensionId.value}, entryPoint: findTextInFilesNew`); + const queryOptions: QueryOptions[] | undefined = options?.include?.map((include) => { + const { includePattern, folder } = parseSearchInclude(GlobPattern.from(include)); + return { + options: { + + ignoreSymlinks: typeof options.followSymlinks === 'boolean' ? !options.followSymlinks : undefined, + disregardIgnoreFiles: typeof options.useIgnoreFiles === 'boolean' ? !options.useIgnoreFiles : undefined, + disregardGlobalIgnoreFiles: typeof options.useIgnoreFiles?.global === 'boolean' ? !options.useIgnoreFiles?.global : undefined, + disregardParentIgnoreFiles: typeof options.useIgnoreFiles?.parent === 'boolean' ? !options.useIgnoreFiles?.parent : undefined, + disregardExcludeSettings: options.useExcludeSettings !== undefined && options.useExcludeSettings === ExcludeSettingOptions.None, + disregardSearchExcludeSettings: options.useExcludeSettings !== undefined && (options.useExcludeSettings !== ExcludeSettingOptions.SearchAndFilesExclude), + fileEncoding: options.encoding, + maxResults: options.maxResults, + previewOptions: options.previewOptions ? { + matchLines: options.previewOptions?.numMatchLines ?? 100, + charsPerLine: options.previewOptions?.charsPerLine ?? 10000, + } : undefined, + surroundingContext: options.surroundingContext, + + includePattern: includePattern, + excludePattern: options.exclude && options.exclude.length > 0 ? options.exclude[0] : undefined, // todo: support multiple excludes + } satisfies ITextQueryBuilderOptions, + folder + } satisfies QueryOptions; + }); + + const complete: Promise = Promise.resolve(undefined); + + const asyncIterable = new AsyncIterableObject(async emitter => { + const progress = (result: ITextSearchResult, uri: URI) => { + if (resultIsMatch(result)) { + emitter.emitOne(new TextSearchMatchNew( + uri, + result.rangeLocations.map((range) => ({ + previewRange: new Range(range.preview.startLineNumber, range.preview.startColumn, range.preview.endLineNumber, range.preview.endColumn), + sourceRange: new Range(range.source.startLineNumber, range.source.startColumn, range.source.endLineNumber, range.source.endColumn) + })), + result.previewText + + )); + } else { + emitter.emitOne(new TextSearchContextNew( + uri, + result.text, + result.lineNumber + )); + + } + return result; + }; + + await complete.then(e => { + return this.findTextInFilesBase( + query, + queryOptions, + progress, + token + ); + }); + }); + + return { + results: asyncIterable, + complete: complete.then((e) => { + return { + limitHit: e?.limitHit ?? false + }; + }), + }; + } + + + async findTextInFilesBase(query: vscode.TextSearchQuery, queryOptions: QueryOptions[] | undefined, callback: (result: ITextSearchResult, uri: URI) => void, token: vscode.CancellationToken = CancellationToken.None): Promise { + const requestId = this._requestIdProvider.getNext(); + + const isCanceled = false; + + this._activeSearchCallbacks[requestId] = p => { + if (isCanceled) { + return; + } + + const uri = URI.revive(p.resource); + p.results!.forEach(rawResult => { + const result: ITextSearchResult = revive(rawResult); + callback(result, uri); + }); + }; + + if (token.isCancellationRequested) { + return {}; + } + + try { + const result = await Promise.all(queryOptions?.map(option => this._proxy.$startTextSearch( + query, + option.folder ?? null, + option.options, + requestId, + token) || {} + ) ?? []); + delete this._activeSearchCallbacks[requestId]; + return result.reduce((acc, val) => { + return { + limitHit: acc?.limitHit || (val?.limitHit ?? false), + message: [acc?.message ?? [], val?.message ?? []].flat(), + }; + }, {}) ?? { limitHit: false }; + + } catch (err) { + delete this._activeSearchCallbacks[requestId]; + throw err; + } + } async findTextInFiles(query: vscode.TextSearchQuery, options: vscode.FindTextInFilesOptions & { useSearchExclude?: boolean }, callback: (result: vscode.TextSearchResult) => void, extensionId: ExtensionIdentifier, token: vscode.CancellationToken = CancellationToken.None): Promise { this._logService.trace(`extHostWorkspace#findTextInFiles: textSearch, extension: ${extensionId.value}, entryPoint: findTextInFiles`); - const requestId = this._requestIdProvider.getNext(); - const previewOptions: vscode.TextSearchPreviewOptions = typeof options.previewOptions === 'undefined' ? { matchLines: 100, @@ -553,56 +673,30 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac excludePattern: excludePattern }; - const isCanceled = false; - - this._activeSearchCallbacks[requestId] = p => { - if (isCanceled) { - return; - } - - const uri = URI.revive(p.resource); - p.results!.forEach(rawResult => { - const result: ITextSearchResult = revive(rawResult); - if (resultIsMatch(result)) { - callback({ - uri, - preview: { - text: result.previewText, - matches: mapArrayOrNot( - result.rangeLocations, - m => new Range(m.preview.startLineNumber, m.preview.startColumn, m.preview.endLineNumber, m.preview.endColumn)) - }, - ranges: mapArrayOrNot( + const progress = (result: ITextSearchResult, uri: URI) => { + if (resultIsMatch(result)) { + callback({ + uri, + preview: { + text: result.previewText, + matches: mapArrayOrNot( result.rangeLocations, - r => new Range(r.source.startLineNumber, r.source.startColumn, r.source.endLineNumber, r.source.endColumn)) - } satisfies vscode.TextSearchMatch); - } else { - callback({ - uri, - text: result.text, - lineNumber: result.lineNumber - } satisfies vscode.TextSearchContext); - } - }); + m => new Range(m.preview.startLineNumber, m.preview.startColumn, m.preview.endLineNumber, m.preview.endColumn)) + }, + ranges: mapArrayOrNot( + result.rangeLocations, + r => new Range(r.source.startLineNumber, r.source.startColumn, r.source.endLineNumber, r.source.endColumn)) + } satisfies vscode.TextSearchMatch); + } else { + callback({ + uri, + text: result.text, + lineNumber: result.lineNumber + } satisfies vscode.TextSearchContext); + } }; - if (token.isCancellationRequested) { - return {}; - } - - try { - const result = await this._proxy.$startTextSearch( - query, - folder ?? null, - queryOptions, - requestId, - token); - delete this._activeSearchCallbacks[requestId]; - return result || {}; - } catch (err) { - delete this._activeSearchCallbacks[requestId]; - throw err; - } + return this.findTextInFilesBase(query, [{ options: queryOptions, folder }], progress, token); } $handleTextSearchResult(result: IRawFileMatch2, requestId: number): void { diff --git a/src/vs/workbench/services/search/common/queryBuilder.ts b/src/vs/workbench/services/search/common/queryBuilder.ts index 1f2fc6438f4..d127817f2bf 100644 --- a/src/vs/workbench/services/search/common/queryBuilder.ts +++ b/src/vs/workbench/services/search/common/queryBuilder.ts @@ -23,6 +23,7 @@ import { IWorkspaceContextService, IWorkspaceFolderData, toWorkspaceFolder, Work import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { ExcludeGlobPattern, getExcludes, ICommonQueryProps, IFileQuery, IFolderQuery, IPatternInfo, ISearchConfiguration, ITextQuery, ITextSearchPreviewOptions, pathIncludedInQuery, QueryType } from 'vs/workbench/services/search/common/search'; +import { GlobPattern } from 'vs/workbench/services/search/common/searchExtTypes'; /** * One folder to search and a glob expression that should be applied. @@ -51,6 +52,20 @@ export function isISearchPatternBuilder(object: ISearchPatternBuilder | ISearchP return (typeof object === 'object' && 'uri' in object && 'pattern' in object); } +export function globPatternToISearchPatternBuilder(globPattern: GlobPattern): ISearchPatternBuilder { + + if (typeof globPattern === 'string') { + return { + pattern: globPattern + }; + } + + return { + pattern: globPattern.pattern, + uri: globPattern.baseUri + }; +} + /** * A set of search paths and a set of glob expressions that should be applied. */