Effortless Emacs Multiple Major Modes with Polymode

5 Jun 2021emacs

I found myself doing something weird recently. Not for the first time, of course, but this was a special kind of weird.

I was editing a file that’s part of my websiteIn Emacs. Of course. Emacs abides.. It’s the implementation of the search form (reachable from the little magnifying glass in the nav bar). This ends up as a normal HTML page, for sure, but it’s written using an unholy mishmash of Pollen syntax, Alpine.js custom attributes and JavaScript. That makes it unenjoyable to edit, since syntax highlighting and language-aware indentation don’t really work.

This mixed language thing is well-known from React and Vue and Svelte and all those JavaScript frameworks that (one way or another) put HTML templates, CSS and JavaScript in the same file. People using React, Vue or Svelte enjoy the economies of scale that come from using popular things, and there are nice editor modes for them.

If you do weird stuff, you’re on your own.

Actually, you’re not on your own. Polymode has your back. You can read about it below the fold.

This post is going to be way shorter than it could be. When I first had the idea of making this work (this sort of thing usually goes by the name of “multiple major modes” in Emacs), I feared that it was going to be horrifying. I knew that people had written all sorts of ad hoc MMM modes for different applications, and I also knew that there was a thing called mmm-mode that looked very frightening.

A bit of searching around turned up Polymode. From the documentation: “Creating new polymodes normally takes a few lines of code.” That sounded... promising. Suspicious and unlikely, but promising.

It turned out that my cynicism was completely misplaced. Polymode is, not to put too fine a point on it, bloody brilliant. Here’s all the code I needed to add to my Emacs configuration to get my weird Pollen/JavaScript mode going:

(require 'polymode)

(define-hostmode poly-pollen-hostmode :mode 'pollen-mode)

(define-innermode poly-js-pollen-innermode
  :mode 'js-mode
  :head-matcher "^⬨\\(html-\\)?script.*{"
  :tail-matcher "^}"
  :head-mode 'host
  :tail-mode 'host)

(define-polymode poly-pollen-mode
  :hostmode 'poly-pollen-hostmode
  :innermodes '(poly-js-pollen-innermode))

That’s it! You pick a main mode as the “host”, tell Polymode what “inner” modes you want, defining some stuff to allow Polymode to identify the beginning and end of bits of code that should be treated with one of the inner modes, bundle the host and inner modes together into a “polymode”, and you’re off to the races!

There’s a picture below of what it looks like in action: Pollen (with all its pretty little lozenges) being used to write some HTML (yes, I know it looks weird with all the Alpine.js custom attributes), then a Pollen script tag with JavasScript code inside it. And both kinds of code have the right syntax highlighting and indentation rules.

Seriously, this was so much easier than I was expecting it to be. Polymode kicks ass. I’m now on the lookout for other excuses to use it.

Polymode with Pollen host, JavaScript inner mode