Skip to content

Commit

Permalink
fix #79815
Browse files Browse the repository at this point in the history
  • Loading branch information
jrieken committed Aug 28, 2019
1 parent 4994c7b commit f0d7286
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 81 deletions.
167 changes: 86 additions & 81 deletions src/vs/base/common/filters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,14 @@ function printTable(table: number[][], pattern: string, patternLen: number, word
return ret;
}

function printTables(pattern: string, patternStart: number, word: string, wordStart: number): void {
pattern = pattern.substr(patternStart);
word = word.substr(wordStart);
console.log(printTable(_table, pattern, pattern.length, word, word.length));
console.log(printTable(_arrows, pattern, pattern.length, word, word.length));
console.log(printTable(_scores, pattern, pattern.length, word, word.length));
}

function isSeparatorAtPos(value: string, index: number): boolean {
if (index < 0 || index >= value.length) {
return false;
Expand Down Expand Up @@ -530,130 +538,127 @@ export interface FuzzyScorer {
(pattern: string, lowPattern: string, patternPos: number, word: string, lowWord: string, wordPos: number, firstMatchCanBeWeak: boolean): FuzzyScore | undefined;
}

export function fuzzyScore(pattern: string, patternLow: string, patternPos: number, word: string, wordLow: string, wordPos: number, firstMatchCanBeWeak: boolean): FuzzyScore | undefined {

if (patternPos > 0) {
pattern = pattern.substr(patternPos);
patternLow = patternLow.substr(patternPos);
patternPos = 0;
}
export function fuzzyScore(pattern: string, patternLow: string, patternStart: number, word: string, wordLow: string, wordStart: number, firstMatchCanBeWeak: boolean): FuzzyScore | undefined {

const patternLen = pattern.length > _maxLen ? _maxLen : pattern.length;
const wordLen = word.length > _maxLen ? _maxLen : word.length;

if (patternPos >= patternLen || wordPos >= wordLen || patternLen > wordLen) {
if (patternStart >= patternLen || wordStart >= wordLen || patternLen > wordLen) {
return undefined;
}

// Run a simple check if the characters of pattern occur
// (in order) at all in word. If that isn't the case we
// stop because no match will be possible
if (!isPatternInWord(patternLow, patternPos, patternLen, wordLow, wordPos, wordLen)) {
if (!isPatternInWord(patternLow, patternStart, patternLen, wordLow, wordStart, wordLen)) {
return undefined;
}

const patternStartPos = patternPos;
const wordStartPos = wordPos;
let row: number = 1;
let column: number = 1;
let patternPos = patternStart;
let wordPos = wordStart;

// There will be a match, fill in tables
for (patternPos = patternStartPos + 1; patternPos <= patternLen; patternPos++) {

for (wordPos = 1; wordPos <= wordLen; wordPos++) {

let score = -1;
if (patternLow[patternPos - 1] === wordLow[wordPos - 1]) {
for (row = 1, patternPos = patternStart; patternPos < patternLen; row++ , patternPos++) {

if (wordPos === (patternPos - patternStartPos)) {
// common prefix: `foobar <-> foobaz`
// ^^^^^
if (pattern[patternPos - 1] === word[wordPos - 1]) {
score = 7;
} else {
score = 5;
}
} else if (isUpperCaseAtPos(wordPos - 1, word, wordLow) && (wordPos === 1 || !isUpperCaseAtPos(wordPos - 2, word, wordLow))) {
// hitting upper-case: `foo <-> forOthers`
// ^^ ^
if (pattern[patternPos - 1] === word[wordPos - 1]) {
score = 7;
} else {
score = 5;
}
} else if (isSeparatorAtPos(wordLow, wordPos - 1) && (wordPos === 1 || !isSeparatorAtPos(wordLow, wordPos - 2))) {
// hitting a separator: `. <-> foo.bar`
// ^
score = 5;

} else if (isSeparatorAtPos(wordLow, wordPos - 2) || isWhitespaceAtPos(wordLow, wordPos - 2)) {
// post separator: `foo <-> bar_foo`
// ^^^
score = 5;
for (column = 1, wordPos = wordStart; wordPos < wordLen; column++ , wordPos++) {

} else {
score = 1;
}
}
const score = _doScore(pattern, patternLow, patternPos, patternStart, word, wordLow, wordPos);

_scores[patternPos][wordPos] = score;
_scores[row][column] = score;

const diag = _table[patternPos - 1][wordPos - 1] + (score > 1 ? 1 : score);
const top = _table[patternPos - 1][wordPos] + -1;
const left = _table[patternPos][wordPos - 1] + -1;
const diag = _table[row - 1][column - 1] + (score > 1 ? 1 : score);
const top = _table[row - 1][column] + -1;
const left = _table[row][column - 1] + -1;

if (left >= top) {
// left or diag
if (left > diag) {
_table[patternPos][wordPos] = left;
_arrows[patternPos][wordPos] = Arrow.Left;
_table[row][column] = left;
_arrows[row][column] = Arrow.Left;
} else if (left === diag) {
_table[patternPos][wordPos] = left;
_arrows[patternPos][wordPos] = Arrow.Left | Arrow.Diag;
_table[row][column] = left;
_arrows[row][column] = Arrow.Left | Arrow.Diag;
} else {
_table[patternPos][wordPos] = diag;
_arrows[patternPos][wordPos] = Arrow.Diag;
_table[row][column] = diag;
_arrows[row][column] = Arrow.Diag;
}
} else {
// top or diag
if (top > diag) {
_table[patternPos][wordPos] = top;
_arrows[patternPos][wordPos] = Arrow.Top;
_table[row][column] = top;
_arrows[row][column] = Arrow.Top;
} else if (top === diag) {
_table[patternPos][wordPos] = top;
_arrows[patternPos][wordPos] = Arrow.Top | Arrow.Diag;
_table[row][column] = top;
_arrows[row][column] = Arrow.Top | Arrow.Diag;
} else {
_table[patternPos][wordPos] = diag;
_arrows[patternPos][wordPos] = Arrow.Diag;
_table[row][column] = diag;
_arrows[row][column] = Arrow.Diag;
}
}
}
}

if (_debug) {
console.log(printTable(_table, pattern, patternLen, word, wordLen));
console.log(printTable(_arrows, pattern, patternLen, word, wordLen));
console.log(printTable(_scores, pattern, patternLen, word, wordLen));
printTables(pattern, patternStart, word, wordStart);
}

_matchesCount = 0;
_topScore = -100;
_patternStartPos = patternStartPos;
_wordStart = wordStart;
_firstMatchCanBeWeak = firstMatchCanBeWeak;
_findAllMatches2(patternLen, wordLen, patternLen === wordLen ? 1 : 0, 0, false);

_findAllMatches2(row - 1, column - 1, patternLen === wordLen ? 1 : 0, 0, false);
if (_matchesCount === 0) {
return undefined;
}

return [_topScore, _topMatch2, wordStartPos];
return [_topScore, _topMatch2, wordStart];
}

function _doScore(pattern: string, patternLow: string, patternPos: number, patternStart: number, word: string, wordLow: string, wordPos: number) {
if (patternLow[patternPos] !== wordLow[wordPos]) {
return -1;
}
if (wordPos === (patternPos - patternStart)) {
// common prefix: `foobar <-> foobaz`
// ^^^^^
if (pattern[patternPos] === word[wordPos]) {
return 7;
} else {
return 5;
}
} else if (isUpperCaseAtPos(wordPos, word, wordLow) && (wordPos === 0 || !isUpperCaseAtPos(wordPos - 1, word, wordLow))) {
// hitting upper-case: `foo <-> forOthers`
// ^^ ^
if (pattern[patternPos] === word[wordPos]) {
return 7;
} else {
return 5;
}
} else if (isSeparatorAtPos(wordLow, wordPos) && (wordPos === 0 || !isSeparatorAtPos(wordLow, wordPos - 1))) {
// hitting a separator: `. <-> foo.bar`
// ^
return 5;

} else if (isSeparatorAtPos(wordLow, wordPos - 1) || isWhitespaceAtPos(wordLow, wordPos - 1)) {
// post separator: `foo <-> bar_foo`
// ^^^
return 5;

} else {
return 1;
}
}

let _matchesCount: number = 0;
let _topMatch2: number = 0;
let _topScore: number = 0;
let _patternStartPos: number = 0;
let _wordStart: number = 0;
let _firstMatchCanBeWeak: boolean = false;

function _findAllMatches2(patternPos: number, wordPos: number, total: number, matches: number, lastMatched: boolean): void {
function _findAllMatches2(row: number, column: number, total: number, matches: number, lastMatched: boolean): void {

if (_matchesCount >= 10 || total < -25) {
// stop when having already 10 results, or
Expand All @@ -663,14 +668,14 @@ function _findAllMatches2(patternPos: number, wordPos: number, total: number, ma

let simpleMatchCount = 0;

while (patternPos > _patternStartPos && wordPos > 0) {
while (row > 0 && column > 0) {

const score = _scores[patternPos][wordPos];
const arrow = _arrows[patternPos][wordPos];
const score = _scores[row][column];
const arrow = _arrows[row][column];

if (arrow === Arrow.Left) {
// left -> no match, skip a word character
wordPos -= 1;
column -= 1;
if (lastMatched) {
total -= 5; // new gap penalty
} else if (matches !== 0) {
Expand All @@ -684,8 +689,8 @@ function _findAllMatches2(patternPos: number, wordPos: number, total: number, ma
if (arrow & Arrow.Left) {
// left
_findAllMatches2(
patternPos,
wordPos - 1,
row,
column - 1,
matches !== 0 ? total - 1 : total, // gap penalty after first match
matches,
lastMatched
Expand All @@ -694,20 +699,20 @@ function _findAllMatches2(patternPos: number, wordPos: number, total: number, ma

// diag
total += score;
patternPos -= 1;
wordPos -= 1;
row -= 1;
column -= 1;
lastMatched = true;

// match -> set a 1 at the word pos
matches += 2 ** wordPos;
matches += 2 ** (column + _wordStart);

// count simple matches and boost a row of
// simple matches when they yield in a
// strong match.
if (score === 1) {
simpleMatchCount += 1;

if (patternPos === _patternStartPos && !_firstMatchCanBeWeak) {
if (row === 0 && !_firstMatchCanBeWeak) {
// when the first match is a weak
// match we discard it
return undefined;
Expand All @@ -724,7 +729,7 @@ function _findAllMatches2(patternPos: number, wordPos: number, total: number, ma
}
}

total -= wordPos >= 3 ? 9 : wordPos * 3; // late start penalty
total -= column >= 3 ? 9 : column * 3; // late start penalty

// dynamically keep track of the current top score
// and insert the current best score at head, the rest at tail
Expand Down
4 changes: 4 additions & 0 deletions src/vs/base/test/common/filters.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,10 @@ suite('Filters', () => {
test('patternPos isn\'t working correctly #79815', function () {
assertMatches(':p'.substr(1), 'prop', '^prop', fuzzyScore, { patternPos: 0 });
assertMatches(':p', 'prop', '^prop', fuzzyScore, { patternPos: 1 });
assertMatches(':p', 'prop', undefined, fuzzyScore, { patternPos: 2 });
assertMatches(':p', 'proP', 'pro^P', fuzzyScore, { patternPos: 1, wordPos: 1 });
assertMatches(':p', 'aprop', 'a^prop', fuzzyScore, { patternPos: 1, firstMatchCanBeWeak: true });
assertMatches(':p', 'aprop', undefined, fuzzyScore, { patternPos: 1, firstMatchCanBeWeak: false });
});

function assertTopScore(filter: typeof fuzzyScore, pattern: string, expected: number, ...words: string[]) {
Expand Down

0 comments on commit f0d7286

Please sign in to comment.