fuzzyScorer.ts ×25

Frontier kind: Code frontier

unlabeled · c_73dec63817ae

599 tests · 6757 LOC · 36 files · introduces 0 tests · 191 LOC · 1 file

Introduces — evidence that enters the hierarchy at this concept

Code
25 ranges191 lines · 1 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
953 ranges6757 lines · 36 files · Browse complete extent
All tests (intent)
599 testsBrowse complete intent

Neighbourhood graph

The orange circle is the focus. Violet and green circles are every ancestor and descendant, broader and narrower, at any distance; blue squares and pink diamonds are the introduced files and exact introduced tests of every visible concept, not only the focus's. Arrows point from broader to narrower concepts and bridge only concepts omitted from this view. Undirected links show source or test introduction. Concept and file size follows LOC; exact test nodes use test-count units.

Introduced files, introduced tests, and structurally relevant concept specialization

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the native relationship evidence on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the native relationship evidence on this page while the interactive map is unavailable.

Native relationship evidence

Every exact file and test below is linked only from the concept that introduces it.

Introduced tests

Every collected test enters the hierarchy at exactly one concept.

No tests are introduced at this concept. Its intent tests are introduced by other concepts.

Introduced code

Every collected source range enters the hierarchy at exactly one concept.

1 file ranked by introduced lines: 191 introduced LOC across 25 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/base/common/fuzzyScorer.ts 191 introduced LOC · 25 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- fuzzyScorer.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { CharCode } from './charCode.js';
7 > import { compareAnything } from './comparers.js';
8 > import { createMatches as createFuzzyMatches, fuzzyScore, IMatch, isUpper, matchesPrefix } from './filters.js';
9 > import { hash } from './hash.js';
10 > import { sep } from './path.js';
11 > import { isLinux, isWindows } from './platform.js';
12 > import { equalsIgnoreCase } from './strings.js';
13 >
14 > //#region Fuzzy scorer
15 >
16 > export type FuzzyScore = [number /* score */, number[] /* match positions */];
17 > export type FuzzyScorerCache = { [key: string]: IItemScore };
18 >
19 > const NO_MATCH = 0;
20 > const NO_SCORE: FuzzyScore = [NO_MATCH, []];
21 >
22 > // const DEBUG = true;
23 > // const DEBUG_MATRIX = false;
24 >
25 > export function scoreFuzzy(target: string, query: string, queryLower: string, allowNonContiguousMatches: boolean): FuzzyScore {
26 if (!target || !query) {
27 return NO_SCORE; // return early if target or query are undefined
49 return res;
50 }
52 function doScoreFuzzy(query: string, queryLower: string, queryLength: number, target: string, targetLower: string, targetLength: number, allowNonContiguousMatches: boolean): FuzzyScore {
53 const scores: number[] = [];
155 return [scores[queryLength * targetLength - 1], positions.reverse()];
156 }
158 function computeCharScore(queryCharAtIndex: string, queryLowerCharAtIndex: string, target: string, targetLower: string, targetIndex: number, matchesSequenceLength: number): number {
159 let score = 0;
235 return score;
236 }
238 function considerAsEqual(a: string, b: string): boolean {
239 if (a === b) {
248 return false;
249 }
251 function scoreSeparatorAtPos(charCode: number): number {
252 switch (charCode) {
266 }
267 }
269 > // function printMatrix(query: string, target: string, matches: number[], scores: number[]): void {
270 > // console.log('\t' + target.split('').join('\t'));
271 > // for (let queryIndex = 0; queryIndex < query.length; queryIndex++) {
272 > // let line = query[queryIndex] + '\t';
273 > // for (let targetIndex = 0; targetIndex < target.length; targetIndex++) {
274 > // const currentIndex = queryIndex * target.length + targetIndex;
275 > // line = line + 'M' + matches[currentIndex] + '/' + 'S' + scores[currentIndex] + '\t';
276 > // }
277 >
278 > // console.log(line);
279 > // }
280 > // }
281 >
282 > //#endregion
283 >
284 >
285 > //#region Alternate fuzzy scorer implementation that is e.g. used for symbols
286 >
287 > export type FuzzyScore2 = [number | undefined /* score */, IMatch[]];
288 >
289 > const NO_SCORE2: FuzzyScore2 = [undefined, []];
290 >
291 > export function scoreFuzzy2(target: string, query: IPreparedQuery | IPreparedQueryPiece, patternStart = 0, wordStart = 0): FuzzyScore2 {
292
293 // Score: multiple inputs
300 return doScoreFuzzy2Single(target, query, patternStart, wordStart);
301 }
303 function doScoreFuzzy2Multiple(target: string, query: IPreparedQueryPiece[], patternStart: number, wordStart: number): FuzzyScore2 {
304 let totalScore = 0;
321 return [totalScore, normalizeMatches(totalMatches)];
322 }
324 function doScoreFuzzy2Single(target: string, query: IPreparedQueryPiece, patternStart: number, wordStart: number): FuzzyScore2 {
325 const score = fuzzyScore(query.normalized, query.normalizedLowercase, patternStart, target, target.toLowerCase(), wordStart, { firstMatchCanBeWeak: true, boostFullMatch: true });
330 return [score[0], createFuzzyMatches(score)];
331 }
333 > //#endregion
334 >
335 >
336 > //#region Item (label, description, path) scorer
337 >
338 > /**
339 > * Scoring on structural items that have a label and optional description.
340 > */
341 > export interface IItemScore {
342 >
343 > /**
344 > * Overall score.
345 > */
346 > score: number;
347 >
348 > /**
349 > * Matches within the label.
350 > */
351 > labelMatch?: IMatch[];
352 >
353 > /**
354 > * Matches within the description.
355 > */
356 > descriptionMatch?: IMatch[];
357 > }
358 >
359 > const NO_ITEM_SCORE = Object.freeze<IItemScore>({ score: 0 });
360 >
361 > export interface IItemAccessor<T> {
362 >
363 > /**
364 > * Just the label of the item to score on.
365 > */
366 > getItemLabel(item: T): string | undefined;
367 >
368 > /**
369 > * The optional description of the item to score on.
370 > */
371 > getItemDescription(item: T): string | undefined;
372 >
373 > /**
374 > * If the item is a file, the path of the file to score on.
375 > */
376 > getItemPath(file: T): string | undefined;
377 > }
378 >
379 > const PATH_IDENTITY_SCORE = 1 << 18;
380 > const LABEL_PREFIX_SCORE_THRESHOLD = 1 << 17;
381 > const LABEL_SCORE_THRESHOLD = 1 << 16;
382 >
383 function getCacheHash(label: string, description: string | undefined, allowNonContiguousMatches: boolean, query: IPreparedQuery) {
384 const values = query.values ? query.values : [query];
393 return cacheHash;
394 }
396 > export function scoreItemFuzzy<T>(item: T, query: IPreparedQuery, allowNonContiguousMatches: boolean, accessor: IItemAccessor<T>, cache: FuzzyScorerCache): IItemScore {
397 if (!item || !query.normalized) {
398 return NO_ITEM_SCORE; // we need an item and query to score on at least
422 return itemScore;
423 }
425 function doScoreItemFuzzy(label: string, description: string | undefined, path: string | undefined, query: IPreparedQuery, allowNonContiguousMatches: boolean): IItemScore {
426 const preferLabelMatches = !path || !query.containsPathSeparator;
439 return doScoreItemFuzzySingle(label, description, path, query, preferLabelMatches, allowNonContiguousMatches);
440 }
442 function doScoreItemFuzzyMultiple(label: string, description: string | undefined, path: string | undefined, query: IPreparedQueryPiece[], preferLabelMatches: boolean, allowNonContiguousMatches: boolean): IItemScore {
443 let totalScore = 0;
471 };
472 }
474 function doScoreItemFuzzySingle(label: string, description: string | undefined, path: string | undefined, query: IPreparedQueryPiece, preferLabelMatches: boolean, allowNonContiguousMatches: boolean): IItemScore {
475
554 return NO_ITEM_SCORE;
555 }
557 function createMatches(offsets: number[] | undefined): IMatch[] {
558 const ret: IMatch[] = [];
573 return ret;
574 }
576 function normalizeMatches(matches: IMatch[]): IMatch[] {
577
603 return normalizedMatches;
604 }
606 function matchOverlaps(matchA: IMatch, matchB: IMatch): boolean {
607 if (matchA.end < matchB.start) {
615 return true;
616 }
618 > //#endregion
619 >
620 >
621 > //#region Comparers
622 >
623 > export function compareItemsByFuzzyScore<T>(itemA: T, itemB: T, query: IPreparedQuery, allowNonContiguousMatches: boolean, accessor: IItemAccessor<T>, cache: FuzzyScorerCache): number {
624 const itemScoreA = scoreItemFuzzy(itemA, query, allowNonContiguousMatches, accessor, cache);
625 const itemScoreB = scoreItemFuzzy(itemB, query, allowNonContiguousMatches, accessor, cache);
682 return fallbackCompare(itemA, itemB, query, accessor);
683 }
685 function computeLabelAndDescriptionMatchDistance<T>(item: T, score: IItemScore, accessor: IItemAccessor<T>): number {
686 let matchStart = -1;
717 return matchEnd - matchStart;
718 }
720 function compareByMatchLength(matchesA?: IMatch[], matchesB?: IMatch[]): number {
721 if ((!matchesA && !matchesB) || ((!matchesA?.length) && (!matchesB?.length))) {
744 return matchLengthA === matchLengthB ? 0 : matchLengthB < matchLengthA ? 1 : -1;
745 }
747 function fallbackCompare<T>(itemA: T, itemB: T, query: IPreparedQuery, accessor: IItemAccessor<T>): number {
748
789 return 0;
790 }
792 > //#endregion
793 >
794 >
795 > //#region Query Normalizer
796 >
797 > export interface IPreparedQueryPiece {
798 >
799 > /**
800 > * The original query as provided as input.
801 > */
802 > original: string;
803 > originalLowercase: string;
804 >
805 > /**
806 > * Original normalized to platform separators:
807 > * - Windows: \
808 > * - Posix: /
809 > */
810 > pathNormalized: string;
811 >
812 > /**
813 > * In addition to the normalized path, will have
814 > * whitespace, wildcards, quotes, ellipsis, and trailing hash characters removed.
815 > */
816 > normalized: string;
817 > normalizedLowercase: string;
818 >
819 > /**
820 > * The query is wrapped in quotes which means
821 > * this query must be a substring of the input.
822 > * In other words, no fuzzy matching is used.
823 > */
824 > expectContiguousMatch: boolean;
825 > }
826 >
827 > export interface IPreparedQuery extends IPreparedQueryPiece {
828 >
829 > /**
830 > * Query split by spaces into pieces.
831 > */
832 > values: IPreparedQueryPiece[] | undefined;
833 >
834 > /**
835 > * Whether the query contains path separator(s) or not.
836 > */
837 > containsPathSeparator: boolean;
838 > }
839 >
840 > /*
841 > * If a query is wrapped in quotes, the user does not want to
842 > * use fuzzy search for this query.
843 > */
844 function queryExpectsExactMatch(query: string) {
845 return query.startsWith('"') && query.endsWith('"');
846 }
848 > /**
849 > * Helper function to prepare a search value for scoring by removing unwanted characters
850 > * and allowing to score on multiple pieces separated by whitespace character.
851 > */
852 > const MULTIPLE_QUERY_VALUES_SEPARATOR = ' ';
853 > export function prepareQuery(original: string): IPreparedQuery {
854 if (typeof original !== 'string') {
855 original = '';
892 return { original, originalLowercase, pathNormalized, normalized, normalizedLowercase, values, containsPathSeparator, expectContiguousMatch: expectExactMatch };
893 }
895 function normalizeQuery(original: string): { pathNormalized: string; normalized: string; normalizedLowercase: string } {
896 let pathNormalized: string;
915 };
916 }
918 > export function pieceToQuery(piece: IPreparedQueryPiece): IPreparedQuery;
919 > export function pieceToQuery(pieces: IPreparedQueryPiece[]): IPreparedQuery;
920 > export function pieceToQuery(arg1: IPreparedQueryPiece | IPreparedQueryPiece[]): IPreparedQuery {
921 if (Array.isArray(arg1)) {
922 return prepareQuery(arg1.map(piece => piece.original).join(MULTIPLE_QUERY_VALUES_SEPARATOR));
925 return prepareQuery(arg1.original);
926 }
928 > //#endregion