@@ -98,28 +98,29 @@ export class LocalSearchProvider implements ISearchProvider {
98
98
}
99
99
100
100
let orderedScore = LocalSearchProvider . START_SCORE ; // Sort is not stable
101
- const useNewKeyMatchingSearch = this . configurationService . getValue ( 'workbench.settings.useWeightedKeySearch' ) === true ;
101
+ const useNewKeyMatchAlgorithm = this . configurationService . getValue ( 'workbench.settings.useWeightedKeySearch' ) === true ;
102
102
const settingMatcher = ( setting : ISetting ) => {
103
103
const { matches, matchType, keyMatchScore } = new SettingMatches (
104
104
this . _filter ,
105
105
setting ,
106
106
true ,
107
107
( filter , setting ) => preferencesModel . findValueMatches ( filter , setting ) ,
108
- useNewKeyMatchingSearch ,
108
+ useNewKeyMatchAlgorithm ,
109
109
this . configurationService
110
110
) ;
111
+ if ( matchType === SettingMatchType . None || matches . length === 0 ) {
112
+ return null ;
113
+ }
114
+
111
115
const score = strings . equalsIgnoreCase ( this . _filter , setting . key ) ?
112
116
LocalSearchProvider . EXACT_MATCH_SCORE :
113
117
orderedScore -- ;
114
-
115
- return matches . length ?
116
- {
117
- matches,
118
- matchType,
119
- keyMatchScore,
120
- score
121
- } :
122
- null ;
118
+ return {
119
+ matches,
120
+ matchType,
121
+ keyMatchScore,
122
+ score
123
+ } ;
123
124
} ;
124
125
125
126
const filterMatches = preferencesModel . filterSettings ( this . _filter , this . getGroupFilter ( this . _filter ) , settingMatcher ) ;
@@ -129,6 +130,14 @@ export class LocalSearchProvider implements ISearchProvider {
129
130
filterMatches : [ exactMatch ] ,
130
131
exactMatch : true
131
132
} ) ;
133
+ } else if ( useNewKeyMatchAlgorithm ) {
134
+ // Filter by the top match type.
135
+ const topMatchType = Math . max ( ...filterMatches . map ( m => m . matchType ) ) ;
136
+ const filteredMatches = filterMatches . filter ( m => m . matchType === topMatchType ) ;
137
+ return Promise . resolve ( {
138
+ filterMatches : filteredMatches ,
139
+ exactMatch : false
140
+ } ) ;
132
141
} else {
133
142
return Promise . resolve ( {
134
143
filterMatches : filterMatches ,
@@ -159,7 +168,7 @@ export class SettingMatches {
159
168
setting : ISetting ,
160
169
private searchDescription : boolean ,
161
170
valuesMatcher : ( filter : string , setting : ISetting ) => IRange [ ] ,
162
- private useNewKeyMatchingSearch : boolean ,
171
+ private useNewKeyMatchAlgorithm : boolean ,
163
172
private readonly configurationService : IConfigurationService
164
173
) {
165
174
this . matches = distinct ( this . _findMatchesInSetting ( searchString , setting ) , ( match ) => `${ match . startLineNumber } _${ match . startColumn } _${ match . endLineNumber } _${ match . endColumn } _` ) ;
@@ -180,53 +189,82 @@ export class SettingMatches {
180
189
return label ;
181
190
}
182
191
192
+ private _toAlphaNumeric ( s : string ) : string {
193
+ return s . replace ( / [ ^ A - Z a - z 0 - 9 ] + / g, '' ) ;
194
+ }
195
+
183
196
private _doFindMatchesInSetting ( searchString : string , setting : ISetting ) : IRange [ ] {
184
197
const descriptionMatchingWords : Map < string , IRange [ ] > = new Map < string , IRange [ ] > ( ) ;
185
198
const keyMatchingWords : Map < string , IRange [ ] > = new Map < string , IRange [ ] > ( ) ;
186
199
const valueMatchingWords : Map < string , IRange [ ] > = new Map < string , IRange [ ] > ( ) ;
187
200
188
- // Key search
201
+ // Key (ID) search
202
+ // First, search by the setting's ID and label.
189
203
const settingKeyAsWords : string = this . _keyToLabel ( setting . key ) ;
190
204
const queryWords = new Set < string > ( searchString . split ( ' ' ) ) ;
191
205
for ( const word of queryWords ) {
192
206
// Check if the key contains the word.
193
207
// Force contiguous matching iff we're using the new algorithm.
194
- const keyMatches = matchesWords ( word , settingKeyAsWords , this . useNewKeyMatchingSearch ) ;
208
+ const keyMatches = matchesWords ( word , settingKeyAsWords , this . useNewKeyMatchAlgorithm ) ;
195
209
if ( keyMatches ?. length ) {
196
210
keyMatchingWords . set ( word , keyMatches . map ( match => this . toKeyRange ( setting , match ) ) ) ;
197
211
}
198
212
}
199
- if ( this . useNewKeyMatchingSearch ) {
213
+ if ( this . useNewKeyMatchAlgorithm ) {
214
+ // New key match algorithm
200
215
if ( keyMatchingWords . size === queryWords . size ) {
201
216
// All words in the query matched with something in the setting key.
202
- this . matchType |= SettingMatchType . KeyMatch ;
203
- // Score based on how many words matched out of the entire key, penalizing longer setting names.
204
- const settingKeyAsWordsCount = settingKeyAsWords . split ( ' ' ) . length ;
205
- this . keyMatchScore = ( keyMatchingWords . size / settingKeyAsWordsCount ) + ( 1 / setting . key . length ) ;
206
- }
207
- const keyMatches = matchesSubString ( searchString , settingKeyAsWords ) ;
208
- if ( keyMatches ?. length ) {
209
- // Handles cases such as "editor formonpast" with missing letters.
210
- keyMatchingWords . set ( searchString , keyMatches . map ( match => this . toKeyRange ( setting , match ) ) ) ;
211
- this . matchType |= SettingMatchType . KeyMatch ;
217
+ // Matches "edit format on paste" to "editor.formatOnPaste".
218
+ this . matchType |= SettingMatchType . AllWordsInSettingsLabel ;
219
+ } else if ( keyMatchingWords . size >= 2 ) {
220
+ // Matches "edit paste" to "editor.formatOnPaste".
221
+ // The if statement reduces noise by preventing "editor formatonpast" from matching all editor settings.
222
+ this . matchType |= SettingMatchType . ContiguousWordsInSettingsLabel ;
212
223
this . keyMatchScore = keyMatchingWords . size ;
213
224
}
225
+ const searchStringAlphaNumeric = this . _toAlphaNumeric ( searchString ) ;
226
+ const keyAlphaNumeric = this . _toAlphaNumeric ( setting . key ) ;
227
+ const keyIdMatches = matchesContiguousSubString ( searchStringAlphaNumeric , keyAlphaNumeric ) ;
228
+ if ( keyIdMatches ?. length ) {
229
+ // Matches "editorformatonp" to "editor.formatonpaste".
230
+ keyMatchingWords . set ( setting . key , keyIdMatches . map ( match => this . toKeyRange ( setting , match ) ) ) ;
231
+ this . matchType |= SettingMatchType . ContiguousQueryInSettingId ;
232
+ }
233
+
234
+ // Fall back to non-contiguous searches if nothing matched yet.
235
+ if ( this . matchType === SettingMatchType . None ) {
236
+ keyMatchingWords . clear ( ) ;
237
+ for ( const word of queryWords ) {
238
+ const keyMatches = matchesWords ( word , settingKeyAsWords , false ) ;
239
+ if ( keyMatches ?. length ) {
240
+ keyMatchingWords . set ( word , keyMatches . map ( match => this . toKeyRange ( setting , match ) ) ) ;
241
+ }
242
+ }
243
+ if ( keyMatchingWords . size >= 2 || ( keyMatchingWords . size === 1 && queryWords . size === 1 ) ) {
244
+ // Matches "edforonpas" to "editor.formatOnPaste".
245
+ // The if statement reduces noise by preventing "editor fomonpast" from matching all editor settings.
246
+ this . matchType |= SettingMatchType . NonContiguousWordsInSettingsLabel ;
247
+ this . keyMatchScore = keyMatchingWords . size ;
248
+ } else {
249
+ const keyIdMatches = matchesSubString ( searchStringAlphaNumeric , keyAlphaNumeric ) ;
250
+ if ( keyIdMatches ?. length ) {
251
+ // Matches "edfmonpas" to "editor.formatOnPaste".
252
+ keyMatchingWords . set ( setting . key , keyIdMatches . map ( match => this . toKeyRange ( setting , match ) ) ) ;
253
+ this . matchType |= SettingMatchType . NonContiguousQueryInSettingId ;
254
+ }
255
+ }
256
+ }
214
257
} else {
215
- // Fall back to the old algorithm.
258
+ // Old key match algorithm
216
259
if ( keyMatchingWords . size ) {
217
- this . matchType |= SettingMatchType . KeyMatch ;
260
+ this . matchType |= SettingMatchType . NonContiguousWordsInSettingsLabel ;
218
261
this . keyMatchScore = keyMatchingWords . size ;
219
262
}
220
- }
221
- const keyIdMatches = matchesContiguousSubString ( searchString , setting . key ) ;
222
- if ( keyIdMatches ?. length ) {
223
- // Handles cases such as "editor.formatonpaste" where the user tries searching for the ID.
224
- keyMatchingWords . set ( setting . key , keyIdMatches . map ( match => this . toKeyRange ( setting , match ) ) ) ;
225
- if ( this . useNewKeyMatchingSearch ) {
226
- this . matchType |= SettingMatchType . KeyMatch ;
227
- this . keyMatchScore = Math . max ( this . keyMatchScore , searchString . length / setting . key . length ) ;
228
- } else {
229
- this . matchType |= SettingMatchType . KeyIdMatch ;
263
+ const keyIdMatches = matchesContiguousSubString ( searchString , setting . key ) ;
264
+ if ( keyIdMatches ?. length ) {
265
+ // Handles cases such as "editor.formatonpaste" where the user tries searching for the ID.
266
+ keyMatchingWords . set ( setting . key , keyIdMatches . map ( match => this . toKeyRange ( setting , match ) ) ) ;
267
+ this . matchType |= SettingMatchType . ContiguousQueryInSettingId ;
230
268
}
231
269
}
232
270
@@ -239,15 +277,12 @@ export class SettingMatches {
239
277
return [ ...keyRanges ] ;
240
278
}
241
279
242
- // New algorithm only: exit early if the key already matched.
243
- if ( this . useNewKeyMatchingSearch && ( this . matchType !== SettingMatchType . None ) ) {
244
- const keyRanges = keyMatchingWords . size ?
245
- Array . from ( keyMatchingWords . values ( ) ) . flat ( ) : [ ] ;
246
- return [ ...keyRanges ] ;
247
- }
248
-
249
280
// Description search
250
- if ( this . searchDescription && this . matchType === SettingMatchType . None ) {
281
+ // Old algorithm: search the description if we haven't matched anything yet.
282
+ // New algorithm: search the description if we found non-contiguous key matches at best.
283
+ const hasContiguousKeyMatchTypes = this . matchType >= SettingMatchType . ContiguousWordsInSettingsLabel ;
284
+ const checkDescription = ( ! this . useNewKeyMatchAlgorithm && this . matchType === SettingMatchType . None ) || ( this . useNewKeyMatchAlgorithm && ! hasContiguousKeyMatchTypes ) ;
285
+ if ( this . searchDescription && checkDescription ) {
251
286
for ( const word of queryWords ) {
252
287
// Search the description lines.
253
288
for ( let lineIndex = 0 ; lineIndex < setting . description . length ; lineIndex ++ ) {
@@ -267,42 +302,47 @@ export class SettingMatches {
267
302
268
303
// Value search
269
304
// Check if the value contains all the words.
270
- if ( setting . enum ?. length ) {
271
- // Search all string values of enums.
272
- for ( const option of setting . enum ) {
273
- if ( typeof option !== 'string' ) {
274
- continue ;
275
- }
276
- valueMatchingWords . clear ( ) ;
277
- for ( const word of queryWords ) {
278
- const valueMatches = matchesContiguousSubString ( word , option ) ;
279
- if ( valueMatches ?. length ) {
280
- valueMatchingWords . set ( word , valueMatches . map ( match => this . toValueRange ( setting , match ) ) ) ;
305
+ // Old algorithm: always search the values.
306
+ // New algorithm: search the values if we found non-contiguous key matches at best.
307
+ const checkValue = ! this . useNewKeyMatchAlgorithm || ! hasContiguousKeyMatchTypes ;
308
+ if ( checkValue ) {
309
+ if ( setting . enum ?. length ) {
310
+ // Search all string values of enums.
311
+ for ( const option of setting . enum ) {
312
+ if ( typeof option !== 'string' ) {
313
+ continue ;
281
314
}
282
- }
283
- if ( valueMatchingWords . size === queryWords . size ) {
284
- this . matchType |= SettingMatchType . DescriptionOrValueMatch ;
285
- break ;
286
- } else {
287
- // Clear out the match for now. We want to require all words to match in the value.
288
315
valueMatchingWords . clear ( ) ;
289
- }
290
- }
291
- } else {
292
- // Search single string value.
293
- const settingValue = this . configurationService . getValue ( setting . key ) ;
294
- if ( typeof settingValue === 'string' ) {
295
- for ( const word of queryWords ) {
296
- const valueMatches = matchesContiguousSubString ( word , settingValue ) ;
297
- if ( valueMatches ?. length ) {
298
- valueMatchingWords . set ( word , valueMatches . map ( match => this . toValueRange ( setting , match ) ) ) ;
316
+ for ( const word of queryWords ) {
317
+ const valueMatches = matchesContiguousSubString ( word , option ) ;
318
+ if ( valueMatches ?. length ) {
319
+ valueMatchingWords . set ( word , valueMatches . map ( match => this . toValueRange ( setting , match ) ) ) ;
320
+ }
321
+ }
322
+ if ( valueMatchingWords . size === queryWords . size ) {
323
+ this . matchType |= SettingMatchType . DescriptionOrValueMatch ;
324
+ break ;
325
+ } else {
326
+ // Clear out the match for now. We want to require all words to match in the value.
327
+ valueMatchingWords . clear ( ) ;
299
328
}
300
329
}
301
- if ( valueMatchingWords . size === queryWords . size ) {
302
- this . matchType |= SettingMatchType . DescriptionOrValueMatch ;
303
- } else {
304
- // Clear out the match for now. We want to require all words to match in the value.
305
- valueMatchingWords . clear ( ) ;
330
+ } else {
331
+ // Search single string value.
332
+ const settingValue = this . configurationService . getValue ( setting . key ) ;
333
+ if ( typeof settingValue === 'string' ) {
334
+ for ( const word of queryWords ) {
335
+ const valueMatches = matchesContiguousSubString ( word , settingValue ) ;
336
+ if ( valueMatches ?. length ) {
337
+ valueMatchingWords . set ( word , valueMatches . map ( match => this . toValueRange ( setting , match ) ) ) ;
338
+ }
339
+ }
340
+ if ( valueMatchingWords . size === queryWords . size ) {
341
+ this . matchType |= SettingMatchType . DescriptionOrValueMatch ;
342
+ } else {
343
+ // Clear out the match for now. We want to require all words to match in the value.
344
+ valueMatchingWords . clear ( ) ;
345
+ }
306
346
}
307
347
}
308
348
}
0 commit comments