diff --git a/src/vs/base/common/fuzzyScorer.ts b/src/vs/base/common/fuzzyScorer.ts index 903272e616a2b..2cf5200cf69ab 100644 --- a/src/vs/base/common/fuzzyScorer.ts +++ b/src/vs/base/common/fuzzyScorer.ts @@ -52,7 +52,7 @@ export function scoreFuzzy(target: string, query: string, queryLower: string, fu } } - const res = doScore(query, queryLower, queryLength, target, targetLower, targetLength); + const res = doScoreFuzzy(query, queryLower, queryLength, target, targetLower, targetLength); // if (DEBUG) { // console.log(`%cFinal Score: ${res[0]}`, 'font-weight: bold'); @@ -62,7 +62,7 @@ export function scoreFuzzy(target: string, query: string, queryLower: string, fu return res; } -function doScore(query: string, queryLower: string, queryLength: number, target: string, targetLower: string, targetLength: number): FuzzyScore { +function doScoreFuzzy(query: string, queryLower: string, queryLength: number, target: string, targetLower: string, targetLength: number): FuzzyScore { const scores: number[] = []; const matches: number[] = []; @@ -264,33 +264,35 @@ function scoreSeparatorAtPos(charCode: number): number { //#region Alternate fuzzy scorer implementation that is e.g. used for symbols -export type FuzzyScore2 = [number /* Score*/, IMatch[]]; +export type FuzzyScore2 = [number /* score*/, IMatch[]]; -export function scoreFuzzy2(target: string, query: IPreparedQuery, matchOffset = 0): FuzzyScore2 | undefined { +const NO_SCORE2: FuzzyScore2 = [NO_MATCH, []]; + +export function scoreFuzzy2(target: string, query: IPreparedQuery, patternStart = 0, matchOffset = 0): FuzzyScore2 { // Score: multiple inputs if (query.values && query.values.length > 1) { - return doScoreFuzzy2Multiple(target, query.values, matchOffset); + return doScoreFuzzy2Multiple(target, query.values, patternStart, matchOffset); } // Score: single input - return doScoreFuzzy2Single(target, query, matchOffset); + return doScoreFuzzy2Single(target, query, patternStart, matchOffset); } -function doScoreFuzzy2Multiple(target: string, query: IPreparedQueryPiece[], matchOffset: number): FuzzyScore2 | undefined { +function doScoreFuzzy2Multiple(target: string, query: IPreparedQueryPiece[], patternStart: number, matchOffset: number): FuzzyScore2 { let totalScore = 0; const totalMatches: IMatch[] = []; for (const queryPiece of query) { - const score = doScoreFuzzy2Single(target, queryPiece, matchOffset); + const [score, matches] = doScoreFuzzy2Single(target, queryPiece, patternStart, matchOffset); if (!score) { // if a single query value does not match, return with // no score entirely, we require all queries to match - return undefined; + return NO_SCORE2; } - totalScore += score[0]; - totalMatches.push(...score[1]); + totalScore += score; + totalMatches.push(...matches); } // if we have a score, ensure that the positions are @@ -298,10 +300,10 @@ function doScoreFuzzy2Multiple(target: string, query: IPreparedQueryPiece[], mat return [totalScore, normalizeMatches(totalMatches)]; } -function doScoreFuzzy2Single(target: string, query: IPreparedQueryPiece, matchOffset: number): FuzzyScore2 | undefined { - const score = fuzzyScore(query.original, query.originalLowercase, 0, target, target.toLowerCase(), 0, true); +function doScoreFuzzy2Single(target: string, query: IPreparedQueryPiece, patternStart: number, matchOffset: number): FuzzyScore2 { + const score = fuzzyScore(query.original, query.originalLowercase, patternStart, target, target.toLowerCase(), 0, true); if (!score) { - return undefined; + return NO_SCORE2; } return [score[0], createFuzzyMatches(score, matchOffset)]; @@ -382,13 +384,13 @@ export function scoreItemFuzzy(item: T, query: IPreparedQuery, fuzzy: boolean return cached; } - const itemScore = doScoreItem(label, description, accessor.getItemPath(item), query, fuzzy); + const itemScore = doScoreItemFuzzy(label, description, accessor.getItemPath(item), query, fuzzy); cache[cacheHash] = itemScore; return itemScore; } -function doScoreItem(label: string, description: string | undefined, path: string | undefined, query: IPreparedQuery, fuzzy: boolean): IItemScore { +function doScoreItemFuzzy(label: string, description: string | undefined, path: string | undefined, query: IPreparedQuery, fuzzy: boolean): IItemScore { const preferLabelMatches = !path || !query.containsPathSeparator; // Treat identity matches on full path highest @@ -398,20 +400,20 @@ function doScoreItem(label: string, description: string | undefined, path: strin // Score: multiple inputs if (query.values && query.values.length > 1) { - return doScoreItemMultiple(label, description, path, query.values, preferLabelMatches, fuzzy); + return doScoreItemFuzzyMultiple(label, description, path, query.values, preferLabelMatches, fuzzy); } // Score: single input - return doScoreItemSingle(label, description, path, query, preferLabelMatches, fuzzy); + return doScoreItemFuzzySingle(label, description, path, query, preferLabelMatches, fuzzy); } -function doScoreItemMultiple(label: string, description: string | undefined, path: string | undefined, query: IPreparedQueryPiece[], preferLabelMatches: boolean, fuzzy: boolean): IItemScore { +function doScoreItemFuzzyMultiple(label: string, description: string | undefined, path: string | undefined, query: IPreparedQueryPiece[], preferLabelMatches: boolean, fuzzy: boolean): IItemScore { let totalScore = 0; const totalLabelMatches: IMatch[] = []; const totalDescriptionMatches: IMatch[] = []; for (const queryPiece of query) { - const { score, labelMatch, descriptionMatch } = doScoreItemSingle(label, description, path, queryPiece, preferLabelMatches, fuzzy); + const { score, labelMatch, descriptionMatch } = doScoreItemFuzzySingle(label, description, path, queryPiece, preferLabelMatches, fuzzy); if (score === NO_MATCH) { // if a single query value does not match, return with // no score entirely, we require all queries to match @@ -437,7 +439,7 @@ function doScoreItemMultiple(label: string, description: string | undefined, pat }; } -function doScoreItemSingle(label: string, description: string | undefined, path: string | undefined, query: IPreparedQueryPiece, preferLabelMatches: boolean, fuzzy: boolean): IItemScore { +function doScoreItemFuzzySingle(label: string, description: string | undefined, path: string | undefined, query: IPreparedQueryPiece, preferLabelMatches: boolean, fuzzy: boolean): IItemScore { // Prefer label matches if told so if (preferLabelMatches) { diff --git a/src/vs/base/test/common/fuzzyScorer.test.ts b/src/vs/base/test/common/fuzzyScorer.test.ts index 35f3f3a11ba71..5849f5139b5d1 100644 --- a/src/vs/base/test/common/fuzzyScorer.test.ts +++ b/src/vs/base/test/common/fuzzyScorer.test.ts @@ -48,15 +48,10 @@ function _doScore(target: string, query: string, fuzzy: boolean): scorer.FuzzySc return scorer.scoreFuzzy(target, preparedQuery.normalized, preparedQuery.normalizedLowercase, fuzzy); } -function _doScore2(target: string, query: string): scorer.FuzzyScore2 | [undefined, undefined] { +function _doScore2(target: string, query: string): scorer.FuzzyScore2 { const preparedQuery = scorer.prepareQuery(query); - const res = scorer.scoreFuzzy2(target, preparedQuery); - if (res) { - return res; - } - - return [undefined, undefined]; + return scorer.scoreFuzzy2(target, preparedQuery); } function scoreItem(item: T, query: string, fuzzy: boolean, accessor: scorer.IItemAccessor, cache: scorer.FuzzyScorerCache): scorer.IItemScore { @@ -898,6 +893,11 @@ suite('Fuzzy Scorer', () => { assert.equal(query.values?.[1].normalized, 'World'); assert.equal(query.values?.[1].normalizedLowercase, 'World'.toLowerCase()); + let restoredQuery = scorer.pieceToQuery(query.values!); + assert.equal(restoredQuery.original, query.original); + assert.equal(restoredQuery.values?.length, query.values?.length); + assert.equal(restoredQuery.containsPathSeparator, query.containsPathSeparator); + // with spaces that are empty query = scorer.prepareQuery(' Hello World '); assert.equal(query.original, ' Hello World '); @@ -957,8 +957,8 @@ suite('Fuzzy Scorer', () => { } function assertNoScore() { - assert.equal(multiScore, undefined); - assert.equal(multiMatches, undefined); + assert.equal(multiScore, 0); + assert.equal(multiMatches.length, 0); } assertScore(); diff --git a/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts b/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts index fd4712304f04e..30079dd5e7fb6 100644 --- a/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts @@ -15,7 +15,8 @@ import { DocumentSymbol, SymbolKinds, SymbolTag, DocumentSymbolProviderRegistry, import { OutlineModel, OutlineElement } from 'vs/editor/contrib/documentSymbols/outlineModel'; import { values } from 'vs/base/common/collections'; import { trim, format } from 'vs/base/common/strings'; -import { prepareQuery, IPreparedQuery, FuzzyScore2, pieceToQuery, scoreFuzzy2 } from 'vs/base/common/fuzzyScorer'; +import { prepareQuery, IPreparedQuery, pieceToQuery, scoreFuzzy2 } from 'vs/base/common/fuzzyScorer'; +import { IMatch } from 'vs/base/common/filters'; export interface IGotoSymbolQuickPickItem extends IQuickPickItem { kind: SymbolKind, @@ -229,13 +230,16 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit } } - let symbolScore: FuzzyScore2 | undefined = undefined; - let containerScore: FuzzyScore2 | undefined = undefined; + let symbolScore: number | undefined = undefined; + let symbolMatches: IMatch[] | undefined = undefined; + + let containerScore: number | undefined = undefined; + let containerMatches: IMatch[] | undefined = undefined; if (query.original.length > filterPos) { // Score by symbol - symbolScore = scoreFuzzy2(symbolLabel, symbolQuery, symbolLabelWithIcon.length - symbolLabel.length /* Readjust matches to account for codicons in label */); + [symbolScore, symbolMatches] = scoreFuzzy2(symbolLabel, symbolQuery, filterPos, symbolLabelWithIcon.length - symbolLabel.length /* Readjust matches to account for codicons in label */); if (!symbolScore) { continue; } @@ -243,7 +247,7 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit // Score by container if specified if (containerQuery) { if (containerLabel && containerQuery.original.length > 0) { - containerScore = scoreFuzzy2(containerLabel, containerQuery); + [containerScore, containerMatches] = scoreFuzzy2(containerLabel, containerQuery); } if (!containerScore) { @@ -251,7 +255,7 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit } if (symbolScore) { - symbolScore[0] += containerScore[0]; // boost symbolScore by containerScore + symbolScore += containerScore; // boost symbolScore by containerScore } } } @@ -261,13 +265,13 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit filteredSymbolPicks.push({ index, kind: symbol.kind, - score: symbolScore?.[0], + score: symbolScore, label: symbolLabelWithIcon, ariaLabel: symbolLabel, description: containerLabel, highlights: deprecated ? undefined : { - label: symbolScore?.[1], - description: containerScore?.[1] + label: symbolMatches, + description: containerMatches }, range: { selection: Range.collapseToStart(symbol.selectionRange), diff --git a/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts b/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts index 58b267835170a..a5ab4a12d6ede 100644 --- a/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts @@ -22,7 +22,8 @@ import { URI } from 'vs/base/common/uri'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { getSelectionSearchString } from 'vs/editor/contrib/find/findController'; import { withNullAsUndefined } from 'vs/base/common/types'; -import { prepareQuery, IPreparedQuery, scoreFuzzy2, FuzzyScore2, pieceToQuery } from 'vs/base/common/fuzzyScorer'; +import { prepareQuery, IPreparedQuery, scoreFuzzy2, pieceToQuery } from 'vs/base/common/fuzzyScorer'; +import { IMatch } from 'vs/base/common/filters'; interface ISymbolQuickPickItem extends IPickerQuickAccessItem { resource: URI | undefined; @@ -127,10 +128,12 @@ export class SymbolsQuickAccessProvider extends PickerQuickAccessProvider 0) { - symbolScore = scoreFuzzy2(symbolLabel, symbolQuery, symbolLabelWithIcon.length - symbolLabel.length /* Readjust matches to account for codicons in label */); + [symbolScore, symbolMatches] = scoreFuzzy2(symbolLabel, symbolQuery, 0, symbolLabelWithIcon.length - symbolLabel.length /* Readjust matches to account for codicons in label */); if (!symbolScore) { continue; } @@ -148,10 +151,11 @@ export class SymbolsQuickAccessProvider extends PickerQuickAccessProvider 0) { if (containerLabel) { - containerScore = scoreFuzzy2(containerLabel, containerQuery); + [containerScore, containerMatches] = scoreFuzzy2(containerLabel, containerQuery); } if (!containerScore) { @@ -159,7 +163,7 @@ export class SymbolsQuickAccessProvider extends PickerQuickAccessProvider