Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts
5283 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { distinct } from '../../../../base/common/arrays.js';
7
import { CancellationToken } from '../../../../base/common/cancellation.js';
8
import { IStringDictionary } from '../../../../base/common/collections.js';
9
import { IMatch, matchesBaseContiguousSubString, matchesContiguousSubString, matchesSubString, matchesWords } from '../../../../base/common/filters.js';
10
import { Disposable } from '../../../../base/common/lifecycle.js';
11
import * as strings from '../../../../base/common/strings.js';
12
import { TfIdfCalculator, TfIdfDocument } from '../../../../base/common/tfIdf.js';
13
import { IRange } from '../../../../editor/common/core/range.js';
14
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
15
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
16
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
17
import { IAiSettingsSearchService } from '../../../services/aiSettingsSearch/common/aiSettingsSearch.js';
18
import { IGroupFilter, ISearchResult, ISetting, ISettingMatch, ISettingMatcher, ISettingsEditorModel, ISettingsGroup, SettingKeyMatchTypes, SettingMatchType } from '../../../services/preferences/common/preferences.js';
19
import { nullRange } from '../../../services/preferences/common/preferencesModels.js';
20
import { EMBEDDINGS_SEARCH_PROVIDER_NAME, IAiSearchProvider, IPreferencesSearchService, IRemoteSearchProvider, ISearchProvider, IWorkbenchSettingsConfiguration, LLM_RANKED_SEARCH_PROVIDER_NAME, STRING_MATCH_SEARCH_PROVIDER_NAME, TF_IDF_SEARCH_PROVIDER_NAME } from '../common/preferences.js';
21
22
export interface IEndpointDetails {
23
urlBase?: string;
24
key?: string;
25
}
26
27
export class PreferencesSearchService extends Disposable implements IPreferencesSearchService {
28
declare readonly _serviceBrand: undefined;
29
30
private _remoteSearchProvider: IRemoteSearchProvider | undefined;
31
private _aiSearchProvider: IAiSearchProvider | undefined;
32
33
constructor(
34
@IInstantiationService private readonly instantiationService: IInstantiationService,
35
@IConfigurationService private readonly configurationService: IConfigurationService,
36
) {
37
super();
38
}
39
40
getLocalSearchProvider(filter: string): LocalSearchProvider {
41
return this.instantiationService.createInstance(LocalSearchProvider, filter);
42
}
43
44
private get remoteSearchAllowed(): boolean {
45
const workbenchSettings = this.configurationService.getValue<IWorkbenchSettingsConfiguration>().workbench.settings;
46
return workbenchSettings.enableNaturalLanguageSearch;
47
}
48
49
getRemoteSearchProvider(filter: string): IRemoteSearchProvider | undefined {
50
if (!this.remoteSearchAllowed) {
51
return undefined;
52
}
53
54
this._remoteSearchProvider ??= this.instantiationService.createInstance(RemoteSearchProvider);
55
this._remoteSearchProvider.setFilter(filter);
56
return this._remoteSearchProvider;
57
}
58
59
getAiSearchProvider(filter: string): IAiSearchProvider | undefined {
60
if (!this.remoteSearchAllowed) {
61
return undefined;
62
}
63
64
this._aiSearchProvider ??= this.instantiationService.createInstance(AiSearchProvider);
65
this._aiSearchProvider.setFilter(filter);
66
return this._aiSearchProvider;
67
}
68
}
69
70
function cleanFilter(filter: string): string {
71
// Remove " and : which are likely to be copypasted as part of a setting name.
72
// Leave other special characters which the user might want to search for.
73
return filter
74
.replace(/[":]/g, ' ')
75
.replace(/ /g, ' ')
76
.trim();
77
}
78
79
export class LocalSearchProvider implements ISearchProvider {
80
constructor(
81
private _filter: string,
82
@IConfigurationService private readonly configurationService: IConfigurationService
83
) {
84
this._filter = cleanFilter(this._filter);
85
}
86
87
searchModel(preferencesModel: ISettingsEditorModel, token: CancellationToken): Promise<ISearchResult | null> {
88
if (!this._filter) {
89
return Promise.resolve(null);
90
}
91
92
const settingMatcher: ISettingMatcher = (setting: ISetting) => {
93
let { matches, matchType, keyMatchScore } = new SettingMatches(
94
this._filter,
95
setting,
96
true,
97
this.configurationService
98
);
99
if (matchType === SettingMatchType.None || matches.length === 0) {
100
return null;
101
}
102
if (strings.equalsIgnoreCase(this._filter, setting.key)) {
103
matchType = SettingMatchType.ExactMatch;
104
}
105
return {
106
matches,
107
matchType,
108
keyMatchScore,
109
score: 0 // only used for RemoteSearchProvider matches.
110
};
111
};
112
113
const filterMatches = preferencesModel.filterSettings(this._filter, this.getGroupFilter(this._filter), settingMatcher);
114
115
// Check the top key match type.
116
const topKeyMatchType = Math.max(...filterMatches.map(m => (m.matchType & SettingKeyMatchTypes)));
117
// Always allow description matches as part of https://github.com/microsoft/vscode/issues/239936.
118
const alwaysAllowedMatchTypes = SettingMatchType.DescriptionOrValueMatch | SettingMatchType.LanguageTagSettingMatch;
119
const filteredMatches = filterMatches
120
.filter(m => (m.matchType & topKeyMatchType) || (m.matchType & alwaysAllowedMatchTypes) || m.matchType === SettingMatchType.ExactMatch)
121
.map(m => ({ ...m, providerName: STRING_MATCH_SEARCH_PROVIDER_NAME }));
122
return Promise.resolve({
123
filterMatches: filteredMatches,
124
exactMatch: filteredMatches.some(m => m.matchType === SettingMatchType.ExactMatch)
125
});
126
}
127
128
private getGroupFilter(filter: string): IGroupFilter {
129
const regex = strings.createRegExp(filter, false, { global: true });
130
return (group: ISettingsGroup) => {
131
return group.id !== 'defaultOverrides' && regex.test(group.title);
132
};
133
}
134
}
135
136
export class SettingMatches {
137
readonly matches: IRange[];
138
matchType: SettingMatchType = SettingMatchType.None;
139
/**
140
* A match score for key matches to allow comparing key matches against each other.
141
* Otherwise, all key matches are treated the same, and sorting is done by ToC order.
142
*/
143
keyMatchScore: number = 0;
144
145
constructor(
146
searchString: string,
147
setting: ISetting,
148
private searchDescription: boolean,
149
private readonly configurationService: IConfigurationService
150
) {
151
this.matches = distinct(this._findMatchesInSetting(searchString, setting), (match) => `${match.startLineNumber}_${match.startColumn}_${match.endLineNumber}_${match.endColumn}_`);
152
}
153
154
private _findMatchesInSetting(searchString: string, setting: ISetting): IRange[] {
155
const result = this._doFindMatchesInSetting(searchString, setting);
156
return result;
157
}
158
159
private _keyToLabel(settingId: string): string {
160
const label = settingId
161
.replace(/[-._]/g, ' ')
162
.replace(/([a-z]+)([A-Z])/g, '$1 $2')
163
.replace(/([A-Za-z]+)(\d+)/g, '$1 $2')
164
.replace(/(\d+)([A-Za-z]+)/g, '$1 $2')
165
.toLowerCase();
166
return label;
167
}
168
169
private _toAlphaNumeric(s: string): string {
170
return s.replace(/[^\p{L}\p{N}]+/gu, '');
171
}
172
173
private _doFindMatchesInSetting(searchString: string, setting: ISetting): IRange[] {
174
const descriptionMatchingWords: Map<string, IRange[]> = new Map<string, IRange[]>();
175
const keyMatchingWords: Map<string, IRange[]> = new Map<string, IRange[]>();
176
const valueMatchingWords: Map<string, IRange[]> = new Map<string, IRange[]>();
177
178
// Key (ID) search
179
// First, search by the setting's ID and label.
180
const settingKeyAsWords: string = this._keyToLabel(setting.key);
181
const queryWords = new Set<string>(searchString.split(' '));
182
for (const word of queryWords) {
183
// Check if the key contains the word. Use contiguous search.
184
const keyMatches = matchesWords(word, settingKeyAsWords, true);
185
if (keyMatches?.length) {
186
keyMatchingWords.set(word, keyMatches.map(match => this.toKeyRange(setting, match)));
187
}
188
}
189
if (keyMatchingWords.size === queryWords.size) {
190
// All words in the query matched with something in the setting key.
191
// Matches "edit format on paste" to "editor.formatOnPaste".
192
this.matchType |= SettingMatchType.AllWordsInSettingsLabel;
193
} else if (keyMatchingWords.size >= 2) {
194
// Matches "edit paste" to "editor.formatOnPaste".
195
// The if statement reduces noise by preventing "editor formatonpast" from matching all editor settings.
196
this.matchType |= SettingMatchType.ContiguousWordsInSettingsLabel;
197
this.keyMatchScore = keyMatchingWords.size;
198
}
199
const searchStringAlphaNumeric = this._toAlphaNumeric(searchString);
200
const keyAlphaNumeric = this._toAlphaNumeric(setting.key);
201
const keyIdMatches = matchesContiguousSubString(searchStringAlphaNumeric, keyAlphaNumeric);
202
if (keyIdMatches?.length) {
203
// Matches "editorformatonp" to "editor.formatonpaste".
204
keyMatchingWords.set(setting.key, keyIdMatches.map(match => this.toKeyRange(setting, match)));
205
this.matchType |= SettingMatchType.ContiguousQueryInSettingId;
206
}
207
208
// Fall back to non-contiguous key (ID) searches if nothing matched yet.
209
if (this.matchType === SettingMatchType.None) {
210
keyMatchingWords.clear();
211
for (const word of queryWords) {
212
const keyMatches = matchesWords(word, settingKeyAsWords, false);
213
if (keyMatches?.length) {
214
keyMatchingWords.set(word, keyMatches.map(match => this.toKeyRange(setting, match)));
215
}
216
}
217
if (keyMatchingWords.size >= 2 || (keyMatchingWords.size === 1 && queryWords.size === 1)) {
218
// Matches "edforonpas" to "editor.formatOnPaste".
219
// The if statement reduces noise by preventing "editor fomonpast" from matching all editor settings.
220
this.matchType |= SettingMatchType.NonContiguousWordsInSettingsLabel;
221
this.keyMatchScore = keyMatchingWords.size;
222
} else {
223
const keyIdMatches = matchesSubString(searchStringAlphaNumeric, keyAlphaNumeric);
224
if (keyIdMatches?.length) {
225
// Matches "edfmonpas" to "editor.formatOnPaste".
226
keyMatchingWords.set(setting.key, keyIdMatches.map(match => this.toKeyRange(setting, match)));
227
this.matchType |= SettingMatchType.NonContiguousQueryInSettingId;
228
}
229
}
230
}
231
232
// Check if the match was for a language tag group setting such as [markdown].
233
// In such a case, move that setting to be last.
234
if (setting.overrides?.length && (this.matchType !== SettingMatchType.None)) {
235
this.matchType = SettingMatchType.LanguageTagSettingMatch;
236
const keyRanges = keyMatchingWords.size ?
237
Array.from(keyMatchingWords.values()).flat() : [];
238
return [...keyRanges];
239
}
240
241
// Description search
242
// Search the description if we found non-contiguous key matches at best.
243
const hasContiguousKeyMatchTypes = this.matchType >= SettingMatchType.ContiguousWordsInSettingsLabel;
244
if (this.searchDescription && !hasContiguousKeyMatchTypes) {
245
// Search the description lines and any additional keywords.
246
const searchableLines = setting.keywords?.length
247
? [...setting.description, setting.keywords.join(' ')]
248
: setting.description;
249
for (const word of queryWords) {
250
for (let lineIndex = 0; lineIndex < searchableLines.length; lineIndex++) {
251
const descriptionMatches = matchesBaseContiguousSubString(word, searchableLines[lineIndex]);
252
if (descriptionMatches?.length) {
253
descriptionMatchingWords.set(word, descriptionMatches.map(match => this.toDescriptionRange(setting, match, lineIndex)));
254
}
255
}
256
}
257
if (descriptionMatchingWords.size === queryWords.size) {
258
this.matchType |= SettingMatchType.DescriptionOrValueMatch;
259
} else {
260
// Clear out the match for now. We want to require all words to match in the description.
261
descriptionMatchingWords.clear();
262
}
263
}
264
265
// Value search
266
// Check if the value contains all the words.
267
// Search the values if we found non-contiguous key matches at best.
268
if (!hasContiguousKeyMatchTypes) {
269
if (setting.enum?.length) {
270
// Search all string values of enums.
271
for (const option of setting.enum) {
272
if (typeof option !== 'string') {
273
continue;
274
}
275
valueMatchingWords.clear();
276
for (const word of queryWords) {
277
const valueMatches = matchesContiguousSubString(word, option);
278
if (valueMatches?.length) {
279
valueMatchingWords.set(word, valueMatches.map(match => this.toValueRange(setting, match)));
280
}
281
}
282
if (valueMatchingWords.size === queryWords.size) {
283
this.matchType |= SettingMatchType.DescriptionOrValueMatch;
284
break;
285
} else {
286
// Clear out the match for now. We want to require all words to match in the value.
287
valueMatchingWords.clear();
288
}
289
}
290
} else {
291
// Search single string value.
292
const settingValue = this.configurationService.getValue(setting.key);
293
if (typeof settingValue === 'string') {
294
for (const word of queryWords) {
295
const valueMatches = matchesContiguousSubString(word, settingValue);
296
if (valueMatches?.length) {
297
valueMatchingWords.set(word, valueMatches.map(match => this.toValueRange(setting, match)));
298
}
299
}
300
if (valueMatchingWords.size === queryWords.size) {
301
this.matchType |= SettingMatchType.DescriptionOrValueMatch;
302
} else {
303
// Clear out the match for now. We want to require all words to match in the value.
304
valueMatchingWords.clear();
305
}
306
}
307
}
308
}
309
310
const descriptionRanges = descriptionMatchingWords.size ?
311
Array.from(descriptionMatchingWords.values()).flat() : [];
312
const keyRanges = keyMatchingWords.size ?
313
Array.from(keyMatchingWords.values()).flat() : [];
314
const valueRanges = valueMatchingWords.size ?
315
Array.from(valueMatchingWords.values()).flat() : [];
316
return [...descriptionRanges, ...keyRanges, ...valueRanges];
317
}
318
319
private toKeyRange(setting: ISetting, match: IMatch): IRange {
320
return {
321
startLineNumber: setting.keyRange.startLineNumber,
322
startColumn: setting.keyRange.startColumn + match.start,
323
endLineNumber: setting.keyRange.startLineNumber,
324
endColumn: setting.keyRange.startColumn + match.end
325
};
326
}
327
328
private toDescriptionRange(setting: ISetting, match: IMatch, lineIndex: number): IRange {
329
const descriptionRange = setting.descriptionRanges[lineIndex];
330
if (!descriptionRange) {
331
// This case occurs with added settings such as the
332
// manage extension setting.
333
return nullRange;
334
}
335
return {
336
startLineNumber: descriptionRange.startLineNumber,
337
startColumn: descriptionRange.startColumn + match.start,
338
endLineNumber: descriptionRange.endLineNumber,
339
endColumn: descriptionRange.startColumn + match.end
340
};
341
}
342
343
private toValueRange(setting: ISetting, match: IMatch): IRange {
344
return {
345
startLineNumber: setting.valueRange.startLineNumber,
346
startColumn: setting.valueRange.startColumn + match.start + 1,
347
endLineNumber: setting.valueRange.startLineNumber,
348
endColumn: setting.valueRange.startColumn + match.end + 1
349
};
350
}
351
}
352
353
class SettingsRecordProvider {
354
private _settingsRecord: IStringDictionary<ISetting> = {};
355
private _currentPreferencesModel: ISettingsEditorModel | undefined;
356
357
constructor() { }
358
359
updateModel(preferencesModel: ISettingsEditorModel) {
360
if (preferencesModel === this._currentPreferencesModel) {
361
return;
362
}
363
364
this._currentPreferencesModel = preferencesModel;
365
this.refresh();
366
}
367
368
private refresh() {
369
this._settingsRecord = {};
370
371
if (!this._currentPreferencesModel) {
372
return;
373
}
374
375
for (const group of this._currentPreferencesModel.settingsGroups) {
376
if (group.id === 'mostCommonlyUsed') {
377
continue;
378
}
379
for (const section of group.sections) {
380
for (const setting of section.settings) {
381
this._settingsRecord[setting.key] = setting;
382
}
383
}
384
}
385
}
386
387
getSettingsRecord(): IStringDictionary<ISetting> {
388
return this._settingsRecord;
389
}
390
}
391
392
class EmbeddingsSearchProvider implements IRemoteSearchProvider {
393
private static readonly EMBEDDINGS_SETTINGS_SEARCH_MAX_PICKS = 10;
394
395
private readonly _recordProvider: SettingsRecordProvider;
396
private _filter: string = '';
397
398
constructor(
399
private readonly _aiSettingsSearchService: IAiSettingsSearchService
400
) {
401
this._recordProvider = new SettingsRecordProvider();
402
}
403
404
setFilter(filter: string) {
405
this._filter = cleanFilter(filter);
406
}
407
408
async searchModel(preferencesModel: ISettingsEditorModel, token: CancellationToken): Promise<ISearchResult | null> {
409
if (!this._filter || !this._aiSettingsSearchService.isEnabled()) {
410
return null;
411
}
412
413
this._recordProvider.updateModel(preferencesModel);
414
this._aiSettingsSearchService.startSearch(this._filter, token);
415
416
return {
417
filterMatches: await this.getEmbeddingsItems(token),
418
exactMatch: false
419
};
420
}
421
422
private async getEmbeddingsItems(token: CancellationToken): Promise<ISettingMatch[]> {
423
const settingsRecord = this._recordProvider.getSettingsRecord();
424
const filterMatches: ISettingMatch[] = [];
425
const settings = await this._aiSettingsSearchService.getEmbeddingsResults(this._filter, token);
426
if (!settings) {
427
return [];
428
}
429
430
const providerName = EMBEDDINGS_SEARCH_PROVIDER_NAME;
431
for (const settingKey of settings) {
432
if (filterMatches.length === EmbeddingsSearchProvider.EMBEDDINGS_SETTINGS_SEARCH_MAX_PICKS) {
433
break;
434
}
435
filterMatches.push({
436
setting: settingsRecord[settingKey],
437
matches: [settingsRecord[settingKey].range],
438
matchType: SettingMatchType.RemoteMatch,
439
keyMatchScore: 0,
440
score: 0, // the results are sorted upstream.
441
providerName
442
});
443
}
444
445
return filterMatches;
446
}
447
}
448
449
class TfIdfSearchProvider implements IRemoteSearchProvider {
450
private static readonly TF_IDF_PRE_NORMALIZE_THRESHOLD = 50;
451
private static readonly TF_IDF_POST_NORMALIZE_THRESHOLD = 0.7;
452
private static readonly TF_IDF_MAX_PICKS = 5;
453
454
private _currentPreferencesModel: ISettingsEditorModel | undefined;
455
private _filter: string = '';
456
private _documents: TfIdfDocument[] = [];
457
private _settingsRecord: IStringDictionary<ISetting> = {};
458
459
constructor() {
460
}
461
462
setFilter(filter: string) {
463
this._filter = cleanFilter(filter);
464
}
465
466
keyToLabel(settingId: string): string {
467
const label = settingId
468
.replace(/[-._]/g, ' ')
469
.replace(/([a-z]+)([A-Z])/g, '$1 $2')
470
.replace(/([A-Za-z]+)(\d+)/g, '$1 $2')
471
.replace(/(\d+)([A-Za-z]+)/g, '$1 $2')
472
.toLowerCase();
473
return label;
474
}
475
476
settingItemToEmbeddingString(item: ISetting): string {
477
let result = `Setting Id: ${item.key}\n`;
478
result += `Label: ${this.keyToLabel(item.key)}\n`;
479
result += `Description: ${item.description}\n`;
480
return result;
481
}
482
483
async searchModel(preferencesModel: ISettingsEditorModel, token: CancellationToken): Promise<ISearchResult | null> {
484
if (!this._filter) {
485
return null;
486
}
487
488
if (this._currentPreferencesModel !== preferencesModel) {
489
// Refresh the documents and settings record
490
this._currentPreferencesModel = preferencesModel;
491
this._documents = [];
492
this._settingsRecord = {};
493
for (const group of preferencesModel.settingsGroups) {
494
if (group.id === 'mostCommonlyUsed') {
495
continue;
496
}
497
for (const section of group.sections) {
498
for (const setting of section.settings) {
499
this._documents.push({
500
key: setting.key,
501
textChunks: [this.settingItemToEmbeddingString(setting)]
502
});
503
this._settingsRecord[setting.key] = setting;
504
}
505
}
506
}
507
}
508
509
return {
510
filterMatches: await this.getTfIdfItems(token),
511
exactMatch: false
512
};
513
}
514
515
private async getTfIdfItems(token: CancellationToken): Promise<ISettingMatch[]> {
516
const filterMatches: ISettingMatch[] = [];
517
const tfIdfCalculator = new TfIdfCalculator();
518
tfIdfCalculator.updateDocuments(this._documents);
519
const tfIdfRankings = tfIdfCalculator.calculateScores(this._filter, token);
520
tfIdfRankings.sort((a, b) => b.score - a.score);
521
const maxScore = tfIdfRankings[0].score;
522
523
if (maxScore < TfIdfSearchProvider.TF_IDF_PRE_NORMALIZE_THRESHOLD) {
524
// Reject all the matches.
525
return [];
526
}
527
528
for (const info of tfIdfRankings) {
529
if (info.score / maxScore < TfIdfSearchProvider.TF_IDF_POST_NORMALIZE_THRESHOLD || filterMatches.length === TfIdfSearchProvider.TF_IDF_MAX_PICKS) {
530
break;
531
}
532
const pick = info.key;
533
filterMatches.push({
534
setting: this._settingsRecord[pick],
535
matches: [this._settingsRecord[pick].range],
536
matchType: SettingMatchType.RemoteMatch,
537
keyMatchScore: 0,
538
score: info.score,
539
providerName: TF_IDF_SEARCH_PROVIDER_NAME
540
});
541
}
542
543
return filterMatches;
544
}
545
}
546
547
class RemoteSearchProvider implements IRemoteSearchProvider {
548
private _tfIdfSearchProvider: TfIdfSearchProvider;
549
private _filter: string = '';
550
551
constructor() {
552
this._tfIdfSearchProvider = new TfIdfSearchProvider();
553
}
554
555
setFilter(filter: string): void {
556
this._filter = filter;
557
this._tfIdfSearchProvider.setFilter(filter);
558
}
559
560
async searchModel(preferencesModel: ISettingsEditorModel, token: CancellationToken): Promise<ISearchResult | null> {
561
if (!this._filter) {
562
return null;
563
}
564
565
const results = await this._tfIdfSearchProvider.searchModel(preferencesModel, token);
566
return results;
567
}
568
}
569
570
class AiSearchProvider implements IAiSearchProvider {
571
private readonly _embeddingsSearchProvider: EmbeddingsSearchProvider;
572
private readonly _recordProvider: SettingsRecordProvider;
573
private _filter: string = '';
574
575
constructor(
576
@IAiSettingsSearchService private readonly aiSettingsSearchService: IAiSettingsSearchService
577
) {
578
this._embeddingsSearchProvider = new EmbeddingsSearchProvider(this.aiSettingsSearchService);
579
this._recordProvider = new SettingsRecordProvider();
580
}
581
582
setFilter(filter: string): void {
583
this._filter = filter;
584
this._embeddingsSearchProvider.setFilter(filter);
585
}
586
587
async searchModel(preferencesModel: ISettingsEditorModel, token: CancellationToken): Promise<ISearchResult | null> {
588
if (!this._filter || !this.aiSettingsSearchService.isEnabled()) {
589
return null;
590
}
591
592
this._recordProvider.updateModel(preferencesModel);
593
const results = await this._embeddingsSearchProvider.searchModel(preferencesModel, token);
594
return results;
595
}
596
597
async getLLMRankedResults(token: CancellationToken): Promise<ISearchResult | null> {
598
if (!this._filter || !this.aiSettingsSearchService.isEnabled()) {
599
return null;
600
}
601
602
const items = await this.getLLMRankedItems(token);
603
return {
604
filterMatches: items,
605
exactMatch: false
606
};
607
}
608
609
private async getLLMRankedItems(token: CancellationToken): Promise<ISettingMatch[]> {
610
const settingsRecord = this._recordProvider.getSettingsRecord();
611
const filterMatches: ISettingMatch[] = [];
612
const settings = await this.aiSettingsSearchService.getLLMRankedResults(this._filter, token);
613
if (!settings) {
614
return [];
615
}
616
617
for (const settingKey of settings) {
618
if (!settingsRecord[settingKey]) {
619
// Non-existent setting.
620
continue;
621
}
622
filterMatches.push({
623
setting: settingsRecord[settingKey],
624
matches: [settingsRecord[settingKey].range],
625
matchType: SettingMatchType.RemoteMatch,
626
keyMatchScore: 0,
627
score: 0, // the results are sorted upstream.
628
providerName: LLM_RANKED_SEARCH_PROVIDER_NAME
629
});
630
}
631
632
return filterMatches;
633
}
634
}
635
636
registerSingleton(IPreferencesSearchService, PreferencesSearchService, InstantiationType.Delayed);
637
638