Path: blob/main/src/vs/base/test/common/fuzzyScorer.test.ts
5252 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import assert from 'assert';6import { compareItemsByFuzzyScore, FuzzyScore, FuzzyScore2, FuzzyScorerCache, IItemAccessor, IItemScore, pieceToQuery, prepareQuery, scoreFuzzy, scoreFuzzy2, scoreItemFuzzy } from '../../common/fuzzyScorer.js';7import { Schemas } from '../../common/network.js';8import { basename, dirname, posix, sep, win32 } from '../../common/path.js';9import { isWindows } from '../../common/platform.js';10import { URI } from '../../common/uri.js';11import { ensureNoDisposablesAreLeakedInTestSuite } from './utils.js';1213class ResourceAccessorClass implements IItemAccessor<URI> {1415getItemLabel(resource: URI): string {16return basename(resource.fsPath);17}1819getItemDescription(resource: URI): string {20return dirname(resource.fsPath);21}2223getItemPath(resource: URI): string {24return resource.fsPath;25}26}2728const ResourceAccessor = new ResourceAccessorClass();2930class ResourceWithSlashAccessorClass implements IItemAccessor<URI> {3132getItemLabel(resource: URI): string {33return basename(resource.fsPath);34}3536getItemDescription(resource: URI): string {37return posix.normalize(dirname(resource.path));38}3940getItemPath(resource: URI): string {41return posix.normalize(resource.path);42}43}4445const ResourceWithSlashAccessor = new ResourceWithSlashAccessorClass();4647class ResourceWithBackslashAccessorClass implements IItemAccessor<URI> {4849getItemLabel(resource: URI): string {50return basename(resource.fsPath);51}5253getItemDescription(resource: URI): string {54return win32.normalize(dirname(resource.path));55}5657getItemPath(resource: URI): string {58return win32.normalize(resource.path);59}60}6162const ResourceWithBackslashAccessor = new ResourceWithBackslashAccessorClass();6364class NullAccessorClass implements IItemAccessor<URI> {6566getItemLabel(resource: URI): string {67return undefined!;68}6970getItemDescription(resource: URI): string {71return undefined!;72}7374getItemPath(resource: URI): string {75return undefined!;76}77}7879function _doScore(target: string, query: string, allowNonContiguousMatches?: boolean): FuzzyScore {80const preparedQuery = prepareQuery(query);8182return scoreFuzzy(target, preparedQuery.normalized, preparedQuery.normalizedLowercase, allowNonContiguousMatches ?? !preparedQuery.expectContiguousMatch);83}8485function _doScore2(target: string, query: string, matchOffset: number = 0): FuzzyScore2 {86const preparedQuery = prepareQuery(query);8788return scoreFuzzy2(target, preparedQuery, 0, matchOffset);89}9091function scoreItem<T>(item: T, query: string, allowNonContiguousMatches: boolean, accessor: IItemAccessor<T>, cache: FuzzyScorerCache = Object.create(null)): IItemScore {92return scoreItemFuzzy(item, prepareQuery(query), allowNonContiguousMatches, accessor, cache);93}9495function compareItemsByScore<T>(itemA: T, itemB: T, query: string, allowNonContiguousMatches: boolean, accessor: IItemAccessor<T>): number {96return compareItemsByFuzzyScore(itemA, itemB, prepareQuery(query), allowNonContiguousMatches, accessor, Object.create(null));97}9899const NullAccessor = new NullAccessorClass();100101suite('Fuzzy Scorer', () => {102103test('score (fuzzy)', function () {104const target = 'HelLo-World';105106const scores: FuzzyScore[] = [];107scores.push(_doScore(target, 'HelLo-World', true)); // direct case match108scores.push(_doScore(target, 'hello-world', true)); // direct mix-case match109scores.push(_doScore(target, 'HW', true)); // direct case prefix (multiple)110scores.push(_doScore(target, 'hw', true)); // direct mix-case prefix (multiple)111scores.push(_doScore(target, 'H', true)); // direct case prefix112scores.push(_doScore(target, 'h', true)); // direct mix-case prefix113scores.push(_doScore(target, 'W', true)); // direct case word prefix114scores.push(_doScore(target, 'Ld', true)); // in-string case match (multiple)115scores.push(_doScore(target, 'ld', true)); // in-string mix-case match (consecutive, avoids scattered hit)116scores.push(_doScore(target, 'w', true)); // direct mix-case word prefix117scores.push(_doScore(target, 'L', true)); // in-string case match118scores.push(_doScore(target, 'l', true)); // in-string mix-case match119scores.push(_doScore(target, '4', true)); // no match120121// Assert scoring order122const sortedScores = scores.concat().sort((a, b) => b[0] - a[0]);123assert.deepStrictEqual(scores, sortedScores);124125// Assert scoring positions126// let positions = scores[0][1];127// assert.strictEqual(positions.length, 'HelLo-World'.length);128129// positions = scores[2][1];130// assert.strictEqual(positions.length, 'HW'.length);131// assert.strictEqual(positions[0], 0);132// assert.strictEqual(positions[1], 6);133});134135test('score (non fuzzy)', function () {136const target = 'HelLo-World';137138assert.ok(_doScore(target, 'HelLo-World', false)[0] > 0);139assert.strictEqual(_doScore(target, 'HelLo-World', false)[1].length, 'HelLo-World'.length);140141assert.ok(_doScore(target, 'hello-world', false)[0] > 0);142assert.strictEqual(_doScore(target, 'HW', false)[0], 0);143assert.ok(_doScore(target, 'h', false)[0] > 0);144assert.ok(_doScore(target, 'ello', false)[0] > 0);145assert.ok(_doScore(target, 'ld', false)[0] > 0);146assert.strictEqual(_doScore(target, 'eo', false)[0], 0);147});148149test('scoreItem - matches are proper', function () {150let res = scoreItem(null, 'something', true, ResourceAccessor);151assert.ok(!res.score);152153const resource = URI.file('/xyz/some/path/someFile123.txt');154155res = scoreItem(resource, 'something', true, NullAccessor);156assert.ok(!res.score);157158// Path Identity159const identityRes = scoreItem(resource, ResourceAccessor.getItemPath(resource), true, ResourceAccessor);160assert.ok(identityRes.score);161assert.strictEqual(identityRes.descriptionMatch!.length, 1);162assert.strictEqual(identityRes.labelMatch!.length, 1);163assert.strictEqual(identityRes.descriptionMatch![0].start, 0);164assert.strictEqual(identityRes.descriptionMatch![0].end, ResourceAccessor.getItemDescription(resource).length);165assert.strictEqual(identityRes.labelMatch![0].start, 0);166assert.strictEqual(identityRes.labelMatch![0].end, ResourceAccessor.getItemLabel(resource).length);167168// Basename Prefix169const basenamePrefixRes = scoreItem(resource, 'som', true, ResourceAccessor);170assert.ok(basenamePrefixRes.score);171assert.ok(!basenamePrefixRes.descriptionMatch);172assert.strictEqual(basenamePrefixRes.labelMatch!.length, 1);173assert.strictEqual(basenamePrefixRes.labelMatch![0].start, 0);174assert.strictEqual(basenamePrefixRes.labelMatch![0].end, 'som'.length);175176// Basename Camelcase177const basenameCamelcaseRes = scoreItem(resource, 'sF', true, ResourceAccessor);178assert.ok(basenameCamelcaseRes.score);179assert.ok(!basenameCamelcaseRes.descriptionMatch);180assert.strictEqual(basenameCamelcaseRes.labelMatch!.length, 2);181assert.strictEqual(basenameCamelcaseRes.labelMatch![0].start, 0);182assert.strictEqual(basenameCamelcaseRes.labelMatch![0].end, 1);183assert.strictEqual(basenameCamelcaseRes.labelMatch![1].start, 4);184assert.strictEqual(basenameCamelcaseRes.labelMatch![1].end, 5);185186// Basename Match187const basenameRes = scoreItem(resource, 'of', true, ResourceAccessor);188assert.ok(basenameRes.score);189assert.ok(!basenameRes.descriptionMatch);190assert.strictEqual(basenameRes.labelMatch!.length, 2);191assert.strictEqual(basenameRes.labelMatch![0].start, 1);192assert.strictEqual(basenameRes.labelMatch![0].end, 2);193assert.strictEqual(basenameRes.labelMatch![1].start, 4);194assert.strictEqual(basenameRes.labelMatch![1].end, 5);195196// Path Match197const pathRes = scoreItem(resource, 'xyz123', true, ResourceAccessor);198assert.ok(pathRes.score);199assert.ok(pathRes.descriptionMatch);200assert.ok(pathRes.labelMatch);201assert.strictEqual(pathRes.labelMatch.length, 1);202assert.strictEqual(pathRes.labelMatch[0].start, 8);203assert.strictEqual(pathRes.labelMatch[0].end, 11);204assert.strictEqual(pathRes.descriptionMatch.length, 1);205assert.strictEqual(pathRes.descriptionMatch[0].start, 1);206assert.strictEqual(pathRes.descriptionMatch[0].end, 4);207208// Ellipsis Match209const ellipsisRes = scoreItem(resource, '…me/path/someFile123.txt', true, ResourceAccessor);210assert.ok(ellipsisRes.score);211assert.ok(pathRes.descriptionMatch);212assert.ok(pathRes.labelMatch);213assert.strictEqual(pathRes.labelMatch.length, 1);214assert.strictEqual(pathRes.labelMatch[0].start, 8);215assert.strictEqual(pathRes.labelMatch[0].end, 11);216assert.strictEqual(pathRes.descriptionMatch.length, 1);217assert.strictEqual(pathRes.descriptionMatch[0].start, 1);218assert.strictEqual(pathRes.descriptionMatch[0].end, 4);219220// No Match221const noRes = scoreItem(resource, '987', true, ResourceAccessor);222assert.ok(!noRes.score);223assert.ok(!noRes.labelMatch);224assert.ok(!noRes.descriptionMatch);225226// No Exact Match227const noExactRes = scoreItem(resource, '"sF"', true, ResourceAccessor);228assert.ok(!noExactRes.score);229assert.ok(!noExactRes.labelMatch);230assert.ok(!noExactRes.descriptionMatch);231assert.strictEqual(noRes.score, noExactRes.score);232233// Verify Scores234assert.ok(identityRes.score > basenamePrefixRes.score);235assert.ok(basenamePrefixRes.score > basenameRes.score);236assert.ok(basenameRes.score > pathRes.score);237assert.ok(pathRes.score > noRes.score);238});239240test('scoreItem - multiple', function () {241const resource = URI.file('/xyz/some/path/someFile123.txt');242243const res1 = scoreItem(resource, 'xyz some', true, ResourceAccessor);244assert.ok(res1.score);245assert.strictEqual(res1.labelMatch?.length, 1);246assert.strictEqual(res1.labelMatch[0].start, 0);247assert.strictEqual(res1.labelMatch[0].end, 4);248assert.strictEqual(res1.descriptionMatch?.length, 1);249assert.strictEqual(res1.descriptionMatch[0].start, 1);250assert.strictEqual(res1.descriptionMatch[0].end, 4);251252const res2 = scoreItem(resource, 'some xyz', true, ResourceAccessor);253assert.ok(res2.score);254assert.strictEqual(res1.score, res2.score);255assert.strictEqual(res2.labelMatch?.length, 1);256assert.strictEqual(res2.labelMatch[0].start, 0);257assert.strictEqual(res2.labelMatch[0].end, 4);258assert.strictEqual(res2.descriptionMatch?.length, 1);259assert.strictEqual(res2.descriptionMatch[0].start, 1);260assert.strictEqual(res2.descriptionMatch[0].end, 4);261262const res3 = scoreItem(resource, 'some xyz file file123', true, ResourceAccessor);263assert.ok(res3.score);264assert.ok(res3.score > res2.score);265assert.strictEqual(res3.labelMatch?.length, 1);266assert.strictEqual(res3.labelMatch[0].start, 0);267assert.strictEqual(res3.labelMatch[0].end, 11);268assert.strictEqual(res3.descriptionMatch?.length, 1);269assert.strictEqual(res3.descriptionMatch[0].start, 1);270assert.strictEqual(res3.descriptionMatch[0].end, 4);271272const res4 = scoreItem(resource, 'path z y', true, ResourceAccessor);273assert.ok(res4.score);274assert.ok(res4.score < res2.score);275assert.strictEqual(res4.labelMatch?.length, 0);276assert.strictEqual(res4.descriptionMatch?.length, 2);277assert.strictEqual(res4.descriptionMatch[0].start, 2);278assert.strictEqual(res4.descriptionMatch[0].end, 4);279assert.strictEqual(res4.descriptionMatch[1].start, 10);280assert.strictEqual(res4.descriptionMatch[1].end, 14);281});282283test('scoreItem - multiple with cache yields different results', function () {284const resource = URI.file('/xyz/some/path/someFile123.txt');285const cache = {};286const res1 = scoreItem(resource, 'xyz sm', true, ResourceAccessor, cache);287assert.ok(res1.score);288289// from the cache's perspective this should be a totally different query290const res2 = scoreItem(resource, 'xyz "sm"', true, ResourceAccessor, cache);291assert.ok(!res2.score);292});293294test('scoreItem - invalid input', function () {295296let res = scoreItem(null, null!, true, ResourceAccessor);297assert.strictEqual(res.score, 0);298299res = scoreItem(null, 'null', true, ResourceAccessor);300assert.strictEqual(res.score, 0);301});302303test('scoreItem - optimize for file paths', function () {304const resource = URI.file('/xyz/others/spath/some/xsp/file123.txt');305306// xsp is more relevant to the end of the file path even though it matches307// fuzzy also in the beginning. we verify the more relevant match at the308// end gets returned.309const pathRes = scoreItem(resource, 'xspfile123', true, ResourceAccessor);310assert.ok(pathRes.score);311assert.ok(pathRes.descriptionMatch);312assert.ok(pathRes.labelMatch);313assert.strictEqual(pathRes.labelMatch.length, 1);314assert.strictEqual(pathRes.labelMatch[0].start, 0);315assert.strictEqual(pathRes.labelMatch[0].end, 7);316assert.strictEqual(pathRes.descriptionMatch.length, 1);317assert.strictEqual(pathRes.descriptionMatch[0].start, 23);318assert.strictEqual(pathRes.descriptionMatch[0].end, 26);319});320321test('scoreItem - avoid match scattering (bug #36119)', function () {322const resource = URI.file('projects/ui/cula/ats/target.mk');323324const pathRes = scoreItem(resource, 'tcltarget.mk', true, ResourceAccessor);325assert.ok(pathRes.score);326assert.ok(pathRes.descriptionMatch);327assert.ok(pathRes.labelMatch);328assert.strictEqual(pathRes.labelMatch.length, 1);329assert.strictEqual(pathRes.labelMatch[0].start, 0);330assert.strictEqual(pathRes.labelMatch[0].end, 9);331});332333test('scoreItem - prefers more compact matches', function () {334const resource = URI.file('/1a111d1/11a1d1/something.txt');335336// expect "ad" to be matched towards the end of the file because the337// match is more compact338const res = scoreItem(resource, 'ad', true, ResourceAccessor);339assert.ok(res.score);340assert.ok(res.descriptionMatch);341assert.ok(!res.labelMatch!.length);342assert.strictEqual(res.descriptionMatch.length, 2);343assert.strictEqual(res.descriptionMatch[0].start, 11);344assert.strictEqual(res.descriptionMatch[0].end, 12);345assert.strictEqual(res.descriptionMatch[1].start, 13);346assert.strictEqual(res.descriptionMatch[1].end, 14);347});348349test('scoreItem - proper target offset', function () {350const resource = URI.file('etem');351352const res = scoreItem(resource, 'teem', true, ResourceAccessor);353assert.ok(!res.score);354});355356test('scoreItem - proper target offset #2', function () {357const resource = URI.file('ede');358359const res = scoreItem(resource, 'de', true, ResourceAccessor);360361assert.strictEqual(res.labelMatch!.length, 1);362assert.strictEqual(res.labelMatch![0].start, 1);363assert.strictEqual(res.labelMatch![0].end, 3);364});365366test('scoreItem - proper target offset #3', function () {367const resource = URI.file('/src/vs/editor/browser/viewParts/lineNumbers/flipped-cursor-2x.svg');368369const res = scoreItem(resource, 'debug', true, ResourceAccessor);370371assert.strictEqual(res.descriptionMatch!.length, 3);372assert.strictEqual(res.descriptionMatch![0].start, 9);373assert.strictEqual(res.descriptionMatch![0].end, 10);374assert.strictEqual(res.descriptionMatch![1].start, 36);375assert.strictEqual(res.descriptionMatch![1].end, 37);376assert.strictEqual(res.descriptionMatch![2].start, 40);377assert.strictEqual(res.descriptionMatch![2].end, 41);378379assert.strictEqual(res.labelMatch!.length, 2);380assert.strictEqual(res.labelMatch![0].start, 9);381assert.strictEqual(res.labelMatch![0].end, 10);382assert.strictEqual(res.labelMatch![1].start, 20);383assert.strictEqual(res.labelMatch![1].end, 21);384});385386test('scoreItem - no match unless query contained in sequence', function () {387const resource = URI.file('abcde');388389const res = scoreItem(resource, 'edcda', true, ResourceAccessor);390assert.ok(!res.score);391});392393test('scoreItem - match if using slash or backslash (local, remote resource)', function () {394const localResource = URI.file('abcde/super/duper');395const remoteResource = URI.from({ scheme: Schemas.vscodeRemote, path: 'abcde/super/duper' });396397for (const resource of [localResource, remoteResource]) {398let res = scoreItem(resource, 'abcde\\super\\duper', true, ResourceAccessor);399assert.ok(res.score);400401res = scoreItem(resource, 'abcde\\super\\duper', true, ResourceWithSlashAccessor);402assert.ok(res.score);403404res = scoreItem(resource, 'abcde\\super\\duper', true, ResourceWithBackslashAccessor);405assert.ok(res.score);406407res = scoreItem(resource, 'abcde/super/duper', true, ResourceAccessor);408assert.ok(res.score);409410res = scoreItem(resource, 'abcde/super/duper', true, ResourceWithSlashAccessor);411assert.ok(res.score);412413res = scoreItem(resource, 'abcde/super/duper', true, ResourceWithBackslashAccessor);414assert.ok(res.score);415}416});417418test('scoreItem - ensure upper case bonus only applies on non-consecutive matches (bug #134723)', function () {419const resourceWithUpper = URI.file('ASDFasdfasdf');420const resourceAllLower = URI.file('asdfasdfasdf');421422assert.ok(scoreItem(resourceAllLower, 'asdf', true, ResourceAccessor).score > scoreItem(resourceWithUpper, 'asdf', true, ResourceAccessor).score);423});424425test('compareItemsByScore - identity', function () {426const resourceA = URI.file('/some/path/fileA.txt');427const resourceB = URI.file('/some/path/other/fileB.txt');428const resourceC = URI.file('/unrelated/some/path/other/fileC.txt');429430// Full resource A path431let query = ResourceAccessor.getItemPath(resourceA);432433let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));434assert.strictEqual(res[0], resourceA);435assert.strictEqual(res[1], resourceB);436assert.strictEqual(res[2], resourceC);437438res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));439assert.strictEqual(res[0], resourceA);440assert.strictEqual(res[1], resourceB);441assert.strictEqual(res[2], resourceC);442443// Full resource B path444query = ResourceAccessor.getItemPath(resourceB);445446res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));447assert.strictEqual(res[0], resourceB);448assert.strictEqual(res[1], resourceA);449assert.strictEqual(res[2], resourceC);450451res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));452assert.strictEqual(res[0], resourceB);453assert.strictEqual(res[1], resourceA);454assert.strictEqual(res[2], resourceC);455});456457test('compareFilesByScore - basename prefix', function () {458const resourceA = URI.file('/some/path/fileA.txt');459const resourceB = URI.file('/some/path/other/fileB.txt');460const resourceC = URI.file('/unrelated/some/path/other/fileC.txt');461462// Full resource A basename463let query = ResourceAccessor.getItemLabel(resourceA);464465let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));466assert.strictEqual(res[0], resourceA);467assert.strictEqual(res[1], resourceB);468assert.strictEqual(res[2], resourceC);469470res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));471assert.strictEqual(res[0], resourceA);472assert.strictEqual(res[1], resourceB);473assert.strictEqual(res[2], resourceC);474475// Full resource B basename476query = ResourceAccessor.getItemLabel(resourceB);477478res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));479assert.strictEqual(res[0], resourceB);480assert.strictEqual(res[1], resourceA);481assert.strictEqual(res[2], resourceC);482483res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));484assert.strictEqual(res[0], resourceB);485assert.strictEqual(res[1], resourceA);486assert.strictEqual(res[2], resourceC);487});488489test('compareFilesByScore - basename camelcase', function () {490const resourceA = URI.file('/some/path/fileA.txt');491const resourceB = URI.file('/some/path/other/fileB.txt');492const resourceC = URI.file('/unrelated/some/path/other/fileC.txt');493494// resource A camelcase495let query = 'fA';496497let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));498assert.strictEqual(res[0], resourceA);499assert.strictEqual(res[1], resourceB);500assert.strictEqual(res[2], resourceC);501502res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));503assert.strictEqual(res[0], resourceA);504assert.strictEqual(res[1], resourceB);505assert.strictEqual(res[2], resourceC);506507// resource B camelcase508query = 'fB';509510res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));511assert.strictEqual(res[0], resourceB);512assert.strictEqual(res[1], resourceA);513assert.strictEqual(res[2], resourceC);514515res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));516assert.strictEqual(res[0], resourceB);517assert.strictEqual(res[1], resourceA);518assert.strictEqual(res[2], resourceC);519});520521test('compareFilesByScore - basename scores', function () {522const resourceA = URI.file('/some/path/fileA.txt');523const resourceB = URI.file('/some/path/other/fileB.txt');524const resourceC = URI.file('/unrelated/some/path/other/fileC.txt');525526// Resource A part of basename527let query = 'fileA';528529let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));530assert.strictEqual(res[0], resourceA);531assert.strictEqual(res[1], resourceB);532assert.strictEqual(res[2], resourceC);533534res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));535assert.strictEqual(res[0], resourceA);536assert.strictEqual(res[1], resourceB);537assert.strictEqual(res[2], resourceC);538539// Resource B part of basename540query = 'fileB';541542res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));543assert.strictEqual(res[0], resourceB);544assert.strictEqual(res[1], resourceA);545assert.strictEqual(res[2], resourceC);546547res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));548assert.strictEqual(res[0], resourceB);549assert.strictEqual(res[1], resourceA);550assert.strictEqual(res[2], resourceC);551});552553test('compareFilesByScore - path scores', function () {554const resourceA = URI.file('/some/path/fileA.txt');555const resourceB = URI.file('/some/path/other/fileB.txt');556const resourceC = URI.file('/unrelated/some/path/other/fileC.txt');557558// Resource A part of path559let query = 'pathfileA';560561let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));562assert.strictEqual(res[0], resourceA);563assert.strictEqual(res[1], resourceB);564assert.strictEqual(res[2], resourceC);565566res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));567assert.strictEqual(res[0], resourceA);568assert.strictEqual(res[1], resourceB);569assert.strictEqual(res[2], resourceC);570571// Resource B part of path572query = 'pathfileB';573574res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));575assert.strictEqual(res[0], resourceB);576assert.strictEqual(res[1], resourceA);577assert.strictEqual(res[2], resourceC);578579res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));580assert.strictEqual(res[0], resourceB);581assert.strictEqual(res[1], resourceA);582assert.strictEqual(res[2], resourceC);583});584585test('compareFilesByScore - prefer shorter basenames', function () {586const resourceA = URI.file('/some/path/fileA.txt');587const resourceB = URI.file('/some/path/other/fileBLonger.txt');588const resourceC = URI.file('/unrelated/the/path/other/fileC.txt');589590// Resource A part of path591const query = 'somepath';592593let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));594assert.strictEqual(res[0], resourceA);595assert.strictEqual(res[1], resourceB);596assert.strictEqual(res[2], resourceC);597598res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));599assert.strictEqual(res[0], resourceA);600assert.strictEqual(res[1], resourceB);601assert.strictEqual(res[2], resourceC);602});603604test('compareFilesByScore - prefer shorter basenames (match on basename)', function () {605const resourceA = URI.file('/some/path/fileA.txt');606const resourceB = URI.file('/some/path/other/fileBLonger.txt');607const resourceC = URI.file('/unrelated/the/path/other/fileC.txt');608609// Resource A part of path610const query = 'file';611612let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));613assert.strictEqual(res[0], resourceA);614assert.strictEqual(res[1], resourceC);615assert.strictEqual(res[2], resourceB);616617res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));618assert.strictEqual(res[0], resourceA);619assert.strictEqual(res[1], resourceC);620assert.strictEqual(res[2], resourceB);621});622623test('compareFilesByScore - prefer shorter paths', function () {624const resourceA = URI.file('/some/path/fileA.txt');625const resourceB = URI.file('/some/path/other/fileB.txt');626const resourceC = URI.file('/unrelated/some/path/other/fileC.txt');627628// Resource A part of path629const query = 'somepath';630631let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));632assert.strictEqual(res[0], resourceA);633assert.strictEqual(res[1], resourceB);634assert.strictEqual(res[2], resourceC);635636res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));637assert.strictEqual(res[0], resourceA);638assert.strictEqual(res[1], resourceB);639assert.strictEqual(res[2], resourceC);640});641642test('compareFilesByScore - prefer shorter paths (bug #17443)', function () {643const resourceA = URI.file('config/test/t1.js');644const resourceB = URI.file('config/test.js');645const resourceC = URI.file('config/test/t2.js');646647const query = 'co/te';648649const res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));650assert.strictEqual(res[0], resourceB);651assert.strictEqual(res[1], resourceA);652assert.strictEqual(res[2], resourceC);653});654655test('compareFilesByScore - prefer matches in label over description if scores are otherwise equal', function () {656const resourceA = URI.file('parts/quick/arrow-left-dark.svg');657const resourceB = URI.file('parts/quickopen/quickopen.ts');658659const query = 'partsquick';660661const res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));662assert.strictEqual(res[0], resourceB);663assert.strictEqual(res[1], resourceA);664});665666test('compareFilesByScore - prefer camel case matches', function () {667const resourceA = URI.file('config/test/NullPointerException.java');668const resourceB = URI.file('config/test/nopointerexception.java');669670for (const query of ['npe', 'NPE']) {671let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));672assert.strictEqual(res[0], resourceA);673assert.strictEqual(res[1], resourceB);674675res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));676assert.strictEqual(res[0], resourceA);677assert.strictEqual(res[1], resourceB);678}679});680681test('compareFilesByScore - prefer more compact camel case matches', function () {682const resourceA = URI.file('config/test/openthisAnythingHandler.js');683const resourceB = URI.file('config/test/openthisisnotsorelevantforthequeryAnyHand.js');684685const query = 'AH';686687let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));688assert.strictEqual(res[0], resourceB);689assert.strictEqual(res[1], resourceA);690691res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));692assert.strictEqual(res[0], resourceB);693assert.strictEqual(res[1], resourceA);694});695696test('compareFilesByScore - prefer more compact matches (label)', function () {697const resourceA = URI.file('config/test/examasdaple.js');698const resourceB = URI.file('config/test/exampleasdaasd.ts');699700const query = 'xp';701702let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));703assert.strictEqual(res[0], resourceB);704assert.strictEqual(res[1], resourceA);705706res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));707assert.strictEqual(res[0], resourceB);708assert.strictEqual(res[1], resourceA);709});710711test('compareFilesByScore - prefer more compact matches (path)', function () {712const resourceA = URI.file('config/test/examasdaple/file.js');713const resourceB = URI.file('config/test/exampleasdaasd/file.ts');714715const query = 'xp';716717let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));718assert.strictEqual(res[0], resourceB);719assert.strictEqual(res[1], resourceA);720721res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));722assert.strictEqual(res[0], resourceB);723assert.strictEqual(res[1], resourceA);724});725726test('compareFilesByScore - prefer more compact matches (label and path)', function () {727const resourceA = URI.file('config/example/thisfile.ts');728const resourceB = URI.file('config/24234243244/example/file.js');729730const query = 'exfile';731732let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));733assert.strictEqual(res[0], resourceB);734assert.strictEqual(res[1], resourceA);735736res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));737assert.strictEqual(res[0], resourceB);738assert.strictEqual(res[1], resourceA);739});740741test('compareFilesByScore - avoid match scattering (bug #34210)', function () {742const resourceA = URI.file('node_modules1/bundle/lib/model/modules/ot1/index.js');743const resourceB = URI.file('node_modules1/bundle/lib/model/modules/un1/index.js');744const resourceC = URI.file('node_modules1/bundle/lib/model/modules/modu1/index.js');745const resourceD = URI.file('node_modules1/bundle/lib/model/modules/oddl1/index.js');746747let query = isWindows ? 'modu1\\index.js' : 'modu1/index.js';748749let res = [resourceA, resourceB, resourceC, resourceD].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));750assert.strictEqual(res[0], resourceC);751752res = [resourceC, resourceB, resourceA, resourceD].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));753assert.strictEqual(res[0], resourceC);754755query = isWindows ? 'un1\\index.js' : 'un1/index.js';756757res = [resourceA, resourceB, resourceC, resourceD].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));758assert.strictEqual(res[0], resourceB);759760res = [resourceC, resourceB, resourceA, resourceD].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));761assert.strictEqual(res[0], resourceB);762});763764test('compareFilesByScore - avoid match scattering (bug #21019 1.)', function () {765const resourceA = URI.file('app/containers/Services/NetworkData/ServiceDetails/ServiceLoad/index.js');766const resourceB = URI.file('app/containers/Services/NetworkData/ServiceDetails/ServiceDistribution/index.js');767const resourceC = URI.file('app/containers/Services/NetworkData/ServiceDetailTabs/ServiceTabs/StatVideo/index.js');768769const query = 'StatVideoindex';770771let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));772assert.strictEqual(res[0], resourceC);773774res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));775assert.strictEqual(res[0], resourceC);776});777778test('compareFilesByScore - avoid match scattering (bug #21019 2.)', function () {779const resourceA = URI.file('src/build-helper/store/redux.ts');780const resourceB = URI.file('src/repository/store/redux.ts');781782const query = 'reproreduxts';783784let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));785assert.strictEqual(res[0], resourceB);786787res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));788assert.strictEqual(res[0], resourceB);789});790791test('compareFilesByScore - avoid match scattering (bug #26649)', function () {792const resourceA = URI.file('photobook/src/components/AddPagesButton/index.js');793const resourceB = URI.file('photobook/src/components/ApprovalPageHeader/index.js');794const resourceC = URI.file('photobook/src/canvasComponents/BookPage/index.js');795796const query = 'bookpageIndex';797798let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));799assert.strictEqual(res[0], resourceC);800801res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));802assert.strictEqual(res[0], resourceC);803});804805test('compareFilesByScore - avoid match scattering (bug #33247)', function () {806const resourceA = URI.file('ui/src/utils/constants.js');807const resourceB = URI.file('ui/src/ui/Icons/index.js');808809const query = isWindows ? 'ui\\icons' : 'ui/icons';810811let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));812assert.strictEqual(res[0], resourceB);813814res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));815assert.strictEqual(res[0], resourceB);816});817818test('compareFilesByScore - avoid match scattering (bug #33247 comment)', function () {819const resourceA = URI.file('ui/src/components/IDInput/index.js');820const resourceB = URI.file('ui/src/ui/Input/index.js');821822const query = isWindows ? 'ui\\input\\index' : 'ui/input/index';823824let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));825assert.strictEqual(res[0], resourceB);826827res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));828assert.strictEqual(res[0], resourceB);829});830831test('compareFilesByScore - avoid match scattering (bug #36166)', function () {832const resourceA = URI.file('django/contrib/sites/locale/ga/LC_MESSAGES/django.mo');833const resourceB = URI.file('django/core/signals.py');834835const query = 'djancosig';836837let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));838assert.strictEqual(res[0], resourceB);839840res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));841assert.strictEqual(res[0], resourceB);842});843844test('compareFilesByScore - avoid match scattering (bug #32918)', function () {845const resourceA = URI.file('adsys/protected/config.php');846const resourceB = URI.file('adsys/protected/framework/smarty/sysplugins/smarty_internal_config.php');847const resourceC = URI.file('duowanVideo/wap/protected/config.php');848849const query = 'protectedconfig.php';850851let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));852assert.strictEqual(res[0], resourceA);853assert.strictEqual(res[1], resourceC);854assert.strictEqual(res[2], resourceB);855856res = [resourceC, resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));857assert.strictEqual(res[0], resourceA);858assert.strictEqual(res[1], resourceC);859assert.strictEqual(res[2], resourceB);860});861862test('compareFilesByScore - avoid match scattering (bug #14879)', function () {863const resourceA = URI.file('pkg/search/gradient/testdata/constraint_attrMatchString.yml');864const resourceB = URI.file('cmd/gradient/main.go');865866const query = 'gradientmain';867868let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));869assert.strictEqual(res[0], resourceB);870871res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));872assert.strictEqual(res[0], resourceB);873});874875test('compareFilesByScore - avoid match scattering (bug #14727 1)', function () {876const resourceA = URI.file('alpha-beta-cappa.txt');877const resourceB = URI.file('abc.txt');878879const query = 'abc';880881let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));882assert.strictEqual(res[0], resourceB);883884res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));885assert.strictEqual(res[0], resourceB);886});887888test('compareFilesByScore - avoid match scattering (bug #14727 2)', function () {889const resourceA = URI.file('xerxes-yak-zubba/index.js');890const resourceB = URI.file('xyz/index.js');891892const query = 'xyz';893894let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));895assert.strictEqual(res[0], resourceB);896897res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));898assert.strictEqual(res[0], resourceB);899});900901test('compareFilesByScore - avoid match scattering (bug #18381)', function () {902const resourceA = URI.file('AssymblyInfo.cs');903const resourceB = URI.file('IAsynchronousTask.java');904905const query = 'async';906907let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));908assert.strictEqual(res[0], resourceB);909910res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));911assert.strictEqual(res[0], resourceB);912});913914test('compareFilesByScore - avoid match scattering (bug #35572)', function () {915const resourceA = URI.file('static/app/source/angluar/-admin/-organization/-settings/layout/layout.js');916const resourceB = URI.file('static/app/source/angular/-admin/-project/-settings/_settings/settings.js');917918const query = 'partisettings';919920let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));921assert.strictEqual(res[0], resourceB);922923res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));924assert.strictEqual(res[0], resourceB);925});926927test('compareFilesByScore - avoid match scattering (bug #36810)', function () {928const resourceA = URI.file('Trilby.TrilbyTV.Web.Portal/Views/Systems/Index.cshtml');929const resourceB = URI.file('Trilby.TrilbyTV.Web.Portal/Areas/Admins/Views/Tips/Index.cshtml');930931const query = 'tipsindex.cshtml';932933let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));934assert.strictEqual(res[0], resourceB);935936res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));937assert.strictEqual(res[0], resourceB);938});939940test('compareFilesByScore - prefer shorter hit (bug #20546)', function () {941const resourceA = URI.file('editor/core/components/tests/list-view-spec.js');942const resourceB = URI.file('editor/core/components/list-view.js');943944const query = 'listview';945946let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));947assert.strictEqual(res[0], resourceB);948949res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));950assert.strictEqual(res[0], resourceB);951});952953test('compareFilesByScore - avoid match scattering (bug #12095)', function () {954const resourceA = URI.file('src/vs/workbench/contrib/files/common/explorerViewModel.ts');955const resourceB = URI.file('src/vs/workbench/contrib/files/browser/views/explorerView.ts');956const resourceC = URI.file('src/vs/workbench/contrib/files/browser/views/explorerViewer.ts');957958const query = 'filesexplorerview.ts';959960let res = [resourceA, resourceB, resourceC].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));961assert.strictEqual(res[0], resourceB);962963res = [resourceA, resourceC, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));964assert.strictEqual(res[0], resourceB);965});966967test('compareFilesByScore - prefer case match (bug #96122)', function () {968const resourceA = URI.file('lists.php');969const resourceB = URI.file('lib/Lists.php');970971const query = 'Lists.php';972973let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));974assert.strictEqual(res[0], resourceB);975976res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));977assert.strictEqual(res[0], resourceB);978});979980test('compareFilesByScore - prefer shorter match (bug #103052) - foo bar', function () {981const resourceA = URI.file('app/emails/foo.bar.js');982const resourceB = URI.file('app/emails/other-footer.other-bar.js');983984for (const query of ['foo bar', 'foobar']) {985let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));986assert.strictEqual(res[0], resourceA);987assert.strictEqual(res[1], resourceB);988989res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));990assert.strictEqual(res[0], resourceA);991assert.strictEqual(res[1], resourceB);992}993});994995test('compareFilesByScore - prefer shorter match (bug #103052) - payment model', function () {996const resourceA = URI.file('app/components/payment/payment.model.js');997const resourceB = URI.file('app/components/online-payments-history/online-payments-history.model.js');998999for (const query of ['payment model', 'paymentmodel']) {1000let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));1001assert.strictEqual(res[0], resourceA);1002assert.strictEqual(res[1], resourceB);10031004res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));1005assert.strictEqual(res[0], resourceA);1006assert.strictEqual(res[1], resourceB);1007}1008});10091010test('compareFilesByScore - prefer shorter match (bug #103052) - color', function () {1011const resourceA = URI.file('app/constants/color.js');1012const resourceB = URI.file('app/components/model/input/pick-avatar-color.js');10131014for (const query of ['color js', 'colorjs']) {1015let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));1016assert.strictEqual(res[0], resourceA);1017assert.strictEqual(res[1], resourceB);10181019res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));1020assert.strictEqual(res[0], resourceA);1021assert.strictEqual(res[1], resourceB);1022}1023});10241025test('compareFilesByScore - prefer strict case prefix', function () {1026const resourceA = URI.file('app/constants/color.js');1027const resourceB = URI.file('app/components/model/input/Color.js');10281029let query = 'Color';10301031let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));1032assert.strictEqual(res[0], resourceB);1033assert.strictEqual(res[1], resourceA);10341035res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));1036assert.strictEqual(res[0], resourceB);1037assert.strictEqual(res[1], resourceA);10381039query = 'color';10401041res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));1042assert.strictEqual(res[0], resourceA);1043assert.strictEqual(res[1], resourceB);10441045res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));1046assert.strictEqual(res[0], resourceA);1047assert.strictEqual(res[1], resourceB);1048});10491050test('compareFilesByScore - prefer prefix (bug #103052)', function () {1051const resourceA = URI.file('test/smoke/src/main.ts');1052const resourceB = URI.file('src/vs/editor/common/services/semantikTokensProviderStyling.ts');10531054const query = 'smoke main.ts';10551056let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));1057assert.strictEqual(res[0], resourceA);1058assert.strictEqual(res[1], resourceB);10591060res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));1061assert.strictEqual(res[0], resourceA);1062assert.strictEqual(res[1], resourceB);1063});10641065test('compareFilesByScore - boost better prefix match if multiple queries are used', function () {1066const resourceA = URI.file('src/vs/workbench/services/host/browser/browserHostService.ts');1067const resourceB = URI.file('src/vs/workbench/browser/workbench.ts');10681069for (const query of ['workbench.ts browser', 'browser workbench.ts', 'browser workbench', 'workbench browser']) {1070let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));1071assert.strictEqual(res[0], resourceB);1072assert.strictEqual(res[1], resourceA);10731074res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));1075assert.strictEqual(res[0], resourceB);1076assert.strictEqual(res[1], resourceA);1077}1078});10791080test('compareFilesByScore - boost shorter prefix match if multiple queries are used', function () {1081const resourceA = URI.file('src/vs/workbench/node/actions/windowActions.ts');1082const resourceB = URI.file('src/vs/workbench/electron-node/window.ts');10831084for (const query of ['window node', 'window.ts node']) {1085let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));1086assert.strictEqual(res[0], resourceB);1087assert.strictEqual(res[1], resourceA);10881089res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));1090assert.strictEqual(res[0], resourceB);1091assert.strictEqual(res[1], resourceA);1092}1093});10941095test('compareFilesByScore - skip preference on label match when using path sep', function () {1096const resourceA = URI.file('djangosite/ufrela/def.py');1097const resourceB = URI.file('djangosite/urls/default.py');10981099const query = 'url/def';11001101let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));1102assert.strictEqual(res[0], resourceB);1103assert.strictEqual(res[1], resourceA);11041105res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));1106assert.strictEqual(res[0], resourceB);1107assert.strictEqual(res[1], resourceA);1108});11091110test('compareFilesByScore - boost shorter prefix match if multiple queries are used (#99171)', function () {1111const resourceA = URI.file('mesh_editor_lifetime_job.h');1112const resourceB = URI.file('lifetime_job.h');11131114const query = 'm life, life m';11151116let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));1117assert.strictEqual(res[0], resourceB);1118assert.strictEqual(res[1], resourceA);11191120res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));1121assert.strictEqual(res[0], resourceB);1122assert.strictEqual(res[1], resourceA);1123});11241125test('compareFilesByScore - boost consecutive matches in the beginning over end', function () {1126const resourceA = URI.file('src/vs/server/node/extensionHostStatusService.ts');1127const resourceB = URI.file('src/vs/workbench/browser/parts/notifications/notificationsStatus.ts');11281129const query = 'notStatus';11301131let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));1132assert.strictEqual(res[0], resourceB);1133assert.strictEqual(res[1], resourceA);11341135res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));1136assert.strictEqual(res[0], resourceB);1137assert.strictEqual(res[1], resourceA);1138});11391140test('prepareQuery', () => {1141assert.strictEqual(prepareQuery(' f*a ').normalized, 'fa');1142assert.strictEqual(prepareQuery(' f…a ').normalized, 'fa');1143assert.strictEqual(prepareQuery('main#').normalized, 'main');1144assert.strictEqual(prepareQuery('main#').original, 'main#');1145assert.strictEqual(prepareQuery('foo*').normalized, 'foo');1146assert.strictEqual(prepareQuery('foo*').original, 'foo*');1147assert.strictEqual(prepareQuery('model Tester.ts').original, 'model Tester.ts');1148assert.strictEqual(prepareQuery('model Tester.ts').originalLowercase, 'model Tester.ts'.toLowerCase());1149assert.strictEqual(prepareQuery('model Tester.ts').normalized, 'modelTester.ts');1150assert.strictEqual(prepareQuery('model Tester.ts').expectContiguousMatch, false); // doesn't have quotes in it1151assert.strictEqual(prepareQuery('Model Tester.ts').normalizedLowercase, 'modeltester.ts');1152assert.strictEqual(prepareQuery('ModelTester.ts').containsPathSeparator, false);1153assert.strictEqual(prepareQuery('Model' + sep + 'Tester.ts').containsPathSeparator, true);1154assert.strictEqual(prepareQuery('"hello"').expectContiguousMatch, true);1155assert.strictEqual(prepareQuery('"hello"').normalized, 'hello');11561157// with spaces1158let query = prepareQuery('He*llo World');1159assert.strictEqual(query.original, 'He*llo World');1160assert.strictEqual(query.normalized, 'HelloWorld');1161assert.strictEqual(query.normalizedLowercase, 'HelloWorld'.toLowerCase());1162assert.strictEqual(query.values?.length, 2);1163assert.strictEqual(query.values?.[0].original, 'He*llo');1164assert.strictEqual(query.values?.[0].normalized, 'Hello');1165assert.strictEqual(query.values?.[0].normalizedLowercase, 'Hello'.toLowerCase());1166assert.strictEqual(query.values?.[1].original, 'World');1167assert.strictEqual(query.values?.[1].normalized, 'World');1168assert.strictEqual(query.values?.[1].normalizedLowercase, 'World'.toLowerCase());11691170const restoredQuery = pieceToQuery(query.values);1171assert.strictEqual(restoredQuery.original, query.original);1172assert.strictEqual(restoredQuery.values?.length, query.values?.length);1173assert.strictEqual(restoredQuery.containsPathSeparator, query.containsPathSeparator);11741175// with spaces that are empty1176query = prepareQuery(' Hello World ');1177assert.strictEqual(query.original, ' Hello World ');1178assert.strictEqual(query.originalLowercase, ' Hello World '.toLowerCase());1179assert.strictEqual(query.normalized, 'HelloWorld');1180assert.strictEqual(query.normalizedLowercase, 'HelloWorld'.toLowerCase());1181assert.strictEqual(query.values?.length, 2);1182assert.strictEqual(query.values?.[0].original, 'Hello');1183assert.strictEqual(query.values?.[0].originalLowercase, 'Hello'.toLowerCase());1184assert.strictEqual(query.values?.[0].normalized, 'Hello');1185assert.strictEqual(query.values?.[0].normalizedLowercase, 'Hello'.toLowerCase());1186assert.strictEqual(query.values?.[1].original, 'World');1187assert.strictEqual(query.values?.[1].originalLowercase, 'World'.toLowerCase());1188assert.strictEqual(query.values?.[1].normalized, 'World');1189assert.strictEqual(query.values?.[1].normalizedLowercase, 'World'.toLowerCase());11901191// Path related1192if (isWindows) {1193assert.strictEqual(prepareQuery('C:\\some\\path').pathNormalized, 'C:\\some\\path');1194assert.strictEqual(prepareQuery('C:\\some\\path').normalized, 'C:\\some\\path');1195assert.strictEqual(prepareQuery('C:\\some\\path').containsPathSeparator, true);1196assert.strictEqual(prepareQuery('C:/some/path').pathNormalized, 'C:\\some\\path');1197assert.strictEqual(prepareQuery('C:/some/path').normalized, 'C:\\some\\path');1198assert.strictEqual(prepareQuery('C:/some/path').containsPathSeparator, true);1199} else {1200assert.strictEqual(prepareQuery('/some/path').pathNormalized, '/some/path');1201assert.strictEqual(prepareQuery('/some/path').normalized, '/some/path');1202assert.strictEqual(prepareQuery('/some/path').containsPathSeparator, true);1203assert.strictEqual(prepareQuery('\\some\\path').pathNormalized, '/some/path');1204assert.strictEqual(prepareQuery('\\some\\path').normalized, '/some/path');1205assert.strictEqual(prepareQuery('\\some\\path').containsPathSeparator, true);1206}1207});12081209test('fuzzyScore2 (matching)', function () {1210const target = 'HelLo-World';12111212for (const offset of [0, 3]) {1213let [score, matches] = _doScore2(offset === 0 ? target : `123${target}`, 'HelLo-World', offset);12141215assert.ok(score);1216assert.strictEqual(matches.length, 1);1217assert.strictEqual(matches[0].start, 0 + offset);1218assert.strictEqual(matches[0].end, target.length + offset);12191220[score, matches] = _doScore2(offset === 0 ? target : `123${target}`, 'HW', offset);12211222assert.ok(score);1223assert.strictEqual(matches.length, 2);1224assert.strictEqual(matches[0].start, 0 + offset);1225assert.strictEqual(matches[0].end, 1 + offset);1226assert.strictEqual(matches[1].start, 6 + offset);1227assert.strictEqual(matches[1].end, 7 + offset);1228}1229});12301231test('fuzzyScore2 (multiple queries)', function () {1232const target = 'HelLo-World';12331234const [firstSingleScore, firstSingleMatches] = _doScore2(target, 'HelLo');1235const [secondSingleScore, secondSingleMatches] = _doScore2(target, 'World');1236const firstAndSecondSingleMatches = [...firstSingleMatches || [], ...secondSingleMatches || []];12371238let [multiScore, multiMatches] = _doScore2(target, 'HelLo World');12391240function assertScore() {1241assert.ok(multiScore ?? 0 >= ((firstSingleScore ?? 0) + (secondSingleScore ?? 0)));1242for (let i = 0; multiMatches && i < multiMatches.length; i++) {1243const multiMatch = multiMatches[i];1244const firstAndSecondSingleMatch = firstAndSecondSingleMatches[i];12451246if (multiMatch && firstAndSecondSingleMatch) {1247assert.strictEqual(multiMatch.start, firstAndSecondSingleMatch.start);1248assert.strictEqual(multiMatch.end, firstAndSecondSingleMatch.end);1249} else {1250assert.fail();1251}1252}1253}12541255function assertNoScore() {1256assert.strictEqual(multiScore, undefined);1257assert.strictEqual(multiMatches.length, 0);1258}12591260assertScore();12611262[multiScore, multiMatches] = _doScore2(target, 'World HelLo');1263assertScore();12641265[multiScore, multiMatches] = _doScore2(target, 'World HelLo World');1266assertScore();12671268[multiScore, multiMatches] = _doScore2(target, 'World HelLo Nothing');1269assertNoScore();12701271[multiScore, multiMatches] = _doScore2(target, 'More Nothing');1272assertNoScore();1273});12741275test('fuzzyScore2 (#95716)', function () {1276const target = '# ❌ Wow';12771278const score = _doScore2(target, '❌');1279assert.ok(score);1280assert.ok(typeof score[0] === 'number');1281assert.ok(score[1].length > 0);1282});12831284test('Using quotes should expect contiguous matches match', function () {1285// missing the "i" in the query1286assert.strictEqual(_doScore('contiguous', '"contguous"')[0], 0);12871288const score = _doScore('contiguous', '"contiguous"');1289assert.ok(score[0] > 0);1290});12911292test('Using quotes should highlight contiguous indexes', function () {1293const score = _doScore('2021-7-26.md', '"26"');1294assert.strictEqual(score[0], 14);12951296// The indexes of the 2 and 6 of "26"1297assert.strictEqual(score[1][0], 7);1298assert.strictEqual(score[1][1], 8);1299});13001301test('Workspace symbol search with special characters (#, *)', function () {1302// Simulates the scenario from the issue where rust-analyzer uses # and * as query modifiers1303// The original query (with special chars) should reach the language server1304// but normalized query (without special chars) should be used for fuzzy matching13051306// Test #: User types "main#", language server returns "main" symbol1307let query = prepareQuery('main#');1308assert.strictEqual(query.original, 'main#'); // Sent to language server1309assert.strictEqual(query.normalized, 'main'); // Used for fuzzy matching1310let [score, matches] = _doScore2('main', 'main#');1311assert.ok(typeof score === 'number' && score > 0, 'Should match "main" symbol when query is "main#"');1312assert.ok(matches.length > 0);13131314// Test *: User types "foo*", language server returns "foo" symbol1315query = prepareQuery('foo*');1316assert.strictEqual(query.original, 'foo*'); // Sent to language server1317assert.strictEqual(query.normalized, 'foo'); // Used for fuzzy matching1318[score, matches] = _doScore2('foo', 'foo*');1319assert.ok(typeof score === 'number' && score > 0, 'Should match "foo" symbol when query is "foo*"');1320assert.ok(matches.length > 0);13211322// Test both: User types "MyClass#*", should match "MyClass"1323query = prepareQuery('MyClass#*');1324assert.strictEqual(query.original, 'MyClass#*');1325assert.strictEqual(query.normalized, 'MyClass');1326[score, matches] = _doScore2('MyClass', 'MyClass#*');1327assert.ok(typeof score === 'number' && score > 0, 'Should match "MyClass" symbol when query is "MyClass#*"');1328assert.ok(matches.length > 0);13291330// Test fuzzy matching still works: User types "MC#", should match "MyClass"1331query = prepareQuery('MC#');1332assert.strictEqual(query.original, 'MC#');1333assert.strictEqual(query.normalized, 'MC');1334[score, matches] = _doScore2('MyClass', 'MC#');1335assert.ok(typeof score === 'number' && score > 0, 'Should fuzzy match "MyClass" symbol when query is "MC#"');1336assert.ok(matches.length > 0);13371338// Make sure leading # or # in the middle are not removed.1339query = prepareQuery('#SpecialFunction');1340assert.strictEqual(query.original, '#SpecialFunction');1341assert.strictEqual(query.normalized, '#SpecialFunction');1342[score, matches] = _doScore2('#SpecialFunction', '#SpecialFunction');1343assert.ok(typeof score === 'number' && score > 0, 'Should match "#SpecialFunction" symbol when query is "#SpecialFunction"');1344assert.ok(matches.length > 0);13451346// Make sure standalone # is not removed1347query = prepareQuery('#');1348assert.strictEqual(query.original, '#');1349assert.strictEqual(query.normalized, '#', 'Standalone # should not be removed');1350[score, matches] = _doScore2('#', '#');1351assert.ok(typeof score === 'number' && score > 0, 'Should match "#" symbol when query is "#"');1352assert.ok(matches.length > 0);1353});13541355ensureNoDisposablesAreLeakedInTestSuite();1356});135713581359