From 16ece8e0c977b4c1d4e78d008c138eb91119f6c1 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 24 May 2017 17:31:02 +0200 Subject: [PATCH] make fuzzy match skip leading whitespace in pattern, fixes #26096 --- src/vs/base/common/filters.ts | 92 +++++++++++++++---------- src/vs/base/test/common/filters.test.ts | 14 +++- 2 files changed, 68 insertions(+), 38 deletions(-) diff --git a/src/vs/base/common/filters.ts b/src/vs/base/common/filters.ts index 19056d36a50..cb219b5c31b 100644 --- a/src/vs/base/common/filters.ts +++ b/src/vs/base/common/filters.ts @@ -448,6 +448,10 @@ _seps['\''] = true; _seps['"'] = true; _seps[':'] = true; +const _ws: { [ch: string]: boolean } = Object.create(null); +_ws[' '] = true; +_ws['\t'] = true; + const enum Arrow { Top = 0b1, Diag = 0b10, Left = 0b100 } export function fuzzyScore(pattern: string, word: string): [number, number[]] { @@ -455,7 +459,20 @@ export function fuzzyScore(pattern: string, word: string): [number, number[]] { const patternLen = pattern.length > 100 ? 100 : pattern.length; const wordLen = word.length > 100 ? 100 : word.length; - if (patternLen === 0) { + // Check for leading whitespace in the pattern and + // start matching just after that position. This is + // like `pattern = pattern.rtrim()` but doesn't create + // a new string + let patternStartPos = 0; + for (const ch of pattern) { + if (_ws[ch]) { + patternStartPos += 1; + } else { + break; + } + } + + if (patternLen === patternStartPos) { return [-1, []]; } @@ -465,16 +482,17 @@ export function fuzzyScore(pattern: string, word: string): [number, number[]] { const lowPattern = pattern.toLowerCase(); const lowWord = word.toLowerCase(); - let i = 0; - let j = 0; - while (i < patternLen && j < wordLen) { - if (lowPattern[i] === lowWord[j]) { - i += 1; + let patternPos = patternStartPos; + let wordPos = 0; + + while (patternPos < patternLen && wordPos < wordLen) { + if (lowPattern[patternPos] === lowWord[wordPos]) { + patternPos += 1; } - j += 1; + wordPos += 1; } - if (i !== patternLen) { + if (patternPos !== patternLen) { // no simple matches found -> return early return undefined; } @@ -482,24 +500,24 @@ export function fuzzyScore(pattern: string, word: string): [number, number[]] { // keep track of the maximum score let maxScore = -1; - for (i = 1; i <= patternLen; i++) { + for (patternPos = patternStartPos + 1; patternPos <= patternLen; patternPos++) { let lastLowWordChar = ''; - for (j = 1; j <= wordLen; j++) { + for (wordPos = 1; wordPos <= wordLen; wordPos++) { let score = -1; - let lowWordChar = lowWord[j - 1]; - if (lowPattern[i - 1] === lowWordChar) { + let lowWordChar = lowWord[wordPos - 1]; + if (lowPattern[patternPos - 1] === lowWordChar) { - if (j === 1) { - if (pattern[i - 1] === word[j - 1]) { + if (wordPos === 1) { + if (pattern[patternPos - 1] === word[wordPos - 1]) { score = 7; } else { score = 5; } - } else if (lowWordChar !== word[j - 1]) { - if (pattern[i - 1] === word[j - 1]) { + } else if (lowWordChar !== word[wordPos - 1]) { + if (pattern[patternPos - 1] === word[wordPos - 1]) { score = 7; } else { score = 5; @@ -512,38 +530,38 @@ export function fuzzyScore(pattern: string, word: string): [number, number[]] { } } - _scores[i][j] = score; + _scores[patternPos][wordPos] = score; if (score > maxScore) { maxScore = score; } - let diag = _table[i - 1][j - 1] + (score > 1 ? 1 : score); - let top = _table[i - 1][j] + -1; - let left = _table[i][j - 1] + -1; + let diag = _table[patternPos - 1][wordPos - 1] + (score > 1 ? 1 : score); + let top = _table[patternPos - 1][wordPos] + -1; + let left = _table[patternPos][wordPos - 1] + -1; if (left >= top) { // left or diag if (left > diag) { - _table[i][j] = left; - _arrows[i][j] = Arrow.Left; + _table[patternPos][wordPos] = left; + _arrows[patternPos][wordPos] = Arrow.Left; } else if (left === diag) { - _table[i][j] = left; - _arrows[i][j] = Arrow.Left | Arrow.Diag; + _table[patternPos][wordPos] = left; + _arrows[patternPos][wordPos] = Arrow.Left | Arrow.Diag; } else { - _table[i][j] = diag; - _arrows[i][j] = Arrow.Diag; + _table[patternPos][wordPos] = diag; + _arrows[patternPos][wordPos] = Arrow.Diag; } } else { // top or diag if (top > diag) { - _table[i][j] = top; - _arrows[i][j] = Arrow.Top; + _table[patternPos][wordPos] = top; + _arrows[patternPos][wordPos] = Arrow.Top; } else if (top === diag) { - _table[i][j] = top; - _arrows[i][j] = Arrow.Top | Arrow.Diag; + _table[patternPos][wordPos] = top; + _arrows[patternPos][wordPos] = Arrow.Top | Arrow.Diag; } else { - _table[i][j] = diag; - _arrows[i][j] = Arrow.Diag; + _table[patternPos][wordPos] = diag; + _arrows[patternPos][wordPos] = Arrow.Diag; } } @@ -562,7 +580,7 @@ export function fuzzyScore(pattern: string, word: string): [number, number[]] { } let bucket: [number, number[]][] = []; - findAllMatches(patternLen, patternLen, wordLen, 0, [], bucket, false); + findAllMatches(patternLen, patternLen, patternStartPos, wordLen, 0, [], bucket, false); if (bucket.length === 0) { return undefined; @@ -580,7 +598,7 @@ export function fuzzyScore(pattern: string, word: string): [number, number[]] { return topMatch; } -function findAllMatches(patternLen: number, patternPos: number, wordPos: number, total: number, matches: number[], bucket: [number, number[]][], lastMatched: boolean): void { +function findAllMatches(patternLen: number, patternPos: number, patternStartPos: number, wordPos: number, total: number, matches: number[], bucket: [number, number[]][], lastMatched: boolean): void { if (bucket.length >= 10) { return; @@ -588,7 +606,7 @@ function findAllMatches(patternLen: number, patternPos: number, wordPos: number, let simpleMatchCount = 0; - while (patternPos > 0 && wordPos > 0) { + while (patternPos > patternStartPos && wordPos > 0) { let score = _scores[patternPos][wordPos]; let arrow = _arrows[patternPos][wordPos]; @@ -609,7 +627,7 @@ function findAllMatches(patternLen: number, patternPos: number, wordPos: number, if (arrow & Arrow.Left) { // left findAllMatches( - patternLen, patternPos, + patternLen, patternPos, patternStartPos, wordPos - 1, matches.length !== 0 ? total - 1 : total, matches.slice(0), bucket, lastMatched @@ -635,7 +653,7 @@ function findAllMatches(patternLen: number, patternPos: number, wordPos: number, } } - if (matches.length !== patternLen) { + if (matches.length !== patternLen - patternStartPos) { // doesn't cover whole pattern return undefined; } diff --git a/src/vs/base/test/common/filters.test.ts b/src/vs/base/test/common/filters.test.ts index ed8f708e757..4753059f57c 100644 --- a/src/vs/base/test/common/filters.test.ts +++ b/src/vs/base/test/common/filters.test.ts @@ -195,7 +195,7 @@ suite('Filters', () => { function assertMatches(pattern: string, word: string, decoratedWord: string, filter: typeof fuzzyScore) { let r = filter(pattern, word); - assert.ok(Boolean(r) === Boolean(decoratedWord)); + assert.ok(!decoratedWord === (!r || r[1].length === 0)); if (r) { const [, matches] = r; let pos = 0; @@ -325,6 +325,18 @@ suite('Filters', () => { assertMatches('f', ':foo', ':^foo', fuzzyScore); }); + test('Vscode 1.12 no longer obeys \'sortText\' in completion items (from language server), #26096', function () { + assertMatches(' ', ' group', undefined, fuzzyScore); + assertMatches(' g', ' group', ' ^group', fuzzyScore); + assertMatches('g', ' group', ' ^group', fuzzyScore); + assertMatches('g g', ' groupGroup', undefined, fuzzyScore); + assertMatches('g g', ' group Group', ' ^group^ ^Group', fuzzyScore); + assertMatches(' g g', ' group Group', ' ^group^ ^Group', fuzzyScore); + assertMatches('zz', 'zzGroup', '^z^zGroup', fuzzyScore); + assertMatches('zzg', 'zzGroup', '^z^z^Group', fuzzyScore); + assertMatches('g', 'zzGroup', 'zz^Group', fuzzyScore); + }); + function assertTopScore(filter: typeof fuzzyScore, pattern: string, expected: number, ...words: string[]) { let topScore = -(100 * 10); let topIdx = 0; -- GitLab