Fixing attribute completion in Emacs nxml-mode

Martin Fowler: 20 Feb 2019

I write most pages on this website in an XML format source file, and use emacs to edit those sources. I’m very happy with how nxml-mode works with prose-style XML, making it much more usable than other environments seem to be. But recent changes (with emacs 26.1, IIRC) put a bit of grit into the environment.

One of the great things about working in emacs with XML is that I can define a schema for my XML formats. I do this using Relax NG Compact Syntax which is a very sane and readable way of writing a schema. (Much better than xml-schema or DTDs.) With the schema, emacs gives me completion for XML tags and attributes. nxml-mode did this with its own way of doing completion, but with recent emacs (26.1) this mechanism was shifted to the more usual emacs way, which works better with the many emacs completion tools that are out there.

Sadly, however, this broke a nifty feature in nxml-mode’s completion. If I complete an attribute, nxml-mode would automatically add "= after the attribute name, making it easy for me to immediately type in the value. Since the change, I actually have to type those two characters in myself (the horror… the horror…)

But, of course, the greatest thing about emacs is that when something like this crops up, I can easily fix that with a few emacs functions. So I added the following to my emacs init.

(defun mf-nxml-tag-start ()
  "returns position of < before point"
  (save-excursion (search-backward "<" nil t)))
(defun mf-nxml-at-attribute-name-p ()
  "truthy if in name of an attribute"
  (save-excursion (re-search-backward rng-in-attribute-regex (mf-nxml-tag-start) t)))
(defun mf-nxml-at-attribute-value-p ()
  "truthy if in value of an attribute"
  (save-excursion (re-search-backward rng-in-attribute-value-regex (mf-nxml-tag-start) t)))

(defun mf-nxml-completion-at-point ()
  "completion at point for nxml mode"
  (interactive)
  (cond
   ((mf-nxml-at-attribute-name-p)
    (completion-at-point)
    (insert "=\""))
   ((mf-nxml-at-attribute-value-p)
    (completion-at-point)
    (insert "\""))
   (t (completion-at-point))))

I just wish other tools I use had the Internal Reprogrammability that makes this kind of thing so normal with The One True Editor.