diff --git a/src/vs/base/common/fuzzyScorer.ts b/src/vs/base/common/fuzzyScorer.ts index 60ba8e6753cf7..2d692f8d585f1 100644 --- a/src/vs/base/common/fuzzyScorer.ts +++ b/src/vs/base/common/fuzzyScorer.ts @@ -500,7 +500,8 @@ function doScoreItemFuzzySingle(label: string, description: string | undefined, } } - // Finally compute description + label scores if we have a description + let itemScore = NO_ITEM_SCORE; + // Otherwise, compute description + label scores if we have a description if (description) { let descriptionPrefix = description; if (!!path) { @@ -540,11 +541,51 @@ function doScoreItemFuzzySingle(label: string, description: string | undefined, } }); - return { score: labelDescriptionScore, labelMatch, descriptionMatch }; + itemScore = { score: labelDescriptionScore, labelMatch, descriptionMatch }; } } - return NO_ITEM_SCORE; + // Finally try to compute a score based on the path if we have one. + // This allows us to partially match on the absolute path of the item + // instead of only matching on the workspace folder relative path. + // + // We assume the label is the path's basename and the description, if + // present, is a subpath of the path's dirname or '.'. + if (path?.endsWith(label) && + (!description || description === '.' || path.endsWith(`${description}${sep}${label}`))) { + const [pathScore, pathPositions] = scoreFuzzy( + path, + query.normalized, + query.normalizedLowercase, + allowNonContiguousMatches && !query.expectContiguousMatch); + + // FIXME: Should we ignore low quality matches across the paths? How? + if (pathScore > itemScore.score) { + const labelStartInPath = path.length - label.length; + let descEndInPath = -1; + let descStartInPath = -1; + if (description && description !== '.') { + descEndInPath = labelStartInPath - sep.length; + descStartInPath = descEndInPath - description.length; + } + const pathMatches = createMatches(pathPositions); + const labelMatch: IMatch[] = []; + const descriptionMatch: IMatch[] = []; + + for (const match of pathMatches) { + if (match.end > labelStartInPath) { + labelMatch.push({ start: Math.max(0, match.start - labelStartInPath), end: match.end - labelStartInPath }); + } + if (match.start < descEndInPath && descStartInPath < match.end) { + descriptionMatch.push({ start: Math.max(0, match.start - descStartInPath), end: Math.min(descEndInPath, match.end) - descStartInPath }); + } + } + + itemScore = { score: pathScore, labelMatch, descriptionMatch }; + } + } + + return itemScore; } function createMatches(offsets: number[] | undefined): IMatch[] { diff --git a/src/vs/workbench/services/search/common/search.ts b/src/vs/workbench/services/search/common/search.ts index 93fa38a845622..1b69eccbb42bb 100644 --- a/src/vs/workbench/services/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -558,7 +558,14 @@ export function isSerializedFileMatch(arg: ISerializedSearchProgressItem): arg i export function isFilePatternMatch(candidate: IRawFileMatch, normalizedFilePatternLowercase: string): boolean { const pathToMatch = candidate.searchPath ? candidate.searchPath : candidate.relativePath; - return fuzzyContains(pathToMatch, normalizedFilePatternLowercase); + if (fuzzyContains(pathToMatch, normalizedFilePatternLowercase)) { + return true; + } + // Also try matching against the absolute path of the file. + if (candidate.base) { + return fuzzyContains(paths.join(candidate.base, candidate.relativePath), normalizedFilePatternLowercase); + } + return false; } export interface ISerializedFileMatch { diff --git a/src/vs/workbench/services/search/node/rawSearchService.ts b/src/vs/workbench/services/search/node/rawSearchService.ts index 6e9251f016f76..25b1060bf9c2c 100644 --- a/src/vs/workbench/services/search/node/rawSearchService.ts +++ b/src/vs/workbench/services/search/node/rawSearchService.ts @@ -417,6 +417,9 @@ const FileMatchItemAccessor = new class implements IItemAccessor } getItemPath(match: IRawFileMatch): string { + if (match.base) { + return join(match.base, match.relativePath); // e.g. /home/user/some/path/to/file/myFile.txt + } return match.relativePath; // e.g. some/path/to/file/myFile.txt } }; diff --git a/src/vs/workbench/services/search/test/electron-browser/rawSearchService.integrationTest.ts b/src/vs/workbench/services/search/test/electron-browser/rawSearchService.integrationTest.ts index 19a60894c78f6..554ac1ab91544 100644 --- a/src/vs/workbench/services/search/test/electron-browser/rawSearchService.integrationTest.ts +++ b/src/vs/workbench/services/search/test/electron-browser/rawSearchService.integrationTest.ts @@ -244,6 +244,38 @@ flakySuite('RawSearchService', () => { assert.deepStrictEqual(results, [path.normalize('/some/where/bbc'), path.normalize('/some/where/bab')]); }); + test('Sorted results match on absolute path', async function () { + const paths = ['foo/bab', 'foo/bbc', 'foo/abb']; + const matches: IRawFileMatch[] = paths.map(relativePath => ({ + base: path.normalize('/some/where'), + relativePath, + basename: relativePath, + size: 3, + searchPath: undefined + })); + const Engine = TestSearchEngine.bind(null, () => matches.shift()!); + const service = new RawSearchService(); + + const results: any[] = []; + const cb: IProgressCallback = value => { + if (Array.isArray(value)) { + results.push(...value.map(v => v.path)); + } else { + assert.fail(JSON.stringify(value)); + } + }; + + await service.doFileSearchWithEngine(Engine, { + type: QueryType.File, + folderQueries: TEST_FOLDER_QUERIES, + filePattern: '/some/where/bb', + sortByScore: true, + maxResults: 2 + }, cb, undefined, 1); + assert.notStrictEqual(typeof TestSearchEngine.last.config!.maxResults, 'number'); + assert.deepStrictEqual(results, [path.normalize('/some/where/foo/bbc'), path.normalize('/some/where/foo/abb')]); + }); + test('Sorted result batches', async function () { let i = 25; const Engine = TestSearchEngine.bind(null, () => i-- ? rawMatch : null); diff --git a/src/vs/workbench/services/search/test/node/search.integrationTest.ts b/src/vs/workbench/services/search/test/node/search.integrationTest.ts index 0fad3392e6f8e..e1c65ae2a7d65 100644 --- a/src/vs/workbench/services/search/test/node/search.integrationTest.ts +++ b/src/vs/workbench/services/search/test/node/search.integrationTest.ts @@ -205,7 +205,7 @@ flakySuite('FileSearchEngine', () => { } }, () => { }, (error) => { assert.ok(!error); - assert.strictEqual(count, 7); + assert.strictEqual(count, 11); done(); }); }); @@ -224,7 +224,7 @@ flakySuite('FileSearchEngine', () => { } }, () => { }, (error) => { assert.ok(!error); - assert.strictEqual(count, 3); + assert.strictEqual(count, 7); done(); }); });