#FuzzyJsPlus
#FuzzyMatching
#JavaScript
#NodeJS
#Deno
#CLI
#Async
#FZF
#Golang
#WebDevelopment
#TypeScript
FuzzyJsPlus is a port of the fuzzy finder command line tool FZF to JavaScript. While not as performant as its Golang counterpart, it provides fast and efficient fuzzy matching capabilities in JavaScript environments, such as Node.js and Deno, or even in the browser.
Use the Fzf
class to create an instance and perform a fuzzy match on a list of
strings:
// filename: basicUsage.js
import { Fzf } from "fzf";
const list = [
"go",
"javascript",
"python",
"rust",
"swift",
"kotlin",
"elixir",
"java",
"lisp",
"v",
"zig",
"nim",
"rescript",
"d",
"haskell",
];
const fzf = new Fzf(list);
const entries = fzf.find("li");
const ranking = entries.map((entry) => entry.item).join(", ");
console.log(ranking); // Output: lisp, kotlin, elixir
Fzf
can also handle lists that aren't plain strings by passing a selector
function in the options:
// filename: nonStringList.js
const list = [
{ id: "1", displayName: "abcd" },
{ id: "2", displayName: "bcde" },
// ...additional items...
];
const fzf = new Fzf(list, {
selector: (item) => item.displayName,
});
By default, Fzf
performs smart case searching, meaning it takes into account
both uppercase and lowercase. You can customize this behavior in the options.
Fzf
provides the indices of matched characters, which you can use to highlight
matched characters, for example, using a React component:
// filename: highlightMatch.js
const HighlightChars = (props) => {
const chars = props.str.split("");
const nodes = chars.map((char, i) => {
if (props.indices.has(i)) {
return <b key={i}>{char}</b>;
} else {
return char;
}
});
return <>{nodes}</>;
};
// usage
const reactElement = (
<HighlightChars str={entry.item.normalize()} indices={entry.positions} />
);
Tiebreakers are functions that Fzf
uses to rank results when they have the
same score. You can provide your own or use the built-in ones:
// filename: tiebreakers.js
import { byLengthAsc, Fzf } from "fzf";
const fzf = new Fzf(list, {
tiebreakers: [byLengthAsc],
});
You can disable the sorter by passing sort: false
in the options:
// filename: disableSort.js
const fzf = new Fzf(list, {
sort
## ASSISTANT
: false,
});
To prefer characters at the end of strings, use forward: false
in the options:
// filename: matchBackwards.js
const fzf = new Fzf(list, {
forward: false,
});
If Fzf
takes a noticeable amount of time to return results, you can use a
faster version for the first few characters of the query and switch back to the
default when more characters are entered. Or opt for an async finder if queries
take around 100ms to resolve:
// filename: asyncFinder.js
const fzf = new Fzf(list);
const fzfFast = new Fzf(list, {
fuzzy: "v1",
});
if (query.length <= 3) {
return fzfFast.find(query);
} else {
return fzf.find(query);
}
In these scenarios, opting for the v1
version of the fuzzy algorithm can speed
up the process, especially when the query contains very few characters. The
default version, v2
, provides better matched positions for items, which may
not be noticeable in queries with few characters.
To use the async finder, import AsyncFzf
instead of Fzf
and handle the
returned promise:
// filename: asyncFinder.js
import { AsyncFzf } from "fzf";
const fzf = new AsyncFzf(list);
fzf.find(query)
.then((result) => {/* process `result` */})
.catch(() => {}); // Handle cancellation errors
For TypeScript, use the Fzf
, FzfResultItem
, and FzfOptions
types as
demonstrated below:
// filename: typescriptUsage.ts
import { Fzf, FzfOptions, FzfResultItem } from "fzf";
interface Fruit {
id: string;
name: string;
}
const fruits: Fruit[] = [/* ... */];
const options: FzfOptions<Fruit> = { selector: (v) => v.name };
const fzf = new Fzf<Fruit[]>(fruits, options);
const results: FzfResultItem<Fruit>[] = fzf.find("ppya"); // Returns a papaya!
To mimic FZF CLI behavior, you can set the match
option to extendedMatch
and
add a tiebreaker function byTrimmedLengthAsc
:
// filename: cliLikeBehavior.js
import { extendedMatch, Fzf } from "fzf";
function byTrimmedLengthAsc(a, b, selector) {
return selector(a.item).trim().length - selector(b.item).trim().length;
}
const fzf = new Fzf(list, {
match: extendedMatch,
tiebreakers: [byTrimmedLengthAsc],
});
If the list is modified after initialization, re-initialize FZF to refresh the internal calculations:
// filename: listModification.js
list.push("newItem");
const fzf = new Fzf(list); // Re-initialize after list modification
-
list
: Can be a list of strings or items where any item property can resolve to a string. -
options
: An optional object that may contain the following keys:limit
: Number (default: Infinity) - Top 'limit' items that match the query will be returned.selector
: Function (default: v => v) - Targets a specific property of the item to search for.casing
: String (default: "smart-case", options: "smart-case" | "case-sensitive" | "case-insensitive") - Defines the case sensitivity of the search
-
normalize
: Boolean (default: true) - If true, FZF will remove diacritics from list items. -
tiebreakers
: Array (default: []) - A list of functions that decide the sort order when the score is tied between two results. -
sort
: Boolean (default: true) - If true, result items will be sorted in descending order by their score. -
fuzzy
: String (default: "v2", options: "v1" | "v2" | false) - Selects the version of the fuzzy algorithm to use. -
match
: Function (default: basicMatch) - Can be set tobasicMatch
orextendedMatch
for advanced patterns. -
forward
: Boolean (default: true) - If false, items will be matched from the end.
query
: The search string.- Returns an array of result entries, where each entry includes:
item
(the original item),start
(the start index of the match),end
(the end index + 1 of the match), andscore
(the closeness of the match to the query).