Embarking in a new affair with completions


What a year this 2020. Beside the pandemic changing our lives in unpredictable ways, from the perspective of my Emacs usage much was going on in my init.el until I felt that a temporary break was necessary.

Most of my tinkering concerned the minibuffer and its completion mechanisms. I finished last year and started this one with Helm, but I am about to close 2020 with the built-in minibuffer completion and Embark providing candidates. A solution that Protesilaos Stavrou explored and which has been really suiting me.

Before detailing how I adapted his approach to my preferences, though, let me give you a bit of rationale. First, Helm rocks and it rocks hard. Its power is evident after a couple of hours with it. I briefly used Icomplete tweaked to display its results vertically, but I didn’t find it responsive enough to stick with it. Then I found out about Selectrum, which fixed the responsiveness but eventually was not adding a significant difference compared to Helm. True, Selectrum has a simpler codebase, but were I only to look at my daily interactions with Emacs, only the user interface can tell Helm and Selectrum apart.

Protesilaos took a different path. He wants to understand the code in front of him, so the less changes the better. In this regard Helm, Selectrum, and even Icomplete add a layer of indirection between him and the minibuffer. He is right in this. The minibuffer is more capable than the plethora of completion frameworks may suggest, and one can leverage its strength without forcing it to behave in a totally new way. Protesilaos’ reasoning got me thinking. Once again, am I looking for a solution from the outside before having really understood what lies underneath my beloved text editor?

Following Protesilaos’ steps, I set up the minibuffer to rely only on orderless and Embark, with Consult chiming in for a some of operations like better history in shell-mode and an improved apropos. What I added to Protesilaos’ code is the only thing that I felt was missing: a command to search for the symbol at point in my project, with the results displayed in an embark-live-occur window in order to quickly jump to a specific entry. Over at the Consult’s GitHub there are talks about a consult-rg utility which would serves this purpose, and there has been suggestions of using project-find-regexp as well.

(defun mu-project-find-refs ()
  "Use `project-find-regexp' to search for thing at point."
  (interactive)
  (if-let (tap (thing-at-point 'symbol))
      (project-find-regexp tap)
    (message "Nothing at point to search for")))

Easy enough to understand. However, if you, like me, set up Embark like Protesilaos does you’ll notice that this command doesn’t show any candidate unless you type something at the Jump to definition prompt. The candidates are there already, though, so I have to avoid waiting for an input and display the candidate list immediately.

The solution is straightforward: just remove embark-live-occur-after-input from minibuffer-setup-hook and use embark-live-occur-after-delay instead. I added a :before advice on mu-project-find-refs for this. The beauty of this advice is that it works elsewhere as well. For instance, I have been using it for consult-flymake and flyspell-correct-at-point too.

Note that this advice requires a little change to the original minibuffer-setup-hook I have lifted from Protesilaos. Instead of adding embark-live-occur-after-input to it I am using this to ensure only embark-live-occur-after-input is present.

(defun mu-embark-live-occur-after-input ()
  "Ensure only `embark-live-occur-after-input' is active."
  (remove-hook 'minibuffer-setup-hook #'embark-live-occur-after-delay)
  (add-hook 'minibuffer-setup-hook #'embark-live-occur-after-input))

Without a dedicated completion framework the minibuffer may feel rather basic at first, but do not let it deceive you with its frugality. Like project.el, all it needs is love.