fuzzyScorer.ts ×17

Frontier kind: Code frontier

unlabeled · c_5124d1b365c3

60 tests · 6992 LOC · 36 files · introduces 0 tests · 193 LOC · 1 file

Introduces — evidence that enters the hierarchy at this concept

Code
17 ranges193 lines · 1 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
978 ranges6992 lines · 36 files · Browse complete extent
All tests (intent)
60 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: 193 introduced LOC across 17 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/base/common/fuzzyScorer.ts 193 introduced LOC · 17 ranges

Open complete file

24
25 export function scoreFuzzy(target: string, query: string, queryLower: string, allowNonContiguousMatches: boolean): FuzzyScore {
26 > if (!target || !query) { fuzzyScorer.ts
27 return NO_SCORE; // return early if target or query are undefined
28 }
30 > const targetLength = target.length;
31 > const queryLength = query.length;
32 >
33 > if (targetLength < queryLength) {
34 return NO_SCORE; // impossible for query to be contained in target
35 }
37 > // if (DEBUG) {
38 > // console.group(`Target: ${target}, Query: ${query}`);
39 > // }
40 >
41 > const targetLower = target.toLowerCase();
42 > const res = doScoreFuzzy(query, queryLower, queryLength, target, targetLower, targetLength, allowNonContiguousMatches);
43 >
44 > // if (DEBUG) {
45 > // console.log(`%cFinal Score: ${res[0]}`, 'font-weight: bold');
46 > // console.groupEnd();
47 > // }
48 >
49 > return res;
50 > }
51
52 > function doScoreFuzzy(query: string, queryLower: string, queryLength: number, target: string, targetLower: string, targetLength: number, allowNonContiguousMatches: boolean): FuzzyScore { fuzzyScorer.ts
53 > const scores: number[] = [];
54 > const matches: number[] = [];
55 >
56 > //
57 > // Build Scorer Matrix:
58 > //
59 > // The matrix is composed of query q and target t. For each index we score
60 > // q[i] with t[i] and compare that with the previous score. If the score is
61 > // equal or larger, we keep the match. In addition to the score, we also keep
62 > // the length of the consecutive matches to use as boost for the score.
63 > //
64 > // t a r g e t
65 > // q
66 > // u
67 > // e
68 > // r
69 > // y
70 > //
71 > for (let queryIndex = 0; queryIndex < queryLength; queryIndex++) {
72 > const queryIndexOffset = queryIndex * targetLength;
73 > const queryIndexPreviousOffset = queryIndexOffset - targetLength;
74 >
75 > const queryIndexGtNull = queryIndex > 0;
76 >
77 > const queryCharAtIndex = query[queryIndex];
78 > const queryLowerCharAtIndex = queryLower[queryIndex];
79 >
80 > for (let targetIndex = 0; targetIndex < targetLength; targetIndex++) {
81 > const targetIndexGtNull = targetIndex > 0;
82 >
83 > const currentIndex = queryIndexOffset + targetIndex;
84 > const leftIndex = currentIndex - 1;
85 > const diagIndex = queryIndexPreviousOffset + targetIndex - 1;
86 >
87 > const leftScore = targetIndexGtNull ? scores[leftIndex] : 0;
88 > const diagScore = queryIndexGtNull && targetIndexGtNull ? scores[diagIndex] : 0;
89 >
90 > const matchesSequenceLength = queryIndexGtNull && targetIndexGtNull ? matches[diagIndex] : 0;
91 >
92 > // If we are not matching on the first query character any more, we only produce a
93 > // score if we had a score previously for the last query index (by looking at the diagScore).
94 > // This makes sure that the query always matches in sequence on the target. For example
95 > // given a target of "ede" and a query of "de", we would otherwise produce a wrong high score
96 > // for query[1] ("e") matching on target[0] ("e") because of the "beginning of word" boost.
97 > let score: number;
98 > if (!diagScore && queryIndexGtNull) {
99 > score = 0;
100 > } else {
101 > score = computeCharScore(queryCharAtIndex, queryLowerCharAtIndex, target, targetLower, targetIndex, matchesSequenceLength);
102 > }
103 >
104 > // We have a score and its equal or larger than the left score
105 > // Match: sequence continues growing from previous diag value
106 > // Score: increases by diag score value
107 > const isValidScore = score && diagScore + score >= leftScore;
108 > if (isValidScore && (
109 > // We don't need to check if it's contiguous if we allow non-contiguous matches
110 > allowNonContiguousMatches ||
111 // We must be looking for a contiguous match.
112 // Looking at an index higher than 0 in the query means we must have already
115 // lastly check if the query is completely contiguous at this index in the target
116 targetLower.startsWith(queryLower, targetIndex)
117 > )) { fuzzyScorer.ts
118 > matches[currentIndex] = matchesSequenceLength + 1;
119 > scores[currentIndex] = diagScore + score;
120 > }
121 >
122 > // We either have no score or the score is lower than the left score
123 > // Match: reset to 0
124 > // Score: pick up from left hand side
125 > else {
126 > matches[currentIndex] = NO_MATCH;
127 > scores[currentIndex] = leftScore;
128 > }
129 > }
130 > }
131 >
132 > // Restore Positions (starting from bottom right of matrix)
133 > const positions: number[] = [];
134 > let queryIndex = queryLength - 1;
135 > let targetIndex = targetLength - 1;
136 > while (queryIndex >= 0 && targetIndex >= 0) {
137 > const currentIndex = queryIndex * targetLength + targetIndex;
138 > const match = matches[currentIndex];
139 > if (match === NO_MATCH) {
140 targetIndex--; // go left
141 > } else { fuzzyScorer.ts
142 positions.push(targetIndex);
143
146 targetIndex--;
147 }
148 > } fuzzyScorer.ts
149 >
150 > // Print matrix
151 > // if (DEBUG_MATRIX) {
152 > // printMatrix(query, target, matches, scores);
153 > // }
154 >
155 > return [scores[queryLength * targetLength - 1], positions.reverse()];
156 > }
157
158 > function computeCharScore(queryCharAtIndex: string, queryLowerCharAtIndex: string, target: string, targetLower: string, targetIndex: number, matchesSequenceLength: number): number { fuzzyScorer.ts
159 > let score = 0;
160 >
161 > if (!considerAsEqual(queryLowerCharAtIndex, targetLower[targetIndex])) {
162 > return score; // no match of characters
163 > }
164 >
165 > // if (DEBUG) {
166 > // console.groupCollapsed(`%cFound a match of char: ${queryLowerCharAtIndex} at index ${targetIndex}`, 'font-weight: normal');
167 > // }
168 >
169 > // Character match bonus
170 > score += 1;
171 >
172 > // if (DEBUG) {
173 > // console.log(`%cCharacter match bonus: +1`, 'font-weight: normal');
174 > // }
175 >
176 > // Consecutive match bonus: sequences up to 3 get the full bonus (6)
177 > // and the remainder gets half the bonus (3). This helps reduce the
178 > // overall boost for long sequence matches.
179 > if (matchesSequenceLength > 0) {
180 score += (Math.min(matchesSequenceLength, 3) * 6) + (Math.max(0, matchesSequenceLength - 3) * 3);
181
184 // }
185 }
187 > // Same case bonus
188 > if (queryCharAtIndex === target[targetIndex]) {
189 > score += 1;
190 >
191 > // if (DEBUG) {
192 > // console.log('Same case bonus: +1');
193 > // }
194 > }
195 >
196 > // Start of word bonus
197 > if (targetIndex === 0) {
198 score += 8;
199
202 // }
203 }
205 > else {
206 >
207 > // After separator bonus
208 > const separatorBonus = scoreSeparatorAtPos(target.charCodeAt(targetIndex - 1));
209 > if (separatorBonus) {
210 score += separatorBonus;
211
214 // }
215 }
217 > // Inside word upper case bonus (camel case). We only give this bonus if we're not in a contiguous sequence.
218 > // For example:
219 > // NPE => NullPointerException = boost
220 > // HTTP => HTTP = not boost
221 > else if (isUpper(target.charCodeAt(targetIndex)) && matchesSequenceLength === 0) {
222 score += 2;
223
226 // }
227 }
228 > } fuzzyScorer.ts
229 >
230 > // if (DEBUG) {
231 > // console.log(`Total score: ${score}`);
232 > // console.groupEnd();
233 > // }
234 >
235 > return score;
236 > }
237
238 > function considerAsEqual(a: string, b: string): boolean { fuzzyScorer.ts
239 > if (a === b) {
240 > return true;
241 > }
242 >
243 > // Special case path separators: ignore platform differences
244 > if (a === '/' || a === '\\') {
245 return b === '/' || b === '\\';
246 }
248 > return false;
249 > }
250
251 > function scoreSeparatorAtPos(charCode: number): number { fuzzyScorer.ts
252 > switch (charCode) {
253 > case CharCode.Slash:
254 > case CharCode.Backslash:
255 return 5; // prefer path separators...
256 > case CharCode.Underline: fuzzyScorer.ts
257 > case CharCode.Dash:
258 > case CharCode.Period:
259 > case CharCode.Space:
260 > case CharCode.SingleQuote:
261 > case CharCode.DoubleQuote:
262 > case CharCode.Colon:
263 return 4; // ...over other separators
264 > default: fuzzyScorer.ts
265 > return 0;
266 > }
267 > }
268
269 // function printMatrix(query: string, target: string, matches: number[], scores: number[]): void {