From a636ab936951aadb8229b22179bf34d93f724419 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 27 Apr 2020 13:32:05 +0200 Subject: [PATCH] Allow Quick Open (Ctrl-P) to Prioritize Exact Case Matches (fix #96122) --- src/vs/base/common/fuzzyScorer.ts | 53 ++++++++++++++------- src/vs/base/test/common/fuzzyScorer.test.ts | 13 +++++ 2 files changed, 48 insertions(+), 18 deletions(-) diff --git a/src/vs/base/common/fuzzyScorer.ts b/src/vs/base/common/fuzzyScorer.ts index a79a15fdb81..d32f1c5c2ce 100644 --- a/src/vs/base/common/fuzzyScorer.ts +++ b/src/vs/base/common/fuzzyScorer.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { compareAnything } from 'vs/base/common/comparers'; -import { matchesPrefix, IMatch, matchesCamelCase, isUpper, fuzzyScore, createMatches as createFuzzyMatches } from 'vs/base/common/filters'; +import { matchesPrefix, IMatch, matchesCamelCase, isUpper, fuzzyScore, createMatches as createFuzzyMatches, matchesStrictPrefix } from 'vs/base/common/filters'; import { sep } from 'vs/base/common/path'; import { isWindows, isLinux } from 'vs/base/common/platform'; import { stripWildcards, equalsIgnoreCase } from 'vs/base/common/strings'; @@ -370,9 +370,10 @@ export interface IItemAccessor { } const PATH_IDENTITY_SCORE = 1 << 18; -const LABEL_PREFIX_SCORE = 1 << 17; -const LABEL_CAMELCASE_SCORE = 1 << 16; -const LABEL_SCORE_THRESHOLD = 1 << 15; +const LABEL_PREFIX_SCORE_MATCHCASE = 1 << 17; +const LABEL_PREFIX_SCORE_IGNORECASE = 1 << 16; +const LABEL_CAMELCASE_SCORE = 1 << 15; +const LABEL_SCORE_THRESHOLD = 1 << 14; export function scoreItemFuzzy(item: T, query: IPreparedQuery, fuzzy: boolean, accessor: IItemAccessor, cache: FuzzyScorerCache): IItemScore { if (!item || !query.normalized) { @@ -458,13 +459,14 @@ function doScoreItemFuzzySingle(label: string, description: string | undefined, // Prefer label matches if told so if (preferLabelMatches) { - // Treat prefix matches on the label second highest - const prefixLabelMatch = matchesPrefix(query.normalized, label); - if (prefixLabelMatch) { - return { score: LABEL_PREFIX_SCORE, labelMatch: prefixLabelMatch }; + // Treat prefix matches on the label highest + const prefixLabelMatchIgnoreCase = matchesPrefix(query.normalized, label); + if (prefixLabelMatchIgnoreCase) { + const prefixLabelMatchStrictCase = matchesStrictPrefix(query.normalized, label); + return { score: prefixLabelMatchStrictCase ? LABEL_PREFIX_SCORE_MATCHCASE : LABEL_PREFIX_SCORE_IGNORECASE, labelMatch: prefixLabelMatchStrictCase || prefixLabelMatchIgnoreCase }; } - // Treat camelcase matches on the label third highest + // Treat camelcase matches on the label second highest const camelcaseLabelMatch = matchesCamelCase(query.normalized, label); if (camelcaseLabelMatch) { return { score: LABEL_CAMELCASE_SCORE, labelMatch: camelcaseLabelMatch }; @@ -600,10 +602,10 @@ export function compareItemsByFuzzyScore(itemA: T, itemB: T, query: IPrepared } } - // 2.) prefer label prefix matches - if (scoreA === LABEL_PREFIX_SCORE || scoreB === LABEL_PREFIX_SCORE) { + // 2.) prefer label prefix matches (match case) + if (scoreA === LABEL_PREFIX_SCORE_MATCHCASE || scoreB === LABEL_PREFIX_SCORE_MATCHCASE) { if (scoreA !== scoreB) { - return scoreA === LABEL_PREFIX_SCORE ? -1 : 1; + return scoreA === LABEL_PREFIX_SCORE_MATCHCASE ? -1 : 1; } const labelA = accessor.getItemLabel(itemA) || ''; @@ -615,7 +617,22 @@ export function compareItemsByFuzzyScore(itemA: T, itemB: T, query: IPrepared } } - // 3.) prefer camelcase matches + // 3.) prefer label prefix matches (ignore case) + if (scoreA === LABEL_PREFIX_SCORE_IGNORECASE || scoreB === LABEL_PREFIX_SCORE_IGNORECASE) { + if (scoreA !== scoreB) { + return scoreA === LABEL_PREFIX_SCORE_IGNORECASE ? -1 : 1; + } + + const labelA = accessor.getItemLabel(itemA) || ''; + const labelB = accessor.getItemLabel(itemB) || ''; + + // prefer shorter names when both match on label prefix + if (labelA.length !== labelB.length) { + return labelA.length - labelB.length; + } + } + + // 4.) prefer camelcase matches if (scoreA === LABEL_CAMELCASE_SCORE || scoreB === LABEL_CAMELCASE_SCORE) { if (scoreA !== scoreB) { return scoreA === LABEL_CAMELCASE_SCORE ? -1 : 1; @@ -636,7 +653,7 @@ export function compareItemsByFuzzyScore(itemA: T, itemB: T, query: IPrepared } } - // 4.) prefer label scores + // 5.) prefer label scores if (scoreA > LABEL_SCORE_THRESHOLD || scoreB > LABEL_SCORE_THRESHOLD) { if (scoreB < LABEL_SCORE_THRESHOLD) { return -1; @@ -647,12 +664,12 @@ export function compareItemsByFuzzyScore(itemA: T, itemB: T, query: IPrepared } } - // 5.) compare by score + // 6.) compare by score if (scoreA !== scoreB) { return scoreA > scoreB ? -1 : 1; } - // 6.) prefer matches in label over non-label matches + // 7.) prefer matches in label over non-label matches const itemAHasLabelMatches = Array.isArray(itemScoreA.labelMatch) && itemScoreA.labelMatch.length > 0; const itemBHasLabelMatches = Array.isArray(itemScoreB.labelMatch) && itemScoreB.labelMatch.length > 0; if (itemAHasLabelMatches && !itemBHasLabelMatches) { @@ -661,14 +678,14 @@ export function compareItemsByFuzzyScore(itemA: T, itemB: T, query: IPrepared return 1; } - // 7.) scores are identical, prefer more compact matches (label and description) + // 8.) scores are identical, prefer more compact matches (label and description) const itemAMatchDistance = computeLabelAndDescriptionMatchDistance(itemA, itemScoreA, accessor); const itemBMatchDistance = computeLabelAndDescriptionMatchDistance(itemB, itemScoreB, accessor); if (itemAMatchDistance && itemBMatchDistance && itemAMatchDistance !== itemBMatchDistance) { return itemBMatchDistance > itemAMatchDistance ? -1 : 1; } - // 7.) at this point, scores are identical and match compactness as well + // 9.) at this point, scores are identical and match compactness as well // for both items so we start to use the fallback compare return fallbackCompare(itemA, itemB, query, accessor); } diff --git a/src/vs/base/test/common/fuzzyScorer.test.ts b/src/vs/base/test/common/fuzzyScorer.test.ts index bc961cb66d8..a40a330ff92 100644 --- a/src/vs/base/test/common/fuzzyScorer.test.ts +++ b/src/vs/base/test/common/fuzzyScorer.test.ts @@ -912,6 +912,19 @@ suite('Fuzzy Scorer', () => { assert.equal(res[0], resourceB); }); + test('compareFilesByScore - prefer case match (bug #96122)', function () { + const resourceA = URI.file('lists.php'); + const resourceB = URI.file('lib/Lists.php'); + + let query = 'Lists.php'; + + let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); + assert.equal(res[0], resourceB); + + res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); + assert.equal(res[0], resourceB); + }); + test('prepareQuery', () => { assert.equal(scorer.prepareQuery(' f*a ').normalized, 'fa'); assert.equal(scorer.prepareQuery('model Tester.ts').original, 'model Tester.ts'); -- GitLab