mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 13:00:58 +00:00
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:
parent
1df2d85c18
commit
368c39cd7a
|
@ -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 ?
|
||||
|
|
|
@ -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]) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -540,6 +540,7 @@ export class SuggestModel implements IDisposable {
|
|||
wordDistance,
|
||||
this._editor.getOption(EditorOption.suggest),
|
||||
this._editor.getOption(EditorOption.snippetSuggestions),
|
||||
undefined,
|
||||
clipboardText
|
||||
);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue