How to set options for fuse.js when I want the same behavior like VSCode (Ctrl+P) file search?
On official example, with {ignoreLocation: true}
search pattern = "tprt"
'The Preservationist' should be first as it has all 4 searched characters (indices), instead of 'Incompetence' which only has 2
The simplest solution that I've found to achieve similar results is to use an upper filter function like VS Code does before using Fuse. For example, you can do something like this:
class FuseFilter {
fuse;
constructor(collection) {
this.fuse = new Fuse(collection, {
keys: ['title'],
isCaseSensitive: false,
threshold: 0.9,
findAllMatches: false,
shouldSort: true,
});
}
process(terms, collection) {
const items = this.upperFilter(terms, collection);
this.fuse.setCollection(items);
const result = this.fuse.search(terms);
return result.map(res => res.item); // Return the actual items
}
upperFilter(terms, collection) {
const regExp = this.createFilterRegExp(terms);
return collection.filter(i => regExp.test(i.title));
}
createFilterRegExp(terms) {
terms = terms.toLowerCase();
const set = new Set();
for (let i = 0; i < terms.length; i++) {
set.add(terms[i]);
}
const regExpStr = Array.from(set.keys())
.filter(c => /^\w$/.test(c))
.join('.*');
return new RegExp(regExpStr, 'i');
}
}
const rawEntries = [
{ title: 'Bitmap' },
{ title: 'Sprite' },
{ title: 'Window_Base' },
{ title: 'Window_Select' },
{ title: 'Window_Help' },
{ title: 'Game_Entity' },
{ title: 'Game_Character' },
{ title: 'Game_Player' },
{ title: 'Game_Map' },
{ title: 'Game_Item' },
{ title: 'Scene_Title' },
{ title: 'Scene_Map' },
{ title: 'Scene_Menu' },
{ title: 'Main' },
];
function init() {
const termsInput = document.getElementById('termsInput');
const resultsContainer = document.getElementById('resultsContainer');
const fuseFilter = new FuseFilter(rawEntries);
function renderEntries(entries) {
resultsContainer.textContent = '';
entries.forEach(i => {
const el = document.createElement('div');
el.className = 'px-6 py-2 text-base hover:bg-teal-600/15';
el.textContent = i.title;
resultsContainer.appendChild(el);
});
}
function handleInputChange() {
const terms = termsInput.value;
if (!terms) {
renderEntries(rawEntries);
return;
}
const result = fuseFilter.process(terms, rawEntries);
renderEntries(result);
}
termsInput.addEventListener('input', handleInputChange);
renderEntries(rawEntries);
}
init();
<script src="https://cdn.jsdelivr.net/npm/fuse.js/dist/fuse.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
<div
class="w-full max-w-md mx-auto flex flex-col items-strecth text-teal-950 border border-teal-900/15"
>
<div class=" bg-zinc-50 flex items-center justify-center gap-3 p-3">
<input
type='text'
id="termsInput"
class="block flex-1 text-sm px-2 py-2 rounded outline-none focus-visible:ring ring-teal-500/30"
/>
</div>
<div
id="resultsContainer"
class="w-full max-w-lg"
/>
</div>