I have already discussed how one can leverage ugrep and consult-xref to make a completing-read variant of project-find-regexp.1

However, today I would like to go a bit further and show how easily Consult can be extended to include a command that behaves like consult-grep, consult-git-grep, and consult-ripgrep, but with ugrep as its engine. True, something along these lines has been around for a while.2 And yet I have no need for an external package when Consult has everything in place in order to devise a custom command.

By inspecting the code behind consult-git-grep and consult-ripgrep the pattern is pretty clear:

  • a defcustom with the command (e.g., git grep) and its arguments
  • a builder function to create the actual command to be invoked (e.g., consult--git-grep-make-builder)
  • a wrapper around consult--grep (e.g., consult-git-grep)

Therefore, the solution is simple:

(defvar mu-consult-ugrep-args (string-join '("ugrep"
                                             "--color=never"
                                             "--exclude-dir=.git/"
                                             "--hidden"
                                             "--ignore-binary"
                                             "--ignore-case"
                                             "--line-buffered"
                                             "--line-number"
                                             "--null"
                                             "--recursive")
                                           " "))

(defun mu-consult-ugrep-make-builder (paths)
  "Create ugrep command line builder given PATHS."
  (let ((cmd (consult--build-args mu-consult-ugrep-args)))
    (lambda (input)
      (pcase-let* ((`(,arg . ,opts) (consult--command-split input))
                   (`(,re . ,hl)
                    (funcall consult--regexp-compiler arg 'extended t)))
        (when re
          (cons (append cmd
                        (cdr (mapcan (lambda (x) (list "--and" "-e" x)) re))
                        opts paths)
                hl))))))

(defun mu-consult-ugrep (&optional dir initial)
  "Search with `ugrep' for files in DIR with INITIAL input."
  (interactive "P")
  (require 'consult)
  (consult--grep "Ugrep" #'mu-consult-ugrep-make-builder dir initial))

Since I am always using the same ugrep options, my builder function is a bit simpler than consult--git-grep-make-builder or consult--ripgrep-make-builder. Other than that, it’s only a matter of understanding the right options for ugrep.

Building on top of Consult is great. Much like consult-git-grep and consult-ripgrep, the integration with Embark is around the corner. And now that the power of the minibuffer is available during my searches I have no use for mu-recursive-grep any more.3