@@ -17,10 +17,11 @@ import { compareAnything } from 'vs/base/common/comparers';
17
17
import { memoize } from 'vs/base/common/decorators' ;
18
18
import { Emitter , Event } from 'vs/base/common/event' ;
19
19
import { IMatch } from 'vs/base/common/filters' ;
20
- import { matchesFuzzyIconAware , parseLabelWithIcons } from 'vs/base/common/iconLabels' ;
20
+ import { IParsedLabelWithIcons , matchesFuzzyIconAware , parseLabelWithIcons } from 'vs/base/common/iconLabels' ;
21
21
import { KeyCode } from 'vs/base/common/keyCodes' ;
22
22
import { dispose , IDisposable } from 'vs/base/common/lifecycle' ;
23
23
import * as platform from 'vs/base/common/platform' ;
24
+ import { ltrim } from 'vs/base/common/strings' ;
24
25
import { withNullAsUndefined } from 'vs/base/common/types' ;
25
26
import { IQuickInputOptions } from 'vs/base/parts/quickinput/browser/quickInput' ;
26
27
import { getIconClass } from 'vs/base/parts/quickinput/browser/quickInputUtils' ;
@@ -258,6 +259,7 @@ export class QuickInputList {
258
259
matchOnDescription = false ;
259
260
matchOnDetail = false ;
260
261
matchOnLabel = true ;
262
+ matchOnLabelMode : 'fuzzy' | 'contiguous' = 'fuzzy' ;
261
263
matchOnMeta = true ;
262
264
sortByLabel = true ;
263
265
private readonly _onChangedAllVisibleChecked = new Emitter < boolean > ( ) ;
@@ -628,7 +630,12 @@ export class QuickInputList {
628
630
else {
629
631
let currentSeparator : IQuickPickSeparator | undefined ;
630
632
this . elements . forEach ( element => {
631
- const labelHighlights = this . matchOnLabel ? withNullAsUndefined ( matchesFuzzyIconAware ( query , parseLabelWithIcons ( element . saneLabel ) ) ) : undefined ;
633
+ let labelHighlights : IMatch [ ] | undefined ;
634
+ if ( this . matchOnLabelMode === 'fuzzy' ) {
635
+ labelHighlights = this . matchOnLabel ? withNullAsUndefined ( matchesFuzzyIconAware ( query , parseLabelWithIcons ( element . saneLabel ) ) ) : undefined ;
636
+ } else {
637
+ labelHighlights = this . matchOnLabel ? withNullAsUndefined ( matchesContiguousIconAware ( query , parseLabelWithIcons ( element . saneLabel ) ) ) : undefined ;
638
+ }
632
639
const descriptionHighlights = this . matchOnDescription ? withNullAsUndefined ( matchesFuzzyIconAware ( query , parseLabelWithIcons ( element . saneDescription || '' ) ) ) : undefined ;
633
640
const detailHighlights = this . matchOnDetail ? withNullAsUndefined ( matchesFuzzyIconAware ( query , parseLabelWithIcons ( element . saneDetail || '' ) ) ) : undefined ;
634
641
const metaHighlights = this . matchOnMeta ? withNullAsUndefined ( matchesFuzzyIconAware ( query , parseLabelWithIcons ( element . saneMeta || '' ) ) ) : undefined ;
@@ -726,6 +733,43 @@ export class QuickInputList {
726
733
}
727
734
}
728
735
736
+ export function matchesContiguousIconAware ( query : string , target : IParsedLabelWithIcons , enableSeparateSubstringMatching = false ) : IMatch [ ] | null {
737
+
738
+ const { text, iconOffsets } = target ;
739
+
740
+ // Return early if there are no icon markers in the word to match against
741
+ if ( ! iconOffsets || iconOffsets . length === 0 ) {
742
+ return matchesContiguous ( query , text , enableSeparateSubstringMatching ) ;
743
+ }
744
+
745
+ // Trim the word to match against because it could have leading
746
+ // whitespace now if the word started with an icon
747
+ const wordToMatchAgainstWithoutIconsTrimmed = ltrim ( text , ' ' ) ;
748
+ const leadingWhitespaceOffset = text . length - wordToMatchAgainstWithoutIconsTrimmed . length ;
749
+
750
+ // match on value without icon
751
+ const matches = matchesContiguous ( query , wordToMatchAgainstWithoutIconsTrimmed , enableSeparateSubstringMatching ) ;
752
+
753
+ // Map matches back to offsets with icon and trimming
754
+ if ( matches ) {
755
+ for ( const match of matches ) {
756
+ const iconOffset = iconOffsets [ match . start + leadingWhitespaceOffset ] /* icon offsets at index */ + leadingWhitespaceOffset /* overall leading whitespace offset */ ;
757
+ match . start += iconOffset ;
758
+ match . end += iconOffset ;
759
+ }
760
+ }
761
+
762
+ return matches ;
763
+ }
764
+
765
+ function matchesContiguous ( word : string , wordToMatchAgainst : string , enableSeparateSubstringMatching = false ) : IMatch [ ] | null {
766
+ const matchIndex = wordToMatchAgainst . toLowerCase ( ) . indexOf ( word . toLowerCase ( ) ) ;
767
+ if ( matchIndex !== - 1 ) {
768
+ return [ { start : matchIndex , end : matchIndex + word . length } ] ;
769
+ }
770
+ return null ;
771
+ }
772
+
729
773
function compareEntries ( elementA : ListElement , elementB : ListElement , lookFor : string ) : number {
730
774
731
775
const labelHighlightsA = elementA . labelHighlights || [ ] ;
0 commit comments