Status Update: August 2025

Published on to joshleeb's blog

G’day!

This month I’ve been working on Ohm, a fuzzy finder based on Orderless. I introduced Ohm in the last update, and I’ll be writing a dedicated post including motivations and usage examples when it’s ready for use.

So far, the core library is functionally complete but needs more documentation. Progress on an FZF-like command line tool is also underway which I expect to be usable for the next status update.

Unsurprisingly, many of the concepts from Orderless have mapped directly onto Ohm, but the shape has changed a fair bit.

Matchers

At the center of Orderless are Component Matching Styles. These are functions that mostly map a string (the component) to a regex. For example, the orderless-flex matching style would return a.*b.*c for the component abc. This regex is then evaluated over all the candidate strings to produce a set of matches.

Ohm takes a different approach. Instead of producing a regex for use downstream, it defines matchers that find a component in a candidate and optionally return a match, as modeled by the Matcher trait.

trait Matcher {
  fn find(&self, component: &str, candidate: &str) -> Option<Match>;
}
Code 1. Matcher trait in Ohm.

The idea behind this change is that we don’t always want to match with regexes. E.g: in the case of LiteralMatcher it’s simpler (and faster?) to perform substring search instead of compiling and evaluating a regex.

Transformers

The next kind of Orderless components are Style Modifiers which are functions that take a predicate function and a regex, and return a new predicate function that indicates whether a match was found. For example, orderless-not which inverts the matching regex.

Another way to think about modifiers is as matcher transformers. That is, a function takes a matcher as input and produce a new matcher. In Ohm that is exactly how they are modeled.

trait Transformer {
  fn apply(&self, matcher: Arc<dyn Matcher>) -> Arc<dyn Matcher>;
}
Code 2. Transformer trait in Ohm.

Implementing orderless-not (called inverse in Ohm) as a transformer is trivial, and quite a bit more flexible and ergonomic.

fn inverse(matcher: Arc<dyn Matcher>) -> Arc<dyn Matcher> {
    Arc::new(move |component: &str, candidate: &str| {
        matcher.find(component, candidate).map(|m| match m {
            Some(_) => None,
            None => Some(Match::new(...)),
        })
    })
}
Code 3. Implementation of the inverse transformer in Ohm.

Predicates & Dispatching

Lastly, Orderless has Style Dispatchers. These are functions that, given a component, will decide which (if any) matching style to use and modify the component to pass to that style.

In Emacs, the dispatcher I would invoke most often is shown in code 4. This function dispatches to the orderless-flex matching style if the component starts with a ‘~’ prefix, and forwards the component without that prefix.

(defun flex-if-tilde (component _index _total)
  (when (string-prefix-p "~" component)
    `(orderless-flex . ,(substring pattern 1))))
Code 4. flex-if-tilde function as an Orderless style dispatcher in Emacs.

Again, Ohm takes a different approach, opting to split Orderless dispatchers into a predicate and a dispatching matcher.

trait Predicate {
  fn check(&self, component: &str, position: Position) -> Option<String>;
}
Code 5. Predicate trait in Ohm.

Predicates in Ohm are very simple. They take a component and its position (e.g: second component out of five) and return the modified component if the predicate succeeds, otherwise None. This change allows for more of a general implementation, such as in code 6 with the PrefixPredicate.

struct PrefixPredicate(char);

impl Predicate for PrefixPredicate {
  fn check(&self, component: &str, _position: Position) -> Option<String> {
    component.strip_prefix(self.0).map(String::from)
  }
}
Code 6. PrefixPredicate implementation in Ohm.

Predicates on their own aren’t very useful. They are intended to be used as part of a matcher (or transformer). For example, DispatchMatcher where a matcher is dispatched for the first successful predicate, otherwise the fallback is used.

struct DispatchMatcher {
  entries: Vec<(Arc<dyn Predicate>, Arc<dyn Matcher>)>,
  fallback: Arc<dyn Matcher>,
}

let root = DispatchMatcher::builder()
  .with(PrefixPredicate('='), LiteralMatcher::default())
  .with(PrefixPredicate('~'), FlexMatcher::default())
  .build(RegexMatcher::default());
Code 7. DispatchMatcher structure and usage in Ohm.

You can see with the Arcs there’s a bit more machinery and trait implementations to improve the ergonomics and support using Ohm across threads (i.e. implementing Send + Sync). For the most part though, that’s everything in the core of Ohm.

Wrapping Up

Next I’ll be working on a command line fuzzy finder that makes use of Ohm core and has a very similar interface to FZF. However, I’ll be taking a break from personal projects and everything else in September as I am getting married (!!!)

That’s all for now. See you in October!