Three Completion Frameworks Configured, Only Two Actually Turned On

My Emacs config has three completion frameworks installed and configured: auto-complete, company, and corfu. Only two of them are actually doing anything. The third has its own dedicated settings block, a custom completion style built specifically for it, and a hook wired up to configure that style — and there is no line anywhere in the config that turns it on.

What's actually running

auto-complete is unconditional and global, set in core/ui.el:

  • (use-package auto-complete
  • :ensure t
  • :init
  • (ac-config-default)
  • (global-auto-complete-mode t))

That's a real, active mode running in every buffer. Then, specifically inside the PHP mode hook in web/phpmode.el, two more things happen on top of it: (auto-complete-mode 1) gets called again — redundant, since the global mode already covers it — and (company-mode 1) turns on Company locally, with its backend list set explicitly to company-phpactor and company-files. So for PHP specifically, two completion UIs are genuinely active at once: auto-complete from the global mode, and Company from the local hook, each with its own idea of what a completion popup should look like.

The one that isn't

corfu gets a full configuration in core/ui.el: corfu-auto and corfu-quit-no-match are set, and there's a custom completion style defined specifically for it —

  • (orderless-define-completion-style orderless-literal-only
  • (orderless-style-dispatchers nil)
  • (orderless-matching-styles '(orderless-literal)))

— hooked in via corfu-mode-hook so that style applies the moment Corfu activates in a buffer. That's real, deliberate work: picking a completion style, restricting it to literal substring matching instead of Orderless's usual fuzzy dispatchers, and making sure it only applies inside Corfu specifically rather than everywhere. What's missing is the one line that would make any of it matter: there's no (corfu-mode 1), no (global-corfu-mode), anywhere in init.el or any of the seven files it loads. corfu-mode-hook is a hook that runs when Corfu's minor mode turns on in a buffer — and nothing in this configuration ever turns it on, globally or per-buffer, so the hook has never fired.

A smaller version of the same thing, one file over

The PHP mode file has an almost identical pattern at a smaller scale. Both company-php and company-phpactor are use-package'd — installed, loaded, ready. Only company-phpactor actually appears in the company-backends list that gets set in the hook. company-php sits there fully available and never referenced by name anywhere else in the file. Same shape as Corfu: a package present and configured enough to look intentional, minus the one line that would actually put it to work.

Why "configured" and "enabled" aren't the same claim

(use-package corfu :ensure t) does exactly one thing on its own: make sure the package is installed and its autoloads are registered, so its functions and variables exist and can be called. It says nothing about whether anything in the running Emacs session ever calls them. Setting corfu-auto to a value, or defining a completion style meant for Corfu specifically, looks like real configuration because it is — but a variable being set and a mode being active are two separate facts, and only one of them showing up gives no signal at all that the other one is missing. There's no error, no startup warning, nothing that would flag "you configured a UI for a mode that's never running." The config quietly does exactly what it says for auto-complete and Company, and just as quietly does nothing for Corfu, and both look identical from the outside — a block of settings sitting in a file, doing their job or not, with no way to tell which just by reading past them.

The actual habit this points at: when auditing a config for what's really active versus what merely looks configured, the settings block is the wrong thing to search for. The thing to search for is the specific call that turns the mode on — global-X-mode, a bare (X-mode 1), a :hook entry that actually attaches to a real hook symbol — and if that line doesn't exist, everything else in the block is inert, however deliberate it looks.

Add new comment

Restricted HTML

  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • Lines and paragraphs break automatically.
  • Web page addresses and email addresses turn into links automatically.
Please share this article on your favorite website or platform.