Compare commits

...

3 commits

Author SHA1 Message Date
Raymond Zhao 6d07c4e616
Remove ordering by value and description 2021-11-18 09:15:28 -08:00
Raymond Zhao 200ef2057c
Fix whole word matching strategy 2021-11-17 09:28:06 -08:00
Raymond Zhao fec1b869f2
Add match-type sorting to filterSettings 2021-11-12 15:32:11 -08:00
4 changed files with 62 additions and 11 deletions

View file

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { ISettingsEditorModel, ISetting, ISettingsGroup, IFilterMetadata, ISearchResult, IGroupFilter, ISettingMatcher, IScoredResults, ISettingMatch, IRemoteSetting, IExtensionSetting } from 'vs/workbench/services/preferences/common/preferences'; import { ISettingsEditorModel, ISetting, ISettingsGroup, IFilterMetadata, ISearchResult, IGroupFilter, ISettingMatcher, IScoredResults, ISettingMatch, IRemoteSetting, IExtensionSetting, SettingMatchType } from 'vs/workbench/services/preferences/common/preferences';
import { IRange } from 'vs/editor/common/core/range'; import { IRange } from 'vs/editor/common/core/range';
import { distinct, top } from 'vs/base/common/arrays'; import { distinct, top } from 'vs/base/common/arrays';
import * as strings from 'vs/base/common/strings'; import * as strings from 'vs/base/common/strings';
@ -114,7 +114,7 @@ export class LocalSearchProvider implements ISearchProvider {
let orderedScore = LocalSearchProvider.START_SCORE; // Sort is not stable let orderedScore = LocalSearchProvider.START_SCORE; // Sort is not stable
const settingMatcher = (setting: ISetting) => { const settingMatcher = (setting: ISetting) => {
const matches = new SettingMatches(this._filter, setting, true, true, (filter, setting) => preferencesModel.findValueMatches(filter, setting)).matches; const { matches, matchType } = new SettingMatches(this._filter, setting, true, true, (filter, setting) => preferencesModel.findValueMatches(filter, setting));
const score = this._filter === setting.key ? const score = this._filter === setting.key ?
LocalSearchProvider.EXACT_MATCH_SCORE : LocalSearchProvider.EXACT_MATCH_SCORE :
orderedScore--; orderedScore--;
@ -122,6 +122,7 @@ export class LocalSearchProvider implements ISearchProvider {
return matches && matches.length ? return matches && matches.length ?
{ {
matches, matches,
matchType,
score score
} : } :
null; null;
@ -210,7 +211,8 @@ class RemoteSearchProvider implements ISearchProvider {
return <ISettingMatch>{ return <ISettingMatch>{
setting, setting,
score: remoteSetting.score, score: remoteSetting.score,
matches: [] // TODO matches: [], // TODO
matchType: SettingMatchType.None
}; };
}); });
@ -338,8 +340,8 @@ class RemoteSearchProvider implements ISearchProvider {
scoredResults[getSettingKey(setting.key, 'core')] || // core setting scoredResults[getSettingKey(setting.key, 'core')] || // core setting
scoredResults[getSettingKey(setting.key)]; // core setting from original prod endpoint scoredResults[getSettingKey(setting.key)]; // core setting from original prod endpoint
if (remoteSetting && remoteSetting.score >= minScore) { if (remoteSetting && remoteSetting.score >= minScore) {
const settingMatches = new SettingMatches(this.options.filter, setting, false, true, (filter, setting) => preferencesModel.findValueMatches(filter, setting)).matches; const { matches, matchType } = new SettingMatches(this.options.filter, setting, false, true, (filter, setting) => preferencesModel.findValueMatches(filter, setting));
return { matches: settingMatches, score: remoteSetting.score }; return { matches, matchType, score: remoteSetting.score };
} }
return null; return null;
@ -448,6 +450,7 @@ export class SettingMatches {
private readonly valueMatchingWords: Map<string, IRange[]> = new Map<string, IRange[]>(); private readonly valueMatchingWords: Map<string, IRange[]> = new Map<string, IRange[]>();
readonly matches: IRange[]; readonly matches: IRange[];
matchType: SettingMatchType = SettingMatchType.None;
constructor(searchString: string, setting: ISetting, private requireFullQueryMatch: boolean, private searchDescription: boolean, private valuesMatcher: (filter: string, setting: ISetting) => IRange[]) { constructor(searchString: string, setting: ISetting, private requireFullQueryMatch: boolean, private searchDescription: boolean, private valuesMatcher: (filter: string, setting: ISetting) => IRange[]) {
this.matches = distinct(this._findMatchesInSetting(searchString, setting), (match) => `${match.startLineNumber}_${match.startColumn}_${match.endLineNumber}_${match.endColumn}_`); this.matches = distinct(this._findMatchesInSetting(searchString, setting), (match) => `${match.startLineNumber}_${match.startColumn}_${match.endLineNumber}_${match.endColumn}_`);
@ -465,6 +468,8 @@ export class SettingMatches {
const subSettingValueRanges: IRange[] = this.getRangesForWords(words, subSettingMatches.valueMatchingWords, [this.descriptionMatchingWords, this.keyMatchingWords, subSettingMatches.keyMatchingWords]); const subSettingValueRanges: IRange[] = this.getRangesForWords(words, subSettingMatches.valueMatchingWords, [this.descriptionMatchingWords, this.keyMatchingWords, subSettingMatches.keyMatchingWords]);
result.push(...descriptionRanges, ...keyRanges, ...subSettingKeyRanges, ...subSettingValueRanges); result.push(...descriptionRanges, ...keyRanges, ...subSettingKeyRanges, ...subSettingValueRanges);
result.push(...subSettingMatches.matches); result.push(...subSettingMatches.matches);
this.refreshMatchType(keyRanges.length + subSettingKeyRanges.length);
this.matchType |= subSettingMatches.matchType;
} }
} }
return result; return result;
@ -478,12 +483,14 @@ export class SettingMatches {
const settingKeyAsWords: string = setting.key.split('.').join(' '); const settingKeyAsWords: string = setting.key.split('.').join(' ');
for (const word of words) { for (const word of words) {
// Whole word match attempts also take place within this loop.
if (this.searchDescription) { if (this.searchDescription) {
for (let lineIndex = 0; lineIndex < setting.description.length; lineIndex++) { for (let lineIndex = 0; lineIndex < setting.description.length; lineIndex++) {
const descriptionMatches = matchesWords(word, setting.description[lineIndex], true); const descriptionMatches = matchesWords(word, setting.description[lineIndex], true);
if (descriptionMatches) { if (descriptionMatches) {
this.descriptionMatchingWords.set(word, descriptionMatches.map(match => this.toDescriptionRange(setting, match, lineIndex))); this.descriptionMatchingWords.set(word, descriptionMatches.map(match => this.toDescriptionRange(setting, match, lineIndex)));
} }
this.checkForWholeWordMatchType(word, setting.description[lineIndex]);
} }
} }
@ -491,6 +498,7 @@ export class SettingMatches {
if (keyMatches) { if (keyMatches) {
this.keyMatchingWords.set(word, keyMatches.map(match => this.toKeyRange(setting, match))); this.keyMatchingWords.set(word, keyMatches.map(match => this.toKeyRange(setting, match)));
} }
this.checkForWholeWordMatchType(word, settingKeyAsWords);
const valueMatches = typeof setting.value === 'string' ? matchesContiguousSubString(word, setting.value) : null; const valueMatches = typeof setting.value === 'string' ? matchesContiguousSubString(word, setting.value) : null;
if (valueMatches) { if (valueMatches) {
@ -498,6 +506,9 @@ export class SettingMatches {
} else if (schema && schema.enum && schema.enum.some(enumValue => typeof enumValue === 'string' && !!matchesContiguousSubString(word, enumValue))) { } else if (schema && schema.enum && schema.enum.some(enumValue => typeof enumValue === 'string' && !!matchesContiguousSubString(word, enumValue))) {
this.valueMatchingWords.set(word, []); this.valueMatchingWords.set(word, []);
} }
if (typeof setting.value === 'string') {
this.checkForWholeWordMatchType(word, setting.value);
}
} }
const descriptionRanges: IRange[] = []; const descriptionRanges: IRange[] = [];
@ -522,9 +533,26 @@ export class SettingMatches {
valueRanges = this.valuesMatcher(searchString, setting); valueRanges = this.valuesMatcher(searchString, setting);
} }
this.refreshMatchType(keyRanges.length);
return [...descriptionRanges, ...keyRanges, ...valueRanges]; return [...descriptionRanges, ...keyRanges, ...valueRanges];
} }
private checkForWholeWordMatchType(singleWordQuery: string, lineToSearch: string) {
// Trim excess ending characters off the query.
singleWordQuery = singleWordQuery.toLowerCase().replace(/[\s-\._]+$/, '');
lineToSearch = lineToSearch.toLowerCase();
const singleWordRegex = new RegExp(`\\b${singleWordQuery}\\b`);
if (singleWordRegex.test(lineToSearch)) {
this.matchType |= SettingMatchType.WholeWordMatch;
}
}
private refreshMatchType(keyRangesLength: number) {
if (keyRangesLength) {
this.matchType |= SettingMatchType.KeyMatch;
}
}
private getRangesForWords(words: string[], from: Map<string, IRange[]>, others: Map<string, IRange[]>[]): IRange[] { private getRangesForWords(words: string[], from: Map<string, IRange[]>, others: Map<string, IRange[]>[]): IRange[] {
const result: IRange[] = []; const result: IRange[] = [];
for (const word of words) { for (const word of words) {

View file

@ -46,7 +46,7 @@ import { settingsTextInputBorder } from 'vs/workbench/contrib/preferences/browse
import { createTOCIterator, TOCTree, TOCTreeModel } from 'vs/workbench/contrib/preferences/browser/tocTree'; import { createTOCIterator, TOCTree, TOCTreeModel } from 'vs/workbench/contrib/preferences/browser/tocTree';
import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_ROW_FOCUS, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, EXTENSION_SETTING_TAG, FEATURE_SETTING_TAG, ID_SETTING_TAG, IPreferencesSearchService, ISearchProvider, MODIFIED_SETTING_TAG, REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, WORKSPACE_TRUST_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/preferences'; import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_ROW_FOCUS, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, EXTENSION_SETTING_TAG, FEATURE_SETTING_TAG, ID_SETTING_TAG, IPreferencesSearchService, ISearchProvider, MODIFIED_SETTING_TAG, REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, WORKSPACE_TRUST_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/preferences';
import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IOpenSettingsOptions, IPreferencesService, ISearchResult, ISettingsEditorModel, ISettingsEditorOptions, SettingValueType, validateSettingsEditorOptions } from 'vs/workbench/services/preferences/common/preferences'; import { IOpenSettingsOptions, IPreferencesService, ISearchResult, ISettingsEditorModel, ISettingsEditorOptions, SettingMatchType, SettingValueType, validateSettingsEditorOptions } from 'vs/workbench/services/preferences/common/preferences';
import { SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput'; import { SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput';
import { Settings2EditorModel } from 'vs/workbench/services/preferences/common/preferencesModels'; import { Settings2EditorModel } from 'vs/workbench/services/preferences/common/preferencesModels';
import { IUserDataSyncWorkbenchService } from 'vs/workbench/services/userDataSync/common/userDataSync'; import { IUserDataSyncWorkbenchService } from 'vs/workbench/services/userDataSync/common/userDataSync';
@ -1262,7 +1262,7 @@ export class SettingsEditor2 extends EditorPane {
for (const g of this.defaultSettingsEditorModel.settingsGroups.slice(1)) { for (const g of this.defaultSettingsEditorModel.settingsGroups.slice(1)) {
for (const sect of g.sections) { for (const sect of g.sections) {
for (const setting of sect.settings) { for (const setting of sect.settings) {
fullResult.filterMatches.push({ setting, matches: [], score: 0 }); fullResult.filterMatches.push({ setting, matches: [], matchType: SettingMatchType.None, score: 0 });
} }
} }
} }

View file

@ -115,9 +115,21 @@ export interface IFilterResult {
exactMatch?: boolean; exactMatch?: boolean;
} }
/**
* The ways a setting could match a query,
* sorted in increasing order of relevance.
* For now, ignore description and value matches.
*/
export enum SettingMatchType {
None = 0,
WholeWordMatch = 1 << 0,
KeyMatch = 1 << 1
}
export interface ISettingMatch { export interface ISettingMatch {
setting: ISetting; setting: ISetting;
matches: IRange[] | null; matches: IRange[] | null;
matchType: SettingMatchType;
score: number; score: number;
} }
@ -157,7 +169,7 @@ export interface IPreferencesEditorModel<T> {
} }
export type IGroupFilter = (group: ISettingsGroup) => boolean | null; export type IGroupFilter = (group: ISettingsGroup) => boolean | null;
export type ISettingMatcher = (setting: ISetting, group: ISettingsGroup) => { matches: IRange[], score: number } | null; export type ISettingMatcher = (setting: ISetting, group: ISettingsGroup) => { matches: IRange[], matchType: SettingMatchType, score: number } | null;
export interface ISettingsEditorModel extends IPreferencesEditorModel<ISetting> { export interface ISettingsEditorModel extends IPreferencesEditorModel<ISetting> {
readonly onDidChangeGroups: Event<void>; readonly onDidChangeGroups: Event<void>;

View file

@ -19,7 +19,7 @@ import { ConfigurationScope, Extensions, IConfigurationNode, IConfigurationPrope
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { Registry } from 'vs/platform/registry/common/platform'; import { Registry } from 'vs/platform/registry/common/platform';
import { EditorModel } from 'vs/workbench/common/editor/editorModel'; import { EditorModel } from 'vs/workbench/common/editor/editorModel';
import { IFilterMetadata, IFilterResult, IGroupFilter, IKeybindingsEditorModel, ISearchResultGroup, ISetting, ISettingMatch, ISettingMatcher, ISettingsEditorModel, ISettingsGroup } from 'vs/workbench/services/preferences/common/preferences'; import { IFilterMetadata, IFilterResult, IGroupFilter, IKeybindingsEditorModel, ISearchResultGroup, ISetting, ISettingMatch, ISettingMatcher, ISettingsEditorModel, ISettingsGroup, SettingMatchType } from 'vs/workbench/services/preferences/common/preferences';
import { withNullAsUndefined, isArray } from 'vs/base/common/types'; import { withNullAsUndefined, isArray } from 'vs/base/common/types';
import { FOLDER_SCOPES, WORKSPACE_SCOPES } from 'vs/workbench/services/configuration/common/configuration'; import { FOLDER_SCOPES, WORKSPACE_SCOPES } from 'vs/workbench/services/configuration/common/configuration';
import { createValidator } from 'vs/workbench/services/preferences/common/preferencesValidation'; import { createValidator } from 'vs/workbench/services/preferences/common/preferencesValidation';
@ -70,14 +70,25 @@ export abstract class AbstractSettingsModel extends EditorModel {
filterMatches.push({ filterMatches.push({
setting, setting,
matches: settingMatchResult && settingMatchResult.matches, matches: settingMatchResult && settingMatchResult.matches,
score: settingMatchResult ? settingMatchResult.score : 0 matchType: settingMatchResult?.matchType ?? SettingMatchType.None,
score: settingMatchResult?.score ?? 0
}); });
} }
} }
} }
} }
return filterMatches.sort((a, b) => b.score - a.score); filterMatches.sort((a, b) => {
// Sort by match type if the match types are not equal.
// The priority of the match type is given by the SettingMatchType enum.
// If they're equal, fall back to the "stable sort" counter score.
if (a.matchType !== b.matchType) {
return b.matchType - a.matchType;
} else {
return b.score - a.score;
}
});
return filterMatches;
} }
getPreference(key: string): ISetting | undefined { getPreference(key: string): ISetting | undefined {