make fuzzyScore full match boost configurable and don't it for inline completions, https://github.com/microsoft/vscode/issues/146523

This commit is contained in:
Johannes 2022-04-06 17:55:02 +02:00
parent 1df2d85c18
commit 368c39cd7a
No known key found for this signature in database
GPG key ID: 6DEF802A22264FCA
9 changed files with 59 additions and 26 deletions

View file

@ -630,7 +630,7 @@ class TypeFilter<T> implements ITreeFilter<T, FuzzyScore | LabelFuzzyScore>, IDi
return { data: FuzzyScore.Default, visibility: true };
}
const score = fuzzyScore(this._pattern, this._lowercasePattern, 0, labelStr, labelStr.toLowerCase(), 0, true);
const score = fuzzyScore(this._pattern, this._lowercasePattern, 0, labelStr, labelStr.toLowerCase(), 0);
if (score) {
this._matchCount++;
return labels.length === 1 ?

View file

@ -363,14 +363,14 @@ export function matchesFuzzy(word: string, wordToMatchAgainst: string, enableSep
* powerful than `matchesFuzzy`
*/
export function matchesFuzzy2(pattern: string, word: string): IMatch[] | null {
const score = fuzzyScore(pattern, pattern.toLowerCase(), 0, word, word.toLowerCase(), 0, true);
const score = fuzzyScore(pattern, pattern.toLowerCase(), 0, word, word.toLowerCase(), 0, { firstMatchCanBeWeak: true, boostFullMatch: true });
return score ? createMatches(score) : null;
}
export function anyScore(pattern: string, lowPattern: string, patternPos: number, word: string, lowWord: string, wordPos: number): FuzzyScore {
const max = Math.min(13, pattern.length);
for (; patternPos < max; patternPos++) {
const result = fuzzyScore(pattern, lowPattern, patternPos, word, lowWord, wordPos, false);
const result = fuzzyScore(pattern, lowPattern, patternPos, word, lowWord, wordPos, { firstMatchCanBeWeak: false, boostFullMatch: true });
if (result) {
return result;
}
@ -541,11 +541,21 @@ export namespace FuzzyScore {
}
}
export interface FuzzyScorer {
(pattern: string, lowPattern: string, patternPos: number, word: string, lowWord: string, wordPos: number, firstMatchCanBeWeak: boolean): FuzzyScore | undefined;
export abstract class FuzzyScoreOptions {
static default = { boostFullMatch: true, firstMatchCanBeWeak: false };
constructor(
readonly firstMatchCanBeWeak: boolean,
readonly boostFullMatch: boolean,
) { }
}
export function fuzzyScore(pattern: string, patternLow: string, patternStart: number, word: string, wordLow: string, wordStart: number, firstMatchCanBeWeak: boolean): FuzzyScore | undefined {
export interface FuzzyScorer {
(pattern: string, lowPattern: string, patternPos: number, word: string, lowWord: string, wordPos: number, options?: FuzzyScoreOptions): FuzzyScore | undefined;
}
export function fuzzyScore(pattern: string, patternLow: string, patternStart: number, word: string, wordLow: string, wordStart: number, options: FuzzyScoreOptions = FuzzyScoreOptions.default): FuzzyScore | undefined {
const patternLen = pattern.length > _maxLen ? _maxLen : pattern.length;
const wordLen = word.length > _maxLen ? _maxLen : word.length;
@ -630,7 +640,7 @@ export function fuzzyScore(pattern: string, patternLow: string, patternStart: nu
printTables(pattern, patternStart, word, wordStart);
}
if (!hasStrongFirstMatch[0] && !firstMatchCanBeWeak) {
if (!hasStrongFirstMatch[0] && !options.firstMatchCanBeWeak) {
return undefined;
}
@ -684,7 +694,7 @@ export function fuzzyScore(pattern: string, patternLow: string, patternStart: nu
result.push(column);
}
if (wordLen === patternLen) {
if (wordLen === patternLen && options.boostFullMatch) {
// the word matches the pattern with all characters!
// giving the score a total match boost (to come up ahead other words)
result[0] += 2;
@ -783,16 +793,16 @@ function _doScore(
//#region --- graceful ---
export function fuzzyScoreGracefulAggressive(pattern: string, lowPattern: string, patternPos: number, word: string, lowWord: string, wordPos: number, firstMatchCanBeWeak: boolean): FuzzyScore | undefined {
return fuzzyScoreWithPermutations(pattern, lowPattern, patternPos, word, lowWord, wordPos, true, firstMatchCanBeWeak);
export function fuzzyScoreGracefulAggressive(pattern: string, lowPattern: string, patternPos: number, word: string, lowWord: string, wordPos: number, options?: FuzzyScoreOptions): FuzzyScore | undefined {
return fuzzyScoreWithPermutations(pattern, lowPattern, patternPos, word, lowWord, wordPos, true, options);
}
export function fuzzyScoreGraceful(pattern: string, lowPattern: string, patternPos: number, word: string, lowWord: string, wordPos: number, firstMatchCanBeWeak: boolean): FuzzyScore | undefined {
return fuzzyScoreWithPermutations(pattern, lowPattern, patternPos, word, lowWord, wordPos, false, firstMatchCanBeWeak);
export function fuzzyScoreGraceful(pattern: string, lowPattern: string, patternPos: number, word: string, lowWord: string, wordPos: number, options?: FuzzyScoreOptions): FuzzyScore | undefined {
return fuzzyScoreWithPermutations(pattern, lowPattern, patternPos, word, lowWord, wordPos, false, options);
}
function fuzzyScoreWithPermutations(pattern: string, lowPattern: string, patternPos: number, word: string, lowWord: string, wordPos: number, aggressive: boolean, firstMatchCanBeWeak: boolean): FuzzyScore | undefined {
let top = fuzzyScore(pattern, lowPattern, patternPos, word, lowWord, wordPos, firstMatchCanBeWeak);
function fuzzyScoreWithPermutations(pattern: string, lowPattern: string, patternPos: number, word: string, lowWord: string, wordPos: number, aggressive: boolean, options?: FuzzyScoreOptions): FuzzyScore | undefined {
let top = fuzzyScore(pattern, lowPattern, patternPos, word, lowWord, wordPos, options);
if (top && !aggressive) {
// when using the original pattern yield a result we`
@ -810,7 +820,7 @@ function fuzzyScoreWithPermutations(pattern: string, lowPattern: string, pattern
for (let movingPatternPos = patternPos + 1; movingPatternPos < tries; movingPatternPos++) {
const newPattern = nextTypoPermutation(pattern, movingPatternPos);
if (newPattern) {
const candidate = fuzzyScore(newPattern, newPattern.toLowerCase(), patternPos, word, lowWord, wordPos, firstMatchCanBeWeak);
const candidate = fuzzyScore(newPattern, newPattern.toLowerCase(), patternPos, word, lowWord, wordPos, options);
if (candidate) {
candidate[0] -= 3; // permutation penalty
if (!top || candidate[0] > top[0]) {

View file

@ -315,7 +315,7 @@ function doScoreFuzzy2Multiple(target: string, query: IPreparedQueryPiece[], pat
}
function doScoreFuzzy2Single(target: string, query: IPreparedQueryPiece, patternStart: number, wordStart: number): FuzzyScore2 {
const score = fuzzyScore(query.original, query.originalLowercase, patternStart, target, target.toLowerCase(), wordStart, true);
const score = fuzzyScore(query.original, query.originalLowercase, patternStart, target, target.toLowerCase(), wordStart);
if (!score) {
return NO_SCORE2;
}

View file

@ -32,7 +32,7 @@ perfSuite('Performance - fuzzyMatch', function () {
const patternLow = pattern.toLowerCase();
for (const item of data) {
count += 1;
match(pattern, patternLow, 0, item, item.toLowerCase(), 0, false);
match(pattern, patternLow, 0, item, item.toLowerCase(), 0);
}
}
}

View file

@ -223,7 +223,7 @@ suite('Filters', () => {
});
function assertMatches(pattern: string, word: string, decoratedWord: string | undefined, filter: FuzzyScorer, opts: { patternPos?: number; wordPos?: number; firstMatchCanBeWeak?: boolean } = {}) {
let r = filter(pattern, pattern.toLowerCase(), opts.patternPos || 0, word, word.toLowerCase(), opts.wordPos || 0, opts.firstMatchCanBeWeak || false);
let r = filter(pattern, pattern.toLowerCase(), opts.patternPos || 0, word, word.toLowerCase(), opts.wordPos || 0, { firstMatchCanBeWeak: opts.firstMatchCanBeWeak ?? false, boostFullMatch: true });
assert.ok(!decoratedWord === !r);
if (r) {
let matches = createMatches(r);
@ -405,7 +405,7 @@ suite('Filters', () => {
test('Cannot set property \'1\' of undefined, #26511', function () {
let word = new Array<void>(123).join('a');
let pattern = new Array<void>(120).join('a');
fuzzyScore(pattern, pattern.toLowerCase(), 0, word, word.toLowerCase(), 0, false);
fuzzyScore(pattern, pattern.toLowerCase(), 0, word, word.toLowerCase(), 0);
assert.ok(true); // must not explode
});
@ -435,7 +435,7 @@ suite('Filters', () => {
let topIdx = 0;
for (let i = 0; i < words.length; i++) {
const word = words[i];
const m = filter(pattern, pattern.toLowerCase(), 0, word, word.toLowerCase(), 0, false);
const m = filter(pattern, pattern.toLowerCase(), 0, word, word.toLowerCase(), 0);
if (m) {
const [score] = m;
if (score > topScore) {
@ -538,7 +538,7 @@ suite('Filters', () => {
});
test('"Go to Symbol" with the exact method name doesn\'t work as expected #84787', function () {
const match = fuzzyScore(':get', ':get', 1, 'get', 'get', 0, true);
const match = fuzzyScore(':get', ':get', 1, 'get', 'get', 0, { firstMatchCanBeWeak: true, boostFullMatch: true });
assert.ok(Boolean(match));
});
@ -557,4 +557,22 @@ suite('Filters', () => {
assertMatches('.lo', 'log', '^l^og', anyScore);
assertMatches('.', 'log', 'log', anyScore);
});
test('configurable full match boost', function () {
let prefix = 'create';
let a = 'createModelServices';
let b = 'create';
let aBoost = fuzzyScore(prefix, prefix, 0, a, a.toLowerCase(), 0, { boostFullMatch: true, firstMatchCanBeWeak: true });
let bBoost = fuzzyScore(prefix, prefix, 0, b, b.toLowerCase(), 0, { boostFullMatch: true, firstMatchCanBeWeak: true });
assert.ok(aBoost);
assert.ok(bBoost);
assert.ok(aBoost[0] < bBoost[0]);
let aScore = fuzzyScore(prefix, prefix, 0, a, a.toLowerCase(), 0, { boostFullMatch: false, firstMatchCanBeWeak: true });
let bScore = fuzzyScore(prefix, prefix, 0, b, b.toLowerCase(), 0, { boostFullMatch: false, firstMatchCanBeWeak: true });
assert.ok(aScore);
assert.ok(bScore);
assert.ok(aScore[0] === bScore[0]);
});
});

View file

@ -5,7 +5,7 @@
import { quickSelect } from 'vs/base/common/arrays';
import { CharCode } from 'vs/base/common/charCode';
import { anyScore, fuzzyScore, FuzzyScore, fuzzyScoreGracefulAggressive, FuzzyScorer } from 'vs/base/common/filters';
import { anyScore, fuzzyScore, FuzzyScore, fuzzyScoreGracefulAggressive, FuzzyScoreOptions, FuzzyScorer } from 'vs/base/common/filters';
import { compareIgnoreCase } from 'vs/base/common/strings';
import { InternalSuggestOptions } from 'vs/editor/common/config/editorOptions';
import { CompletionItemKind, CompletionItemProvider } from 'vs/editor/common/languages';
@ -41,6 +41,7 @@ export class CompletionModel {
private readonly _wordDistance: WordDistance;
private readonly _options: InternalSuggestOptions;
private readonly _snippetCompareFn = CompletionModel._compareCompletionItems;
private readonly _fuzzyScoreOptions: FuzzyScoreOptions;
private _lineContext: LineContext;
private _refilterKind: Refilter;
@ -55,7 +56,8 @@ export class CompletionModel {
wordDistance: WordDistance,
options: InternalSuggestOptions,
snippetSuggestions: 'top' | 'bottom' | 'inline' | 'none',
readonly clipboardText: string | undefined
fuzzyScoreOptions: FuzzyScoreOptions | undefined = FuzzyScoreOptions.default,
readonly clipboardText: string | undefined = undefined
) {
this._items = items;
this._column = column;
@ -63,6 +65,7 @@ export class CompletionModel {
this._options = options;
this._refilterKind = Refilter.All;
this._lineContext = lineContext;
this._fuzzyScoreOptions = fuzzyScoreOptions;
if (snippetSuggestions === 'top') {
this._snippetCompareFn = CompletionModel._compareCompletionItemsSnippetsUp;
@ -209,7 +212,7 @@ export class CompletionModel {
// if it matches we check with the label to compute highlights
// and if that doesn't yield a result we have no highlights,
// despite having the match
let match = scoreFn(word, wordLow, wordPos, item.completion.filterText, item.filterTextLow!, 0, false);
let match = scoreFn(word, wordLow, wordPos, item.completion.filterText, item.filterTextLow!, 0, this._fuzzyScoreOptions);
if (!match) {
continue; // NO match
}
@ -225,7 +228,7 @@ export class CompletionModel {
} else {
// by default match `word` against the `label`
let match = scoreFn(word, wordLow, wordPos, item.textLabel, item.labelLow, 0, false);
let match = scoreFn(word, wordLow, wordPos, item.textLabel, item.labelLow, 0, this._fuzzyScoreOptions);
if (!match) {
continue; // NO match
}

View file

@ -183,6 +183,7 @@ class SuggestInlineCompletions implements InlineCompletionsProvider<InlineComple
WordDistance.None,
this._getEditorOption(EditorOption.suggest, model),
this._getEditorOption(EditorOption.snippetSuggestions, model),
{ boostFullMatch: false, firstMatchCanBeWeak: false },
clipboardText
);
result = new InlineCompletionResults(model, position.lineNumber, wordInfo, completionModel, completions, this._suggestMemoryService);

View file

@ -540,6 +540,7 @@ export class SuggestModel implements IDisposable {
wordDistance,
this._editor.getOption(EditorOption.suggest),
this._editor.getOption(EditorOption.snippetSuggestions),
undefined,
clipboardText
);

View file

@ -201,7 +201,7 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess
item.highlights = undefined;
return true;
}
const score = fuzzyScore(picker.value, picker.value.toLowerCase(), 1 /*@-character*/, item.label, item.label.toLowerCase(), 0, true);
const score = fuzzyScore(picker.value, picker.value.toLowerCase(), 1 /*@-character*/, item.label, item.label.toLowerCase(), 0, { firstMatchCanBeWeak: true, boostFullMatch: true });
if (!score) {
return false;
}