diff --git a/src/vs/base/common/arrays.ts b/src/vs/base/common/arrays.ts index 408750697f0fba49f3d766fc560c26b1987feb05..27c94557b087bd08379a3e96ecf3bfa676380121 100644 --- a/src/vs/base/common/arrays.ts +++ b/src/vs/base/common/arrays.ts @@ -540,3 +540,9 @@ export function find(arr: ArrayLike, predicate: (value: T, index: number, return undefined; } + +export function mapArrayOrNot(items: T | T[], fn: (_: T) => U): U | U[] { + return Array.isArray(items) ? + items.map(fn) : + fn(items); +} diff --git a/src/vs/platform/search/common/search.ts b/src/vs/platform/search/common/search.ts index 599c2dd2b9a80f7032f7bfaa707c4471cc4babae..90a8d3778eabdcff24b9cae6d9d6455b334d14c8 100644 --- a/src/vs/platform/search/common/search.ts +++ b/src/vs/platform/search/common/search.ts @@ -3,17 +3,18 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { mapArrayOrNot } from 'vs/base/common/arrays'; +import { CancellationToken } from 'vs/base/common/cancellation'; import { Event } from 'vs/base/common/event'; import * as glob from 'vs/base/common/glob'; import { IDisposable } from 'vs/base/common/lifecycle'; import * as objects from 'vs/base/common/objects'; import * as paths from 'vs/base/common/paths'; +import { getNLines } from 'vs/base/common/strings'; import { URI as uri, UriComponents } from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import { IFilesConfiguration } from 'vs/platform/files/common/files'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { getNLines } from 'vs/base/common/strings'; export const VIEW_ID = 'workbench.view.search'; @@ -170,12 +171,12 @@ export interface ISearchRange { export interface ITextSearchResultPreview { text: string; - match: ISearchRange; + matches: ISearchRange | ISearchRange[]; } export interface ITextSearchResult { uri?: uri; - range: ISearchRange; + ranges: ISearchRange | ISearchRange[]; preview: ITextSearchResultPreview; } @@ -248,13 +249,13 @@ export class FileMatch implements IFileMatch { } export class TextSearchResult implements ITextSearchResult { - range: ISearchRange; + ranges: ISearchRange | ISearchRange[]; preview: ITextSearchResultPreview; - constructor(text: string, range: ISearchRange, previewOptions?: ITextSearchPreviewOptions) { - this.range = range; + constructor(text: string, range: ISearchRange | ISearchRange[], previewOptions?: ITextSearchPreviewOptions) { + this.ranges = range; - if (previewOptions && previewOptions.matchLines === 1) { + if (previewOptions && previewOptions.matchLines === 1 && !Array.isArray(range)) { // 1 line preview requested text = getNLines(text, previewOptions.matchLines); const leadingChars = Math.floor(previewOptions.charsPerLine / 5); @@ -267,13 +268,15 @@ export class TextSearchResult implements ITextSearchResult { this.preview = { text: previewText, - match: new OneLineRange(0, range.startColumn - previewStart, endColInPreview) + matches: new OneLineRange(0, range.startColumn - previewStart, endColInPreview) }; } else { - // n line or no preview requested + const firstMatchLine = Array.isArray(range) ? range[0].startLineNumber : range.startLineNumber; + + // n line, no preview requested, or multiple matches in the preview this.preview = { text, - match: new SearchRange(0, range.startColumn, range.endLineNumber - range.startLineNumber, range.endColumn) + matches: mapArrayOrNot(range, r => new SearchRange(r.startLineNumber - firstMatchLine, r.startColumn, r.endLineNumber - firstMatchLine, r.endColumn)) }; } } diff --git a/src/vs/platform/search/test/common/search.test.ts b/src/vs/platform/search/test/common/search.test.ts index 25b89a956d7d0d83b05a6809977a3b83aed7b587..437e69245d142db1eae116d7cdab88ff02eb316a 100644 --- a/src/vs/platform/search/test/common/search.test.ts +++ b/src/vs/platform/search/test/common/search.test.ts @@ -14,56 +14,56 @@ suite('TextSearchResult', () => { function assertPreviewRangeText(text: string, result: TextSearchResult): void { assert.equal( - result.preview.text.substring(result.preview.match.startColumn, result.preview.match.endColumn), + result.preview.text.substring((result.preview.matches).startColumn, (result.preview.matches).endColumn), text); } test('empty without preview options', () => { const range = new OneLineRange(5, 0, 0); const result = new TextSearchResult('', range); - assert.deepEqual(result.range, range); + assert.deepEqual(result.ranges, range); assertPreviewRangeText('', result); }); test('empty with preview options', () => { const range = new OneLineRange(5, 0, 0); const result = new TextSearchResult('', range, previewOptions1); - assert.deepEqual(result.range, range); + assert.deepEqual(result.ranges, range); assertPreviewRangeText('', result); }); test('short without preview options', () => { const range = new OneLineRange(5, 4, 7); const result = new TextSearchResult('foo bar', range); - assert.deepEqual(result.range, range); + assert.deepEqual(result.ranges, range); assertPreviewRangeText('bar', result); }); test('short with preview options', () => { const range = new OneLineRange(5, 4, 7); const result = new TextSearchResult('foo bar', range, previewOptions1); - assert.deepEqual(result.range, range); + assert.deepEqual(result.ranges, range); assertPreviewRangeText('bar', result); }); test('leading', () => { const range = new OneLineRange(5, 25, 28); const result = new TextSearchResult('long text very long text foo', range, previewOptions1); - assert.deepEqual(result.range, range); + assert.deepEqual(result.ranges, range); assertPreviewRangeText('foo', result); }); test('trailing', () => { const range = new OneLineRange(5, 0, 3); const result = new TextSearchResult('foo long text very long text long text very long text long text very long text long text very long text long text very long text', range, previewOptions1); - assert.deepEqual(result.range, range); + assert.deepEqual(result.ranges, range); assertPreviewRangeText('foo', result); }); test('middle', () => { const range = new OneLineRange(5, 30, 33); const result = new TextSearchResult('long text very long text long foo text very long text long text very long text long text very long text long text very long text', range, previewOptions1); - assert.deepEqual(result.range, range); + assert.deepEqual(result.ranges, range); assertPreviewRangeText('foo', result); }); @@ -75,7 +75,7 @@ suite('TextSearchResult', () => { const range = new OneLineRange(0, 4, 7); const result = new TextSearchResult('foo bar', range, previewOptions); - assert.deepEqual(result.range, range); + assert.deepEqual(result.ranges, range); assertPreviewRangeText('b', result); }); @@ -87,7 +87,7 @@ suite('TextSearchResult', () => { const range = new SearchRange(5, 4, 6, 3); const result = new TextSearchResult('foo bar\nfoo bar', range, previewOptions); - assert.deepEqual(result.range, range); + assert.deepEqual(result.ranges, range); assertPreviewRangeText('bar', result); }); diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index c3ec7ebe43636435fc18d6eb04b7ec738407f484..3e5385814b3c8afa310abca8ebefab12de3c469c 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -234,8 +234,9 @@ declare module 'vscode' { /** * The Range within `text` corresponding to the text of the match. + * The number of matches must match the TextSearchResult's range property. */ - match: Range; + matches: Range | Range[]; } /** @@ -248,9 +249,9 @@ declare module 'vscode' { uri: Uri; /** - * The range of the match within the document. + * The range of the match within the document, or multiple ranges for multiple matches. */ - range: Range; + ranges: Range | Range[]; /** * A preview of the text result. diff --git a/src/vs/workbench/api/node/extHostWorkspace.ts b/src/vs/workbench/api/node/extHostWorkspace.ts index 3d9ed901014b27e9e3370352c3fd8112eb136aa9..ec86a96f263c58a76af3f9c92c150db17374135a 100644 --- a/src/vs/workbench/api/node/extHostWorkspace.ts +++ b/src/vs/workbench/api/node/extHostWorkspace.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { join, relative } from 'path'; -import { delta as arrayDelta } from 'vs/base/common/arrays'; +import { delta as arrayDelta, mapArrayOrNot } from 'vs/base/common/arrays'; import { Emitter, Event } from 'vs/base/common/event'; import { TernarySearchTree } from 'vs/base/common/map'; import { Counter } from 'vs/base/common/numbers'; @@ -425,9 +425,13 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape { uri: URI.revive(p.resource), preview: { text: match.preview.text, - match: new Range(match.preview.match.startLineNumber, match.preview.match.startColumn, match.preview.match.endLineNumber, match.preview.match.endColumn) + matches: mapArrayOrNot( + match.preview.matches, + m => new Range(m.startLineNumber, m.startColumn, m.endLineNumber, m.endColumn)) }, - range: new Range(match.range.startLineNumber, match.range.startColumn, match.range.endLineNumber, match.range.endColumn) + ranges: mapArrayOrNot( + match.ranges, + r => new Range(r.startLineNumber, r.startColumn, r.endLineNumber, r.endColumn)) }); }); }; diff --git a/src/vs/workbench/parts/search/common/searchModel.ts b/src/vs/workbench/parts/search/common/searchModel.ts index 2aa6b6536608987db439ec742cb26081dcec15c5..1064869f6f924fae93b741715082c5b115263027 100644 --- a/src/vs/workbench/parts/search/common/searchModel.ts +++ b/src/vs/workbench/parts/search/common/searchModel.ts @@ -21,7 +21,7 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IProgressRunner } from 'vs/platform/progress/common/progress'; import { ReplacePattern } from 'vs/platform/search/common/replace'; -import { IFileMatch, IPatternInfo, ISearchComplete, ISearchProgressItem, ISearchService, ITextSearchPreviewOptions, ITextSearchResult, ITextSearchStats, TextSearchResult, ITextQuery } from 'vs/platform/search/common/search'; +import { IFileMatch, IPatternInfo, ISearchComplete, ISearchProgressItem, ISearchService, ITextQuery, ITextSearchPreviewOptions, ITextSearchResult, ITextSearchStats, TextSearchResult } from 'vs/platform/search/common/search'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { overviewRulerFindMatchForeground } from 'vs/platform/theme/common/colorRegistry'; import { themeColorFromId } from 'vs/platform/theme/common/themeService'; @@ -37,17 +37,21 @@ export class Match { private _rangeInPreviewText: Range; constructor(private _parent: FileMatch, _result: ITextSearchResult) { + if (Array.isArray(_result.ranges) || Array.isArray(_result.preview.matches)) { + throw new Error('A Match can only be built from a single search result'); + } + this._range = new Range( - _result.range.startLineNumber + 1, - _result.range.startColumn + 1, - _result.range.endLineNumber + 1, - _result.range.endColumn + 1); + _result.ranges.startLineNumber + 1, + _result.ranges.startColumn + 1, + _result.ranges.endLineNumber + 1, + _result.ranges.endColumn + 1); this._rangeInPreviewText = new Range( - _result.preview.match.startLineNumber + 1, - _result.preview.match.startColumn + 1, - _result.preview.match.endLineNumber + 1, - _result.preview.match.endColumn + 1); + _result.preview.matches.startLineNumber + 1, + _result.preview.matches.startColumn + 1, + _result.preview.matches.endLineNumber + 1, + _result.preview.matches.endColumn + 1); this._previewText = _result.preview.text; this._id = this._parent.id() + '>' + this._range + this.getMatchString(); @@ -171,8 +175,8 @@ export class FileMatch extends Disposable { this.updateMatchesForModel(); } else { this.rawMatch.matches.forEach(rawMatch => { - let match = new Match(this, rawMatch); - this.add(match); + textSearchResultToMatches(rawMatch, this) + .forEach(m => this.add(m)); }); } } @@ -416,8 +420,8 @@ export class FolderMatch extends Disposable { if (this._fileMatches.has(rawFileMatch.resource)) { const existingFileMatch = this._fileMatches.get(rawFileMatch.resource); rawFileMatch.matches.forEach(m => { - let match = new Match(existingFileMatch, m); - existingFileMatch.add(match); + textSearchResultToMatches(m, existingFileMatch) + .forEach(m => existingFileMatch.add(m)); }); updated.push(existingFileMatch); } else { @@ -1011,3 +1015,21 @@ export function editorMatchToTextSearchResult(match: FindMatch, model: ITextMode new Range(match.range.startLineNumber - 1, match.range.startColumn - 1, match.range.endLineNumber - 1, match.range.endColumn - 1), previewOptions); } + +function textSearchResultToMatches(rawMatch: ITextSearchResult, fileMatch: FileMatch): Match[] { + if (Array.isArray(rawMatch.ranges)) { + return rawMatch.ranges.map((r, i) => { + return new Match(fileMatch, { + uri: rawMatch.uri, + ranges: r, + preview: { + text: rawMatch.preview.text, + matches: rawMatch.preview.matches[i] + } + }); + }); + } else { + let match = new Match(fileMatch, rawMatch); + return [match]; + } +} diff --git a/src/vs/workbench/parts/search/test/browser/searchActions.test.ts b/src/vs/workbench/parts/search/test/browser/searchActions.test.ts index e1624978573e795984f1feb18596de4d87a1f9b3..f1b6b7d4771c36a0af7e07b70cc648772189b509 100644 --- a/src/vs/workbench/parts/search/test/browser/searchActions.test.ts +++ b/src/vs/workbench/parts/search/test/browser/searchActions.test.ts @@ -134,7 +134,7 @@ suite('Search Actions', () => { function aMatch(fileMatch: FileMatch): Match { const line = ++counter; - const range = { + const ranges = { startLineNumber: line, startColumn: 0, endLineNumber: line, @@ -143,9 +143,9 @@ suite('Search Actions', () => { let match = new Match(fileMatch, { preview: { text: 'some match', - match: range + matches: ranges }, - range + ranges }); fileMatch.add(match); return match; diff --git a/src/vs/workbench/parts/search/test/browser/searchViewlet.test.ts b/src/vs/workbench/parts/search/test/browser/searchViewlet.test.ts index fa0456019cffb86978a4848035d58fd76794380a..e694eabdc78b528a3584411e9a4c7df229153767 100644 --- a/src/vs/workbench/parts/search/test/browser/searchViewlet.test.ts +++ b/src/vs/workbench/parts/search/test/browser/searchViewlet.test.ts @@ -36,7 +36,7 @@ suite('Search - Viewlet', () => { }] }; - const range = { + const ranges = { startLineNumber: 1, startColumn: 0, endLineNumber: 1, @@ -47,9 +47,9 @@ suite('Search - Viewlet', () => { matches: [{ preview: { text: 'bar', - match: range + matches: ranges }, - range + ranges }] }]); diff --git a/src/vs/workbench/services/search/node/ripgrepSearchUtils.ts b/src/vs/workbench/services/search/node/ripgrepSearchUtils.ts index 601d679d55120b239b08f81fd936c02dd7d5ec21..90be1eb45fdb38c04cc9d4a0b2b9a186bc8bce4d 100644 --- a/src/vs/workbench/services/search/node/ripgrepSearchUtils.ts +++ b/src/vs/workbench/services/search/node/ripgrepSearchUtils.ts @@ -7,6 +7,7 @@ import { startsWith } from 'vs/base/common/strings'; import { ILogService } from 'vs/platform/log/common/log'; import { SearchRange, TextSearchResult } from 'vs/platform/search/common/search'; import * as vscode from 'vscode'; +import { mapArrayOrNot } from 'vs/base/common/arrays'; export type Maybe = T | null | undefined; @@ -14,26 +15,32 @@ export function anchorGlob(glob: string): string { return startsWith(glob, '**') || startsWith(glob, '/') ? glob : `/${glob}`; } -export function createTextSearchResult(uri: vscode.Uri, text: string, range: Range, previewOptions?: vscode.TextSearchPreviewOptions): vscode.TextSearchResult { - const searchRange: SearchRange = { - startLineNumber: range.start.line, - startColumn: range.start.character, - endLineNumber: range.end.line, - endColumn: range.end.character, - }; +/** + * Create a vscode.TextSearchResult by using our internal TextSearchResult type for its previewOptions logic. + */ +export function createTextSearchResult(uri: vscode.Uri, text: string, range: Range | Range[], previewOptions?: vscode.TextSearchPreviewOptions): vscode.TextSearchResult { + const searchRange = mapArrayOrNot(range, rangeToSearchRange); const internalResult = new TextSearchResult(text, searchRange, previewOptions); - const internalPreviewRange = internalResult.preview.match; + const internalPreviewRange = internalResult.preview.matches; return { - range: new Range(internalResult.range.startLineNumber, internalResult.range.startColumn, internalResult.range.endLineNumber, internalResult.range.endColumn), + ranges: mapArrayOrNot(searchRange, searchRangeToRange), uri, preview: { text: internalResult.preview.text, - match: new Range(internalPreviewRange.startLineNumber, internalPreviewRange.startColumn, internalPreviewRange.endLineNumber, internalPreviewRange.endColumn), + matches: mapArrayOrNot(internalPreviewRange, searchRangeToRange) } }; } +function rangeToSearchRange(range: Range): SearchRange { + return new SearchRange(range.start.line, range.start.character, range.end.line, range.end.character); +} + +function searchRangeToRange(range: SearchRange): Range { + return new Range(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn); +} + export class Position { constructor(public readonly line, public readonly character) { } diff --git a/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts b/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts index 43a67d24edbaf7b019d3e2d9321134d4f8af95de..2fe55d3340eb3cc2bbbe57f12ffec077ff6b2b28 100644 --- a/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts +++ b/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts @@ -138,6 +138,7 @@ export function rgErrorMsgForDisplay(msg: string): Maybe { export class RipgrepParser extends EventEmitter { private remainder = ''; private isDone = false; + private hitLimit = false; private stringDecoder: NodeStringDecoder; private numResults = 0; @@ -190,56 +191,62 @@ export class RipgrepParser extends EventEmitter { } if (parsedLine.type === 'match') { - let hitLimit = false; const uri = URI.file(path.join(this.rootFolder, parsedLine.data.path.text)); - parsedLine.data.submatches.map((match: any) => { - if (hitLimit) { - return null; - } - - if (this.numResults >= this.maxResults) { - // Finish the line, then report the result below - hitLimit = true; - } + const result = this.submatchesToResult(parsedLine, parsedLine.data.submatches, uri); + this.onResult(result); - return this.submatchToResult(parsedLine, match, uri); - }).forEach((result: any) => { - if (result) { - this.onResult(result); - } - }); - - if (hitLimit) { + if (this.hitLimit) { this.cancel(); this.emit('hitLimit'); } } } - private submatchToResult(parsedLine: any, match: any, uri: vscode.Uri): vscode.TextSearchResult { + private submatchesToResult(parsedLine: any, matches: any[], uri: vscode.Uri): vscode.TextSearchResult { const lineNumber = parsedLine.data.line_number - 1; - let lineText = bytesOrTextToString(parsedLine.data.lines); - let matchText = bytesOrTextToString(match.match); - const newlineMatches = matchText.match(/\n/g); - const newlines = newlineMatches ? newlineMatches.length : 0; + const fullText = bytesOrTextToString(parsedLine.data.lines); + const fullTextBytes = Buffer.from(fullText); + + let prevMatchEnd = 0; + let prevMatchEndCol = 0; + let prevMatchEndLine = lineNumber; + const ranges = matches.map((match, i) => { + if (this.hitLimit) { + return null; + } + + this.numResults++; + if (this.numResults >= this.maxResults) { + // Finish the line, then report the result below + this.hitLimit = true; + } - const textBytes = Buffer.from(lineText); - let startCol = textBytes.slice(0, match.start).toString().length; - const endChars = startCol + textBytes.slice(match.start, match.end).toString().length; + let matchText = bytesOrTextToString(match.match); + const inBetweenChars = fullTextBytes.slice(prevMatchEnd, match.start).toString().length; + let startCol = prevMatchEndCol + inBetweenChars; - const endLineNumber = lineNumber + newlines; - let endCol = endChars - (lineText.lastIndexOf('\n', lineText.length - 2) + 1); + const stats = getNumLinesAndLastNewlineLength(matchText); + let startLineNumber = prevMatchEndLine; + let endLineNumber = stats.numLines + startLineNumber; + let endCol = stats.numLines > 0 ? + stats.lastLineLength : + stats.lastLineLength + startCol; - if (lineNumber === 0) { - if (startsWithUTF8BOM(matchText)) { + if (lineNumber === 0 && i === 0 && startsWithUTF8BOM(matchText)) { matchText = stripUTF8BOM(matchText); startCol -= 3; endCol -= 3; } - } - const range = new Range(lineNumber, startCol, endLineNumber, endCol); - return createTextSearchResult(uri, lineText, range, this.previewOptions); + prevMatchEnd = match.end; + prevMatchEndCol = endCol; + prevMatchEndLine = endLineNumber; + + return new Range(startLineNumber, startCol, endLineNumber, endCol); + }) + .filter(r => !!r); + + return createTextSearchResult(uri, fullText, ranges, this.previewOptions); } private onResult(match: vscode.TextSearchResult): void { @@ -253,6 +260,23 @@ function bytesOrTextToString(obj: any): string { obj.text; } +function getNumLinesAndLastNewlineLength(text: string): { numLines: number, lastLineLength: number } { + const re = /\n/g; + let numLines = 0; + let lastNewlineIdx = -1; + let match: ReturnType; + while (match = re.exec(text)) { + numLines++; + lastNewlineIdx = match.index; + } + + const lastLineLength = lastNewlineIdx >= 0 ? + text.length - lastNewlineIdx : + text.length; + + return { numLines, lastLineLength }; +} + function getRgArgs(query: vscode.TextSearchQuery, options: vscode.TextSearchOptions): string[] { const args = ['--hidden', '--heading', '--line-number', '--color', 'ansi', '--colors', 'path:none', '--colors', 'line:none', '--colors', 'match:fg:red', '--colors', 'match:style:nobold']; args.push(query.isCaseSensitive ? '--case-sensitive' : '--ignore-case'); diff --git a/src/vs/workbench/services/search/node/textSearchAdapter.ts b/src/vs/workbench/services/search/node/textSearchAdapter.ts index 6759cd6e8dd5f81c72373ec9c10eae1bf3d388f3..e26b235acdf50caa1b0e1f6e6166972aebc63a6a 100644 --- a/src/vs/workbench/services/search/node/textSearchAdapter.ts +++ b/src/vs/workbench/services/search/node/textSearchAdapter.ts @@ -50,6 +50,6 @@ function fileMatchToSerialized(match: IFileMatch): ISerializedFileMatch { return { path: match.resource.fsPath, matches: match.matches, - numMatches: match.matches.length + numMatches: match.matches.reduce((sum, m) => sum + (Array.isArray(m.ranges) ? m.ranges.length : 1), 0) }; } \ No newline at end of file diff --git a/src/vs/workbench/services/search/node/textSearchManager.ts b/src/vs/workbench/services/search/node/textSearchManager.ts index 51e997d2629fec80e8d07675b98d24693df03b9f..dcc59084dcb89ccd1b0fea2c9d64fb9d101df5e4 100644 --- a/src/vs/workbench/services/search/node/textSearchManager.ts +++ b/src/vs/workbench/services/search/node/textSearchManager.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as path from 'path'; +import { mapArrayOrNot } from 'vs/base/common/arrays'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import * as glob from 'vs/base/common/glob'; @@ -82,6 +83,8 @@ export class TextSearchManager { const testingPs: TPromise[] = []; const progress = { report: (result: vscode.TextSearchResult) => { + // TODO: validate result.ranges vs result.preview.matches + const hasSibling = folderQuery.folder.scheme === 'file' && glob.hasSiblingPromiseFn(() => { return this.readdir(path.dirname(result.uri.fsPath)); }); @@ -201,20 +204,20 @@ function extensionResultToFrontendResult(data: vscode.TextSearchResult): ITextSe // Warning: result from RipgrepTextSearchEH has fake vscode.Range. Don't depend on any other props beyond these... return { preview: { - match: { - startLineNumber: data.preview.match.start.line, - startColumn: data.preview.match.start.character, - endLineNumber: data.preview.match.end.line, - endColumn: data.preview.match.end.character - }, + matches: mapArrayOrNot(data.preview.matches, m => ({ + startLineNumber: m.start.line, + startColumn: m.start.character, + endLineNumber: m.end.line, + endColumn: m.end.character + })), text: data.preview.text }, - range: { - startLineNumber: data.range.start.line, - startColumn: data.range.start.character, - endLineNumber: data.range.end.line, - endColumn: data.range.end.character - } + ranges: mapArrayOrNot(data.ranges, r => ({ + startLineNumber: r.start.line, + startColumn: r.start.character, + endLineNumber: r.end.line, + endColumn: r.end.character + })) }; } diff --git a/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts b/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts index aaa56cd0e534eedc7d17c616a6602d4982cc11de..dcdbd567fcb766061a8b25bcd4449ae26d2ebcc1 100644 --- a/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts +++ b/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts @@ -385,13 +385,13 @@ suite('Search-integration', function () { }; return doRipgrepSearchTest(config, 1).then(results => { - const matchRange = results[0].matches[0].range; - assert.deepEqual(matchRange, { + const matchRange = results[0].matches[0].ranges; + assert.deepEqual(matchRange, [{ startLineNumber: 0, startColumn: 1, endLineNumber: 0, endColumn: 2 - }); + }]); }); }); }); diff --git a/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts b/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts index da34e6e3cf97005dc06f24d0f94251739a41c68c..f9e7b51d2601baea0de7f9a00fc37c4d71dc4177 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { mapArrayOrNot } from 'vs/base/common/arrays'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { dispose } from 'vs/base/common/lifecycle'; @@ -625,7 +626,7 @@ suite('ExtHostSearch', () => { function makePreview(text: string): vscode.TextSearchResult['preview'] { return { - match: new Range(0, 0, 0, text.length), + matches: new Range(0, 0, 0, text.length), text }; } @@ -633,7 +634,7 @@ suite('ExtHostSearch', () => { function makeTextResult(baseFolder: URI, relativePath: string): vscode.TextSearchResult { return { preview: makePreview('foo'), - range: new Range(0, 0, 0, 3), + ranges: new Range(0, 0, 0, 3), uri: joinPath(baseFolder, relativePath) }; } @@ -663,9 +664,14 @@ suite('ExtHostSearch', () => { actualTextSearchResults.push({ preview: { text: lineMatch.preview.text, - match: new Range(lineMatch.preview.match.startLineNumber, lineMatch.preview.match.startColumn, lineMatch.preview.match.endLineNumber, lineMatch.preview.match.endColumn) + matches: mapArrayOrNot( + lineMatch.preview.matches, + m => new Range(m.startLineNumber, m.startColumn, m.endLineNumber, m.endColumn)) }, - range: new Range(lineMatch.range.startLineNumber, lineMatch.range.startColumn, lineMatch.range.endLineNumber, lineMatch.range.endColumn), + ranges: mapArrayOrNot( + lineMatch.ranges, + r => new Range(r.startLineNumber, r.startColumn, r.endLineNumber, r.endColumn), + ), uri: fileMatch.resource }); } @@ -679,7 +685,7 @@ suite('ExtHostSearch', () => { ...r, ...{ uri: r.uri.toString(), - range: rangeToString(r.range), + range: mapArrayOrNot(r.ranges, rangeToString), preview: { text: r.preview.text, match: null // Don't care about this right now