Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save itsbrex/afef66e8bc88b6807ca60b71ae268b88 to your computer and use it in GitHub Desktop.
Save itsbrex/afef66e8bc88b6807ca60b71ae268b88 to your computer and use it in GitHub Desktop.

FuzzyJsPlus: Advanced Fuzzy Matching for JavaScript Environments

#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.

Basic Usage

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

Non-String List

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,
});

Case Sensitivity

By default, Fzf performs smart case searching, meaning it takes into account both uppercase and lowercase. You can customize this behavior in the options.

Highlighting Matched Characters

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

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],
});

Disabling Sorter

You can disable the sorter by passing sort: false in the options:

// filename: disableSort.js
const fzf = new Fzf(list, {
  sort

## ASSISTANT
: false,
});

Matching Backwards

To prefer characters at the end of strings, use forward: false in the options:

// filename: matchBackwards.js
const fzf = new Fzf(list, {
  forward: false,
});

Async Finder

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

TypeScript Usage

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!

CLI-like Behavior

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],
});

Modification to List

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

API

Constructor: new Fzf(list, options?)

  • 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 to basicMatch or extendedMatch for advanced patterns.

  • forward: Boolean (default: true) - If false, items will be matched from the end.

Method: fzf.find(query)

  • 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), and score (the closeness of the match to the query).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment