oantolin / orderless

Emacs completion style that matches multiple regexps in any order
GNU General Public License v3.0
776 stars 27 forks source link

Quickly exclude several types of candidates #158

Closed DamienCassou closed 9 months ago

DamienCassou commented 9 months ago

I frequently search my code base for regexp patterns using consult-ripgrep. Very often, I want to exclude test files from search results. My test files have one of these suffixes: "-tests.js" or "spec.component.js". I could type "!-tests.js !spec.component.js" as orderless input but that is tedious.

I thought I could configure a special dispatcher with a dedicated matching style like this:

(defun my/orderless-no-test-dispatcher (component _index _total)
  (when (string= component "=")
    (cons (list 'my/orderless-no-component-test) "")))

(defun my/orderless-no-component-test (_component)
  (rx-to-string `(or (regexp
                      ,(orderless-without-literal ".spec.component.js"))
                     (regexp
                      ,(orderless-without-literal "-tests.js")))))

(add-to-list 'orderless-style-dispatchers #'my/orderless-no-test-dispatcher)

With this, I thought I could just type "=" and get non-test candidates. I thought this was brilliant until I realized that the regexp returned by my/orderless-no-component-test is going to match any possible candidate and is thus 100% stupid :-D.

Something that actually seems to work is to write a dispatcher that can be used several times:

(defun my/orderless-no-test-dispatcher (component index _total)
  (let ((suffixes '(".spec.component.js" "-tests.js")))
    (when (and (<= 0 index (1- (length suffixes))) (string= component "="))
      (let ((result (cons #'orderless-without-literal
                          (elt suffixes index))))
        result))))

With this, I can type "= =" (2 components) and get the desired filter. This is much better than typing the suffixes manually and I can't say I'm proud :-).

Is there a way to configure orderless to reject a candidate if it matches any of several suffixes?

An implementation of orderless-without-regexp could solve my problem (see https://github.com/oantolin/orderless/issues/88) but it doesn't look like it is possible to implement.

oantolin commented 9 months ago

I don't think you can do better than your second attempt if you limit yourself to matching styles and style dispatchers. Those mechanisms only give you a single regexp per component of the input string and what you really want is to turn = into two negative literals. There is however a different mechanism that allows you to produce as many components as you wish: the orderless-component-separator.

So you can abandon your dispatchers and instead set orderless-component-separator to a function that replaces an = with as many negative literals as you need. Here's a simple example that only checks for = at the beginning of the list of components, and relies on the default style dispatcher to kick in later and deal with the !s:

(defun my-whacky-separator (string)
  (let ((components (split-string string " +" t)))
    (if (equal (car components) "=")
        (cons '("!-tests.js" "!.spec.component.js")
              (cdr components))
      components)))

(setq orderless-component-separator #'my-whacky-separator)

Let me know if there's still any issue left to address with that solution.

oantolin commented 9 months ago

Of course you could also store the text !-tests.js !spec.component.js in a text register or bind = to a keyboard macro that types that text for you.

DamienCassou commented 9 months ago

A text register is just perfect. Thanks for the reminder :-).