Skip to content

Commit 465e8b4

Browse files
committed
Move fuzzy/contiguous filtering into quick pick
1 parent 6fc8217 commit 465e8b4

File tree

4 files changed

+65
-20
lines changed

4 files changed

+65
-20
lines changed

src/vs/base/parts/quickinput/browser/quickInput.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,7 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
450450
private _matchOnDescription = false;
451451
private _matchOnDetail = false;
452452
private _matchOnLabel = true;
453+
private _matchOnLabelMode: 'fuzzy' | 'contiguous' = 'fuzzy';
453454
private _sortByLabel = true;
454455
private _autoFocusOnList = true;
455456
private _keepScrollPosition = false;
@@ -595,6 +596,15 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
595596
this.update();
596597
}
597598

599+
get matchOnLabelMode() {
600+
return this._matchOnLabelMode;
601+
}
602+
603+
set matchOnLabelMode(matchOnLabelMode: 'fuzzy' | 'contiguous') {
604+
this._matchOnLabelMode = matchOnLabelMode;
605+
this.update();
606+
}
607+
598608
get sortByLabel() {
599609
return this._sortByLabel;
600610
}
@@ -994,6 +1004,7 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
9941004
this.ui.list.matchOnDescription = this.matchOnDescription;
9951005
this.ui.list.matchOnDetail = this.matchOnDetail;
9961006
this.ui.list.matchOnLabel = this.matchOnLabel;
1007+
this.ui.list.matchOnLabelMode = this.matchOnLabelMode;
9971008
this.ui.list.sortByLabel = this.sortByLabel;
9981009
if (this.itemsUpdated) {
9991010
this.itemsUpdated = false;

src/vs/base/parts/quickinput/browser/quickInputList.ts

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@ import { compareAnything } from 'vs/base/common/comparers';
1717
import { memoize } from 'vs/base/common/decorators';
1818
import { Emitter, Event } from 'vs/base/common/event';
1919
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';
2121
import { KeyCode } from 'vs/base/common/keyCodes';
2222
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
2323
import * as platform from 'vs/base/common/platform';
24+
import { ltrim } from 'vs/base/common/strings';
2425
import { withNullAsUndefined } from 'vs/base/common/types';
2526
import { IQuickInputOptions } from 'vs/base/parts/quickinput/browser/quickInput';
2627
import { getIconClass } from 'vs/base/parts/quickinput/browser/quickInputUtils';
@@ -258,6 +259,7 @@ export class QuickInputList {
258259
matchOnDescription = false;
259260
matchOnDetail = false;
260261
matchOnLabel = true;
262+
matchOnLabelMode: 'fuzzy' | 'contiguous' = 'fuzzy';
261263
matchOnMeta = true;
262264
sortByLabel = true;
263265
private readonly _onChangedAllVisibleChecked = new Emitter<boolean>();
@@ -628,7 +630,12 @@ export class QuickInputList {
628630
else {
629631
let currentSeparator: IQuickPickSeparator | undefined;
630632
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+
}
632639
const descriptionHighlights = this.matchOnDescription ? withNullAsUndefined(matchesFuzzyIconAware(query, parseLabelWithIcons(element.saneDescription || ''))) : undefined;
633640
const detailHighlights = this.matchOnDetail ? withNullAsUndefined(matchesFuzzyIconAware(query, parseLabelWithIcons(element.saneDetail || ''))) : undefined;
634641
const metaHighlights = this.matchOnMeta ? withNullAsUndefined(matchesFuzzyIconAware(query, parseLabelWithIcons(element.saneMeta || ''))) : undefined;
@@ -726,6 +733,43 @@ export class QuickInputList {
726733
}
727734
}
728735

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.indexOf(word);
767+
if (matchIndex !== -1) {
768+
return [{ start: matchIndex, end: matchIndex + word.length }];
769+
}
770+
return null;
771+
}
772+
729773
function compareEntries(elementA: ListElement, elementB: ListElement, lookFor: string): number {
730774

731775
const labelHighlightsA = elementA.labelHighlights || [];

src/vs/base/parts/quickinput/common/quickInput.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,13 @@ export interface IQuickPick<T extends IQuickPickItem> extends IQuickInput {
292292

293293
matchOnLabel: boolean;
294294

295+
/**
296+
* The mode to filter label with. Fuzzy will use fuzzy searching and
297+
* contiguous will make filter entries that do not contain the exact string
298+
* (including whitespace). This defaults to `'fuzzy'`.
299+
*/
300+
matchOnLabelMode: 'fuzzy' | 'contiguous';
301+
295302
sortByLabel: boolean;
296303

297304
autoFocusOnList: boolean;

src/vs/workbench/contrib/terminal/browser/terminalInstance.ts

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -970,31 +970,14 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
970970
quickPick.sortByLabel = false;
971971
quickPick.placeholder = placeholder;
972972
quickPick.customButton = true;
973-
quickPick.matchOnLabel = filterMode === 'fuzzy';
973+
quickPick.matchOnLabelMode = filterMode || 'contiguous';
974974
if (filterMode === 'fuzzy') {
975975
quickPick.customLabel = nls.localize('terminal.contiguousSearch', 'Use Contiguous Search');
976976
quickPick.onDidCustom(() => {
977977
quickPick.hide();
978978
this.runRecent(type, 'contiguous', quickPick.value);
979979
});
980980
} else {
981-
// contiguous is the default for command
982-
quickPick.onDidChangeValue(value => {
983-
quickPick.items = originalItems.filter(item => {
984-
if (item.type === 'separator') {
985-
return true;
986-
}
987-
item.highlights = undefined;
988-
const matchIndex = item.label.indexOf(value);
989-
if (matchIndex !== -1) {
990-
item.highlights = {
991-
label: [{ start: matchIndex, end: matchIndex + value.length }]
992-
};
993-
return true;
994-
}
995-
return false;
996-
});
997-
});
998981
quickPick.customLabel = nls.localize('terminal.fuzzySearch', 'Use Fuzzy Search');
999982
quickPick.onDidCustom(() => {
1000983
quickPick.hide();

0 commit comments

Comments
 (0)